Compare commits
47 Commits
59716cc794
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
9b1b223186
|
|||
|
2375abf688
|
|||
|
1640013546
|
|||
|
b3c4248cae
|
|||
|
348e8ac66e
|
|||
|
d3cd6b18c8
|
|||
|
0224b5675f
|
|||
|
3044bb37a5
|
|||
|
23f6d70e6c
|
|||
|
4f89d24801
|
|||
|
54941a7702
|
|||
|
9ed90a6e26
|
|||
|
a7fce43f70
|
|||
|
11ed946c63
|
|||
|
1f6b0b1f37
|
|||
|
6541d4dfd6
|
|||
|
9fa97919b3
|
|||
|
b6ef13c198
|
|||
|
67014b9c4e
|
|||
|
e6538d221f
|
|||
|
bfc5c3933c
|
|||
|
3a79103ca3
|
|||
|
ea1e613fd1
|
|||
|
912b510c67
|
|||
|
63a39f1e72
|
|||
|
d4ac14ae92
|
|||
|
23c3731f06
|
|||
|
a7c1fd6ee3
|
|||
|
7dc2ba7549
|
|||
|
df0d5b7321
|
|||
|
0d57851bc0
|
|||
|
cb6af550e2
|
|||
| 75637b7177 | |||
|
5621c62e3d
|
|||
|
bbc975fb38
|
|||
|
5b182580a7
|
|||
|
762d414d54
|
|||
|
7933bea68a
|
|||
|
b83ddd52a4
|
|||
|
d145f85d17
|
|||
|
14ce2c6f9b
|
|||
|
7c6e0dc2a8
|
|||
|
b13a256eb6
|
|||
|
47c63705e2
|
|||
|
d1a6728864
|
|||
|
291c65ed18
|
|||
|
4d4578ff46
|
+295
@@ -0,0 +1,295 @@
|
||||
---
|
||||
BasedOnStyle: Mozilla
|
||||
AccessModifierOffset: 0
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: true
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseArrows: false
|
||||
AlignCaseColons: false
|
||||
AlignConsecutiveTableGenBreakingDAGArgColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenCondOperatorColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenDefinitionColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionDeclarations: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseExpressionOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortNamespacesOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackLongBracedList: true
|
||||
BinPackParameters: OnePerLine
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: true
|
||||
IndentBraces: true
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: true
|
||||
BracedInitializerIndentWidth: -1
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakAfterReturnType: None
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Allman
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTemplateCloser: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakBinaryOperations: Never
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BreakInheritanceList: BeforeComma
|
||||
BreakStringLiterals: true
|
||||
BreakTemplateDeclarations: Yes
|
||||
ColumnLimit: 128
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
EnumTrailingComma: Leave
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: ^"(llvm|llvm-c|clang|clang-c)/
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: ^(<|"(gtest|gmock|isl|json)/)
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: .*
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: (Test)?$
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: true
|
||||
IndentCaseLabels: true
|
||||
IndentExportBlock: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLines:
|
||||
AtEndOfFile: false
|
||||
AtStartOfBlock: true
|
||||
AtStartOfFile: true
|
||||
KeepFormFeed: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MainIncludeChar: Quote
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
OneLineFormatOffRegex: ""
|
||||
PPIndentWidth: -1
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakBeforeMemberAccess: 150
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: Always
|
||||
RemoveBracesLLVM: false
|
||||
RemoveEmptyLinesInUnwrappedLines: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes:
|
||||
Enabled: true
|
||||
IgnoreCase: false
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterOperatorKeyword: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: false
|
||||
AfterForeachMacros: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterIfMacros: true
|
||||
AfterNot: false
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: false
|
||||
InConditionalStatements: false
|
||||
InCStyleCasts: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
UseTab: ForIndentation
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
WrapNamespaceBodyWithEmptyLines: Leave
|
||||
@@ -18,14 +18,54 @@ jobs:
|
||||
|
||||
- uses: https://codeberg.org/mlugg/setup-zig@v2
|
||||
|
||||
- name: Install MPSS
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
|
||||
apt-get update
|
||||
apt-get install -y unzip curl
|
||||
|
||||
MPSS_URL="https://drive.kbz8.me/dav/public-files/KSKeiOKxnFPyfws/mpss.zip"
|
||||
MPSS_ZIP="/tmp/mpss.zip"
|
||||
MPSS_BIN="/opt/mpss/3.8.6/sysroots/k1om-mpss-linux/bin"
|
||||
|
||||
curl --fail --location --show-error --progress-bar "$MPSS_URL" -o "$MPSS_ZIP"
|
||||
|
||||
size="$(stat -c%s "$MPSS_ZIP")"
|
||||
test "$size" -gt 300000000
|
||||
|
||||
unzip -t "$MPSS_ZIP" >/dev/null
|
||||
|
||||
rm -rf /opt/mpss
|
||||
unzip -q "$MPSS_ZIP" -d /opt
|
||||
chmod -R a+rX /opt/mpss
|
||||
|
||||
test -d "$MPSS_BIN"
|
||||
|
||||
# Append as fallback for following CI steps.
|
||||
echo "PATH=$PATH:$MPSS_BIN" >> "$GITHUB_ENV"
|
||||
|
||||
echo "MPSS installed."
|
||||
|
||||
- name: Check MPSS PATH
|
||||
run: |
|
||||
echo "$PATH"
|
||||
ls -la /opt/mpss/3.8.6/sysroots/k1om-mpss-linux/bin | head -50
|
||||
|
||||
- name: ZLint pass
|
||||
uses: DonIsaac/zlint-action@v0.0.1
|
||||
|
||||
- name: Building Ape
|
||||
run: zig build ape
|
||||
run: zig build ape --release=safe
|
||||
|
||||
- name: Building Soft
|
||||
run: zig build soft
|
||||
run: zig build soft --release=safe
|
||||
|
||||
- name: Building Flint
|
||||
run: zig build flint
|
||||
run: zig build flint --release=safe
|
||||
|
||||
- name: Building Phi
|
||||
run: zig build phi --release=safe
|
||||
|
||||
- name: Generating docs
|
||||
run: zig build docs
|
||||
@@ -42,3 +82,19 @@ jobs:
|
||||
local-path: "./zig-out/docs"
|
||||
remote-path: "/www"
|
||||
sync: full
|
||||
|
||||
- name: Generating IR docs
|
||||
run: zig build docs-ir
|
||||
|
||||
- name: Deploying IR docs
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: milanmk/actions-file-deployer@master
|
||||
with:
|
||||
remote-protocol: sftp
|
||||
remote-host: ${{ secrets.SFTP_HOST_DOCS_IR }}
|
||||
remote-user: ${{ secrets.SFTP_USER_DOCS_IR }}
|
||||
remote-password: ${{ secrets.SFTP_PASSWORD_DOCS_IR }}
|
||||
remote-port: 6969
|
||||
local-path: "./zig-out/docs-ir"
|
||||
remote-path: "/www"
|
||||
sync: full
|
||||
|
||||
@@ -15,11 +15,54 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://codeberg.org/mlugg/setup-zig@v2
|
||||
|
||||
- name: Install MPSS
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
|
||||
apt-get update
|
||||
apt-get install -y unzip curl
|
||||
|
||||
MPSS_URL="https://drive.kbz8.me/dav/public-files/KSKeiOKxnFPyfws/mpss.zip"
|
||||
MPSS_ZIP="/tmp/mpss.zip"
|
||||
MPSS_BIN="/opt/mpss/3.8.6/sysroots/k1om-mpss-linux/bin"
|
||||
|
||||
curl --fail --location --show-error --progress-bar "$MPSS_URL" -o "$MPSS_ZIP"
|
||||
|
||||
size="$(stat -c%s "$MPSS_ZIP")"
|
||||
test "$size" -gt 300000000
|
||||
|
||||
unzip -t "$MPSS_ZIP" >/dev/null
|
||||
|
||||
rm -rf /opt/mpss
|
||||
unzip -q "$MPSS_ZIP" -d /opt
|
||||
chmod -R a+rX /opt/mpss
|
||||
|
||||
test -d "$MPSS_BIN"
|
||||
|
||||
# Append as fallback for following CI steps.
|
||||
echo "PATH=$PATH:$MPSS_BIN" >> "$GITHUB_ENV"
|
||||
|
||||
echo "MPSS installed."
|
||||
|
||||
- name: Check MPSS PATH
|
||||
run: |
|
||||
echo "$PATH"
|
||||
ls -la /opt/mpss/3.8.6/sysroots/k1om-mpss-linux/bin | head -50
|
||||
|
||||
- name: Installing dependencies
|
||||
run: apt update && apt install spirv-tools
|
||||
|
||||
- name: Ape Tests
|
||||
run: zig build test-ape
|
||||
run: zig build test-ape --release=safe
|
||||
|
||||
- name: Soft Tests
|
||||
run: zig build test-soft
|
||||
run: zig build test-soft --release=safe
|
||||
|
||||
- name: Flint Tests
|
||||
run: zig build test-flint
|
||||
run: zig build test-flint --release=safe
|
||||
|
||||
- name: Phi Tests
|
||||
run: zig build test-phi --release=safe
|
||||
|
||||
- name: IR Tests
|
||||
run: zig build test-ir --release=safe
|
||||
|
||||
@@ -8,8 +8,6 @@ scripts/__pycache__/
|
||||
*.o
|
||||
.gdb_history
|
||||
*.json
|
||||
*.png
|
||||
!logo.png
|
||||
*.bin
|
||||
*.qpa
|
||||
*.xml
|
||||
|
||||
@@ -14,7 +14,7 @@ To understand Vulkan - not as a humble API mere mortals call upon, but as a laby
|
||||
It does not seek to produce a performant or production-worthy driver. \
|
||||
*The gods are merciful, but not that merciful.*
|
||||
|
||||
## Soft [software implementation]
|
||||
## Soft [software]
|
||||
|
||||
Soft be a software implementation of the Vulkan specification, abiding within this driver's own codebase.\
|
||||
It maketh use of a bespoke [SPIR-V interpreter](https://git.kbz8.me/kbz_8/SPIRV-Interpreter) and renderer, by whose workings its labours are carried forth.
|
||||
@@ -89,11 +89,11 @@ vkCmdResetEvent | âś… Implemented
|
||||
vkCmdResetQueryPool | âś… Implemented
|
||||
vkCmdResolveImage | âś… Implemented
|
||||
vkCmdSetBlendConstants | âś… Implemented
|
||||
vkCmdSetDepthBias | ⚙️ WIP
|
||||
vkCmdSetDepthBounds | ⚙️ WIP
|
||||
vkCmdSetDepthBias | âś… Implemented
|
||||
vkCmdSetDepthBounds | âś… Implemented
|
||||
vkCmdSetDeviceMaskKHR | âś… Implemented
|
||||
vkCmdSetEvent | âś… Implemented
|
||||
vkCmdSetLineWidth | ⚙️ WIP
|
||||
vkCmdSetLineWidth | âś… Implemented
|
||||
vkCmdSetScissor | âś… Implemented
|
||||
vkCmdSetStencilCompareMask | âś… Implemented
|
||||
vkCmdSetStencilReference | âś… Implemented
|
||||
@@ -101,7 +101,7 @@ vkCmdSetStencilWriteMask | âś… Implemented
|
||||
vkCmdSetViewport | âś… Implemented
|
||||
vkCmdUpdateBuffer | âś… Implemented
|
||||
vkCmdWaitEvents | âś… Implemented
|
||||
vkCmdWriteTimestamp | ⚙️ WIP
|
||||
vkCmdWriteTimestamp | âś… Implemented
|
||||
vkCreateBuffer | âś… Implemented
|
||||
vkCreateBufferView | âś… Implemented
|
||||
vkCreateCommandPool | âś… Implemented
|
||||
@@ -162,11 +162,14 @@ vkFlushMappedMemoryRanges | âś… Implemented
|
||||
vkFreeCommandBuffers | âś… Implemented
|
||||
vkFreeDescriptorSets | âś… Implemented
|
||||
vkFreeMemory | âś… Implemented
|
||||
vkGetBufferDeviceAddress | âś… Implemented
|
||||
vkGetBufferDeviceAddressEXT | âś… Implemented
|
||||
vkGetBufferDeviceAddressKHR | âś… Implemented
|
||||
vkGetBufferMemoryRequirements | âś… Implemented
|
||||
vkGetDeviceGroupPeerMemoryFeaturesKHR | âś… Implemented
|
||||
vkGetDeviceGroupPresentCapabilitiesKHR | âś… Implemented
|
||||
vkGetDeviceGroupSurfacePresentModesKHR | âś… Implemented
|
||||
vkGetDeviceMemoryCommitment | ⚙️ WIP
|
||||
vkGetDeviceMemoryCommitment | âś… Implemented
|
||||
vkGetDeviceProcAddr | âś… Implemented
|
||||
vkGetDeviceQueue | âś… Implemented
|
||||
vkGetEventStatus | âś… Implemented
|
||||
@@ -215,6 +218,406 @@ vkWaitForFences | âś… Implemented
|
||||
|
||||
[Here](https://vulkan-driver.kbz8.me/cts/soft/) shalt thou find a most meticulous account of the Vulkan 1.0 conformance trials, set forth for thy scrutiny.
|
||||
|
||||
## Phi [Xeon Phi KNC]
|
||||
|
||||
Phi be an implementation of the Vulkan specification, wrought for the Xeon Phi Knights Corner cards.
|
||||
Whether the Knights Landing cards shall one day receive the same providence remaineth to be seen.
|
||||
|
||||
### Build
|
||||
|
||||
To bring forth the driver:
|
||||
```
|
||||
zig build phi --release=[fast|safe|small]
|
||||
```
|
||||
|
||||
And for those who seek another manner of building:
|
||||
```
|
||||
zig build --help
|
||||
```
|
||||
|
||||
The same rites apply as for the Soft build.
|
||||
|
||||
#### Vulkan 1.0 specification
|
||||
<details>
|
||||
<summary>
|
||||
Here standeth the present reckoning of thy Vulkan 1.0 implementation
|
||||
</summary>
|
||||
|
||||
\
|
||||
⚠️ Implemented, yet perchance not fully tested nor proven conformant, but rather working in a manner most general to thee and thine.\
|
||||
Assume thou that functions lacking in this array are, for now, not intended to be wrought.
|
||||
|
||||
Name | Status
|
||||
-------------------------------------------------|--------
|
||||
vkAcquireNextImage2KHR | ⚙️ WIP
|
||||
vkAcquireNextImageKHR | ⚙️ WIP
|
||||
vkAllocateCommandBuffers | âś… Implemented
|
||||
vkAllocateDescriptorSets | ⚙️ WIP
|
||||
vkAllocateMemory | âś… Implemented
|
||||
vkBeginCommandBuffer | âś… Implemented
|
||||
vkBindBufferMemory | ⚙️ WIP
|
||||
vkBindImageMemory | âś… Implemented
|
||||
vkCmdBeginQuery | ⚙️ WIP
|
||||
vkCmdBeginRenderPass | ⚙️ WIP
|
||||
vkCmdBindDescriptorSets | ⚙️ WIP
|
||||
vkCmdBindIndexBuffer | ⚙️ WIP
|
||||
vkCmdBindPipeline | ⚙️ WIP
|
||||
vkCmdBindVertexBuffers | ⚙️ WIP
|
||||
vkCmdBlitImage | âś… Implemented
|
||||
vkCmdClearAttachments | ⚙️ WIP
|
||||
vkCmdClearColorImage | ⚙️ WIP
|
||||
vkCmdClearDepthStencilImage | ⚙️ WIP
|
||||
vkCmdCopyBuffer | âś… Implemented
|
||||
vkCmdCopyBufferToImage | âś… Implemented
|
||||
vkCmdCopyImage | ⚙️ WIP
|
||||
vkCmdCopyImageToBuffer | âś… Implemented
|
||||
vkCmdCopyQueryPoolResults | ⚙️ WIP
|
||||
vkCmdDispatch | ⚙️ WIP
|
||||
vkCmdDispatchBaseKHR | ⚙️ WIP
|
||||
vkCmdDispatchIndirect | ⚙️ WIP
|
||||
vkCmdDraw | ⚙️ WIP
|
||||
vkCmdDrawIndexed | ⚙️ WIP
|
||||
vkCmdDrawIndexedIndirect | ⚙️ WIP
|
||||
vkCmdDrawIndirect | ⚙️ WIP
|
||||
vkCmdEndQuery | ⚙️ WIP
|
||||
vkCmdEndRenderPass | ⚙️ WIP
|
||||
vkCmdExecuteCommands | ⚙️ WIP
|
||||
vkCmdFillBuffer | âś… Implemented
|
||||
vkCmdNextSubpass | ⚙️ WIP
|
||||
vkCmdPipelineBarrier | ⚙️ WIP
|
||||
vkCmdPushConstants | ⚙️ WIP
|
||||
vkCmdResetEvent | ⚙️ WIP
|
||||
vkCmdResetQueryPool | ⚙️ WIP
|
||||
vkCmdResolveImage | ⚙️ WIP
|
||||
vkCmdSetBlendConstants | ⚙️ WIP
|
||||
vkCmdSetDepthBias | ⚙️ WIP
|
||||
vkCmdSetDepthBounds | ⚙️ WIP
|
||||
vkCmdSetDeviceMaskKHR | ⚙️ WIP
|
||||
vkCmdSetEvent | ⚙️ WIP
|
||||
vkCmdSetLineWidth | ⚙️ WIP
|
||||
vkCmdSetScissor | ⚙️ WIP
|
||||
vkCmdSetStencilCompareMask | ⚙️ WIP
|
||||
vkCmdSetStencilReference | ⚙️ WIP
|
||||
vkCmdSetStencilWriteMask | ⚙️ WIP
|
||||
vkCmdSetViewport | ⚙️ WIP
|
||||
vkCmdUpdateBuffer | ⚙️ WIP
|
||||
vkCmdWaitEvents | ⚙️ WIP
|
||||
vkCmdWriteTimestamp | ⚙️ WIP
|
||||
vkCreateBuffer | ⚙️ WIP
|
||||
vkCreateBufferView | ⚙️ WIP
|
||||
vkCreateCommandPool | âś… Implemented
|
||||
vkCreateComputePipelines | ⚙️ WIP
|
||||
vkCreateDescriptorPool | ⚙️ WIP
|
||||
vkCreateDescriptorSetLayout | ⚙️ WIP
|
||||
vkCreateDevice | âś… Implemented
|
||||
vkCreateEvent | ⚙️ WIP
|
||||
vkCreateFence | ⚙️ WIP
|
||||
vkCreateFramebuffer | ⚙️ WIP
|
||||
vkCreateGraphicsPipelines | ⚙️ WIP
|
||||
vkCreateImage | âś… Implemented
|
||||
vkCreateImageView | ⚙️ WIP
|
||||
vkCreateInstance | âś… Implemented
|
||||
vkCreatePipelineCache | ⚙️ WIP
|
||||
vkCreatePipelineLayout | ⚙️ WIP
|
||||
vkCreateQueryPool | ⚙️ WIP
|
||||
vkCreateRenderPass | ⚙️ WIP
|
||||
vkCreateSampler | ⚙️ WIP
|
||||
vkCreateSemaphore | ⚙️ WIP
|
||||
vkCreateShaderModule | ⚙️ WIP
|
||||
vkCreateSwapchainKHR | ⚙️ WIP
|
||||
vkCreateWaylandSurfaceKHR | ⚙️ WIP
|
||||
vkCreateWin32SurfaceKHR | ⚙️ WIP
|
||||
vkCreateXcbSurfaceKHR | ⚙️ WIP
|
||||
vkCreateXlibSurfaceKHR | ⚙️ WIP
|
||||
vkDestroyBuffer | ⚙️ WIP
|
||||
vkDestroyBufferView | ⚙️ WIP
|
||||
vkDestroyCommandPool | âś… Implemented
|
||||
vkDestroyDescriptorPool | ⚙️ WIP
|
||||
vkDestroyDescriptorSetLayout | ⚙️ WIP
|
||||
vkDestroyDevice | âś… Implemented
|
||||
vkDestroyEvent | ⚙️ WIP
|
||||
vkDestroyFence | ⚙️ WIP
|
||||
vkDestroyFramebuffer | ⚙️ WIP
|
||||
vkDestroyImage | âś… Implemented
|
||||
vkDestroyImageView | ⚙️ WIP
|
||||
vkDestroyInstance | âś… Implemented
|
||||
vkDestroyPipeline | ⚙️ WIP
|
||||
vkDestroyPipelineCache | ⚙️ WIP
|
||||
vkDestroyPipelineLayout | ⚙️ WIP
|
||||
vkDestroyQueryPool | ⚙️ WIP
|
||||
vkDestroyRenderPass | ⚙️ WIP
|
||||
vkDestroySampler | ⚙️ WIP
|
||||
vkDestroySemaphore | ⚙️ WIP
|
||||
vkDestroyShaderModule | ⚙️ WIP
|
||||
vkDestroySurfaceKHR | ⚙️ WIP
|
||||
vkDestroySwapchainKHR | ⚙️ WIP
|
||||
vkDeviceWaitIdle | ⚙️ WIP
|
||||
vkEndCommandBuffer | âś… Implemented
|
||||
vkEnumerateDeviceExtensionProperties | ⚙️ WIP
|
||||
vkEnumerateDeviceLayerProperties | ⚙️ WIP
|
||||
vkEnumerateInstanceExtensionProperties | ⚙️ WIP
|
||||
vkEnumerateInstanceLayerProperties | ⚙️ WIP
|
||||
vkEnumeratePhysicalDeviceGroupsKHR | ⚙️ WIP
|
||||
vkEnumeratePhysicalDevices | âś… Implemented
|
||||
vkFlushMappedMemoryRanges | ⚙️ WIP
|
||||
vkFreeCommandBuffers | ⚙️ WIP
|
||||
vkFreeDescriptorSets | ⚙️ WIP
|
||||
vkFreeMemory | âś… Implemented
|
||||
vkGetBufferDeviceAddress | ⚙️ WIP
|
||||
vkGetBufferDeviceAddressEXT | ⚙️ WIP
|
||||
vkGetBufferDeviceAddressKHR | ⚙️ WIP
|
||||
vkGetBufferMemoryRequirements | ⚙️ WIP
|
||||
vkGetDeviceGroupPeerMemoryFeaturesKHR | ⚙️ WIP
|
||||
vkGetDeviceGroupPresentCapabilitiesKHR | ⚙️ WIP
|
||||
vkGetDeviceGroupSurfacePresentModesKHR | ⚙️ WIP
|
||||
vkGetDeviceMemoryCommitment | ⚙️ WIP
|
||||
vkGetDeviceProcAddr | ⚙️ WIP
|
||||
vkGetDeviceQueue | ⚙️ WIP
|
||||
vkGetEventStatus | ⚙️ WIP
|
||||
vkGetFenceStatus | ⚙️ WIP
|
||||
vkGetImageMemoryRequirements | âś… Implemented
|
||||
vkGetImageSparseMemoryRequirements | ⚙️ WIP
|
||||
vkGetImageSubresourceLayout | âś… Implemented
|
||||
vkGetInstanceProcAddr | ⚙️ WIP
|
||||
vkGetPhysicalDeviceFeatures | âś… Implemented
|
||||
vkGetPhysicalDeviceFormatProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceImageFormatProperties | âś… Implemented
|
||||
vkGetPhysicalDeviceMemoryProperties | âś… Implemented
|
||||
vkGetPhysicalDeviceProperties | âś… Implemented
|
||||
vkGetPhysicalDeviceQueueFamilyProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSparseImageFormatProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceWaylandPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPipelineCacheData | ⚙️ WIP
|
||||
vkGetQueryPoolResults | ⚙️ WIP
|
||||
vkGetRenderAreaGranularity | ⚙️ WIP
|
||||
vkGetSwapchainImagesKHR | ⚙️ WIP
|
||||
vkInvalidateMappedMemoryRanges | ⚙️ WIP
|
||||
vkMapMemory | ⚙️ WIP
|
||||
vkMergePipelineCaches | ⚙️ WIP
|
||||
vkQueueBindSparse | ⚙️ WIP
|
||||
vkQueuePresentKHR | ⚙️ WIP
|
||||
vkQueueSubmit | âś… Implemented
|
||||
vkQueueWaitIdle | ⚙️ WIP
|
||||
vkResetCommandBuffer | ⚙️ WIP
|
||||
vkResetCommandPool | âś… Implemented
|
||||
vkResetDescriptorPool | ⚙️ WIP
|
||||
vkResetEvent | ⚙️ WIP
|
||||
vkResetFences | ⚙️ WIP
|
||||
vkResetQueryPool | ⚙️ WIP
|
||||
vkSetEvent | ⚙️ WIP
|
||||
vkUnmapMemory | ⚙️ WIP
|
||||
vkUpdateDescriptorSets | ⚙️ WIP
|
||||
vkWaitForFences | ⚙️ WIP
|
||||
</details>
|
||||
|
||||
## Flint [Intel]
|
||||
|
||||
Flint be an implementation of the Vulkan specification, fashioned for the elder Intel iGPUs of generations 9 through 11.
|
||||
Whether Haswell and those that followed-even the Xe kindred-shall one day partake of its workings remaineth unwritten.
|
||||
|
||||
### Build
|
||||
|
||||
The rite of forging the driver:
|
||||
```
|
||||
zig build flint --release=[fast|safe|small]
|
||||
```
|
||||
|
||||
Should the foregoing not suffice, further build options await thee:
|
||||
```
|
||||
zig build --help
|
||||
```
|
||||
|
||||
The same rites apply as for the Soft and Phi build.
|
||||
|
||||
#### Vulkan 1.0 specification
|
||||
<details>
|
||||
<summary>
|
||||
Behold the present state of thy Vulkan 1.0 implementation
|
||||
</summary>
|
||||
|
||||
\
|
||||
⚠️ Implemented, yet perchance not fully tested nor proven conformant, but rather working in a manner most general to thee and thine.\
|
||||
Assume thou that functions lacking in this array are, for now, not intended to be wrought.
|
||||
|
||||
Name | Status
|
||||
-------------------------------------------------|--------
|
||||
vkAcquireNextImage2KHR | ⚙️ WIP
|
||||
vkAcquireNextImageKHR | ⚙️ WIP
|
||||
vkAllocateCommandBuffers | âś… Implemented
|
||||
vkAllocateDescriptorSets | ⚙️ WIP
|
||||
vkAllocateMemory | âś… Implemented
|
||||
vkBeginCommandBuffer | âś… Implemented
|
||||
vkBindBufferMemory | âś… Implemented
|
||||
vkBindImageMemory | âś… Implemented
|
||||
vkCmdBeginQuery | ⚙️ WIP
|
||||
vkCmdBeginRenderPass | ⚙️ WIP
|
||||
vkCmdBindDescriptorSets | ⚙️ WIP
|
||||
vkCmdBindIndexBuffer | ⚙️ WIP
|
||||
vkCmdBindPipeline | ⚙️ WIP
|
||||
vkCmdBindVertexBuffers | ⚙️ WIP
|
||||
vkCmdBlitImage | ⚙️ WIP
|
||||
vkCmdClearAttachments | ⚙️ WIP
|
||||
vkCmdClearColorImage | ⚙️ WIP
|
||||
vkCmdClearDepthStencilImage | ⚙️ WIP
|
||||
vkCmdCopyBuffer | âś… Implemented
|
||||
vkCmdCopyBufferToImage | âś… Implemented
|
||||
vkCmdCopyImage | âś… Implemented
|
||||
vkCmdCopyImageToBuffer | âś… Implemented
|
||||
vkCmdCopyQueryPoolResults | ⚙️ WIP
|
||||
vkCmdDispatch | ⚙️ WIP
|
||||
vkCmdDispatchBaseKHR | ⚙️ WIP
|
||||
vkCmdDispatchIndirect | ⚙️ WIP
|
||||
vkCmdDraw | ⚙️ WIP
|
||||
vkCmdDrawIndexed | ⚙️ WIP
|
||||
vkCmdDrawIndexedIndirect | ⚙️ WIP
|
||||
vkCmdDrawIndirect | ⚙️ WIP
|
||||
vkCmdEndQuery | ⚙️ WIP
|
||||
vkCmdEndRenderPass | ⚙️ WIP
|
||||
vkCmdExecuteCommands | âś… Implemented
|
||||
vkCmdFillBuffer | âś… Implemented
|
||||
vkCmdNextSubpass | ⚙️ WIP
|
||||
vkCmdPipelineBarrier | ⚙️ WIP
|
||||
vkCmdPushConstants | ⚙️ WIP
|
||||
vkCmdResetEvent | ⚙️ WIP
|
||||
vkCmdResetQueryPool | ⚙️ WIP
|
||||
vkCmdResolveImage | ⚙️ WIP
|
||||
vkCmdSetBlendConstants | ⚙️ WIP
|
||||
vkCmdSetDepthBias | ⚙️ WIP
|
||||
vkCmdSetDepthBounds | ⚙️ WIP
|
||||
vkCmdSetDeviceMaskKHR | ⚙️ WIP
|
||||
vkCmdSetEvent | ⚙️ WIP
|
||||
vkCmdSetLineWidth | ⚙️ WIP
|
||||
vkCmdSetScissor | ⚙️ WIP
|
||||
vkCmdSetStencilCompareMask | ⚙️ WIP
|
||||
vkCmdSetStencilReference | ⚙️ WIP
|
||||
vkCmdSetStencilWriteMask | ⚙️ WIP
|
||||
vkCmdSetViewport | ⚙️ WIP
|
||||
vkCmdUpdateBuffer | ⚙️ WIP
|
||||
vkCmdWaitEvents | ⚙️ WIP
|
||||
vkCmdWriteTimestamp | ⚙️ WIP
|
||||
vkCreateBuffer | âś… Implemented
|
||||
vkCreateBufferView | ⚙️ WIP
|
||||
vkCreateCommandPool | âś… Implemented
|
||||
vkCreateComputePipelines | ⚙️ WIP
|
||||
vkCreateDescriptorPool | ⚙️ WIP
|
||||
vkCreateDescriptorSetLayout | ⚙️ WIP
|
||||
vkCreateDevice | âś… Implemented
|
||||
vkCreateEvent | ⚙️ WIP
|
||||
vkCreateFence | âś… Implemented
|
||||
vkCreateFramebuffer | ⚙️ WIP
|
||||
vkCreateGraphicsPipelines | ⚙️ WIP
|
||||
vkCreateImage | âś… Implemented
|
||||
vkCreateImageView | ⚙️ WIP
|
||||
vkCreateInstance | âś… Implemented
|
||||
vkCreatePipelineCache | ⚙️ WIP
|
||||
vkCreatePipelineLayout | ⚙️ WIP
|
||||
vkCreateQueryPool | ⚙️ WIP
|
||||
vkCreateRenderPass | ⚙️ WIP
|
||||
vkCreateSampler | ⚙️ WIP
|
||||
vkCreateSemaphore | âś… Implemented
|
||||
vkCreateShaderModule | ⚙️ WIP
|
||||
vkCreateSwapchainKHR | ⚙️ WIP
|
||||
vkCreateWaylandSurfaceKHR | ⚙️ WIP
|
||||
vkCreateWin32SurfaceKHR | ⚙️ WIP
|
||||
vkCreateXcbSurfaceKHR | ⚙️ WIP
|
||||
vkCreateXlibSurfaceKHR | ⚙️ WIP
|
||||
vkDestroyBuffer | âś… Implemented
|
||||
vkDestroyBufferView | ⚙️ WIP
|
||||
vkDestroyCommandPool | âś… Implemented
|
||||
vkDestroyDescriptorPool | ⚙️ WIP
|
||||
vkDestroyDescriptorSetLayout | ⚙️ WIP
|
||||
vkDestroyDevice | âś… Implemented
|
||||
vkDestroyEvent | ⚙️ WIP
|
||||
vkDestroyFence | âś… Implemented
|
||||
vkDestroyFramebuffer | ⚙️ WIP
|
||||
vkDestroyImage | âś… Implemented
|
||||
vkDestroyImageView | ⚙️ WIP
|
||||
vkDestroyInstance | âś… Implemented
|
||||
vkDestroyPipeline | ⚙️ WIP
|
||||
vkDestroyPipelineCache | ⚙️ WIP
|
||||
vkDestroyPipelineLayout | ⚙️ WIP
|
||||
vkDestroyQueryPool | ⚙️ WIP
|
||||
vkDestroyRenderPass | ⚙️ WIP
|
||||
vkDestroySampler | ⚙️ WIP
|
||||
vkDestroySemaphore | âś… Implemented
|
||||
vkDestroyShaderModule | ⚙️ WIP
|
||||
vkDestroySurfaceKHR | ⚙️ WIP
|
||||
vkDestroySwapchainKHR | ⚙️ WIP
|
||||
vkDeviceWaitIdle | âś… Implemented
|
||||
vkEndCommandBuffer | âś… Implemented
|
||||
vkEnumerateDeviceExtensionProperties | âś… Implemented
|
||||
vkEnumerateDeviceLayerProperties | ⚙️ WIP
|
||||
vkEnumerateInstanceExtensionProperties | ⚙️ WIP
|
||||
vkEnumerateInstanceLayerProperties | ⚙️ WIP
|
||||
vkEnumeratePhysicalDeviceGroupsKHR | ⚙️ WIP
|
||||
vkEnumeratePhysicalDevices | âś… Implemented
|
||||
vkFlushMappedMemoryRanges | âś… Implemented
|
||||
vkFreeCommandBuffers | âś… Implemented
|
||||
vkFreeDescriptorSets | ⚙️ WIP
|
||||
vkFreeMemory | âś… Implemented
|
||||
vkGetBufferDeviceAddress | ⚙️ WIP
|
||||
vkGetBufferDeviceAddressEXT | ⚙️ WIP
|
||||
vkGetBufferDeviceAddressKHR | ⚙️ WIP
|
||||
vkGetBufferMemoryRequirements | ⚙️ WIP
|
||||
vkGetDeviceGroupPeerMemoryFeaturesKHR | ⚙️ WIP
|
||||
vkGetDeviceGroupPresentCapabilitiesKHR | ⚙️ WIP
|
||||
vkGetDeviceGroupSurfacePresentModesKHR | ⚙️ WIP
|
||||
vkGetDeviceMemoryCommitment | ⚙️ WIP
|
||||
vkGetDeviceProcAddr | âś… Implemented
|
||||
vkGetDeviceQueue | âś… Implemented
|
||||
vkGetEventStatus | ⚙️ WIP
|
||||
vkGetFenceStatus | âś… Implemented
|
||||
vkGetImageMemoryRequirements | ⚙️ WIP
|
||||
vkGetImageSparseMemoryRequirements | ⚙️ WIP
|
||||
vkGetImageSubresourceLayout | ⚙️ WIP
|
||||
vkGetInstanceProcAddr | ⚙️ WIP
|
||||
vkGetPhysicalDeviceFeatures | âś… Implemented
|
||||
vkGetPhysicalDeviceFormatProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceImageFormatProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceMemoryProperties | âś… Implemented
|
||||
vkGetPhysicalDeviceProperties | âś… Implemented
|
||||
vkGetPhysicalDeviceQueueFamilyProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSparseImageFormatProperties | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceWaylandPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP
|
||||
vkGetPipelineCacheData | ⚙️ WIP
|
||||
vkGetQueryPoolResults | ⚙️ WIP
|
||||
vkGetRenderAreaGranularity | ⚙️ WIP
|
||||
vkGetSwapchainImagesKHR | ⚙️ WIP
|
||||
vkInvalidateMappedMemoryRanges | âś… Implemented
|
||||
vkMapMemory | âś… Implemented
|
||||
vkMergePipelineCaches | ⚙️ WIP
|
||||
vkQueueBindSparse | ⚙️ WIP
|
||||
vkQueuePresentKHR | ⚙️ WIP
|
||||
vkQueueSubmit | âś… Implemented
|
||||
vkQueueWaitIdle | âś… Implemented
|
||||
vkResetCommandBuffer | âś… Implemented
|
||||
vkResetCommandPool | âś… Implemented
|
||||
vkResetDescriptorPool | ⚙️ WIP
|
||||
vkResetEvent | ⚙️ WIP
|
||||
vkResetFences | âś… Implemented
|
||||
vkResetQueryPool | ⚙️ WIP
|
||||
vkSetEvent | ⚙️ WIP
|
||||
vkUnmapMemory | âś… Implemented
|
||||
vkUpdateDescriptorSets | ⚙️ WIP
|
||||
vkWaitForFences | âś… Implemented
|
||||
</details>
|
||||
|
||||
[Herein](https://vulkan-driver.kbz8.me/cts/flint/) is the reckoning of the Vulkan 1.0 conformance trials laid bare. Judge it as thou wilt.
|
||||
|
||||
## License
|
||||
|
||||
Released unto the world as MIT for study, experimentation, and the occasional horrified whisper.
|
||||
|
||||
@@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const Step = std.Build.Step;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const driver_version: std.SemanticVersion = .{ .major = 1, .minor = 0, .patch = 0 };
|
||||
const driver_version: std.SemanticVersion = .{ .major = 26, .minor = 0, .patch = 0 };
|
||||
|
||||
const ImplementationDesc = struct {
|
||||
name: []const u8,
|
||||
@@ -45,6 +45,13 @@ const implementations = [_]ImplementationDesc{
|
||||
.custom = customFlint,
|
||||
.options = optionsFlint,
|
||||
},
|
||||
.{
|
||||
.name = "phi",
|
||||
.root_source_file = "src/phi/lib.zig",
|
||||
.vulkan_version = .{ .major = 1, .minor = 0, .patch = 0 },
|
||||
.custom = customPhi,
|
||||
.options = optionsPhi,
|
||||
},
|
||||
};
|
||||
|
||||
const RunningMode = enum {
|
||||
@@ -64,6 +71,37 @@ pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const ir_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/compiler/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const ir_tests = b.addTest(.{
|
||||
.root_module = ir_mod,
|
||||
.test_runner = .{
|
||||
.path = b.path("test/test_runner.zig"),
|
||||
.mode = .simple,
|
||||
},
|
||||
});
|
||||
const run_ir_tests = b.addRunArtifact(ir_tests);
|
||||
const ir_test_step = b.step("test-ir", "Run shared shader ir tests");
|
||||
ir_test_step.dependOn(&run_ir_tests.step);
|
||||
|
||||
const ir_autodoc_test = b.addObject(.{
|
||||
.name = "lib",
|
||||
.root_module = ir_mod,
|
||||
});
|
||||
|
||||
const ir_install_docs = b.addInstallDirectory(.{
|
||||
.source_dir = ir_autodoc_test.getEmittedDocs(),
|
||||
.install_dir = .prefix,
|
||||
.install_subdir = "docs-ir",
|
||||
});
|
||||
|
||||
const ir_docs_step = b.step("docs-ir", "Build and install the documentation or shader IR");
|
||||
ir_docs_step.dependOn(&ir_install_docs.step);
|
||||
|
||||
const base_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/vulkan/lib.zig"),
|
||||
.target = target,
|
||||
@@ -81,10 +119,12 @@ pub fn build(b: *std.Build) !void {
|
||||
const drm = b.dependency("drm", .{}).module("drm");
|
||||
|
||||
const logs_option: LogType = b.option(LogType, "logs", "Driver logs") orelse .none;
|
||||
const debug_allocator_option = b.option(bool, "device-debug-allocator", "Debug device allocator") orelse false;
|
||||
|
||||
const options = b.addOptions();
|
||||
options.addOption(std.SemanticVersion, "driver_version", driver_version);
|
||||
options.addOption(LogType, "logs", logs_option);
|
||||
options.addOption(bool, "device_debug_allocator", debug_allocator_option);
|
||||
|
||||
base_mod.addImport("vulkan", vulkan);
|
||||
base_mod.addImport("zmath", zmath);
|
||||
@@ -166,7 +206,13 @@ pub fn build(b: *std.Build) !void {
|
||||
const install_step = b.step(impl.name, b.fmt("Build libvulkan_{s}", .{impl.name}));
|
||||
install_step.dependOn(&lib_install.step);
|
||||
|
||||
const lib_tests = b.addTest(.{ .root_module = lib_mod });
|
||||
const lib_tests = b.addTest(.{
|
||||
.root_module = lib_mod,
|
||||
.test_runner = .{
|
||||
.path = b.path("test/test_runner.zig"),
|
||||
.mode = .simple,
|
||||
},
|
||||
});
|
||||
|
||||
const run_tests = b.addRunArtifact(lib_tests);
|
||||
const test_step = b.step(b.fmt("test-{s}", .{impl.name}), b.fmt("Run libvulkan_{s} tests", .{impl.name}));
|
||||
@@ -209,97 +255,6 @@ pub fn build(b: *std.Build) !void {
|
||||
docs_step.dependOn(&install_docs.step);
|
||||
}
|
||||
|
||||
fn customApe(
|
||||
b: *std.Build,
|
||||
lib: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
base_mod: *std.Build.Module,
|
||||
vulkan: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
use_llvm: bool,
|
||||
) !void {
|
||||
for (implementations) |impl| {
|
||||
if (std.mem.eql(u8, impl.name, "ape"))
|
||||
continue;
|
||||
|
||||
const mod = b.createModule(.{
|
||||
.root_source_file = b.path(impl.root_source_file),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "base", .module = base_mod },
|
||||
.{ .name = "vulkan", .module = vulkan },
|
||||
},
|
||||
});
|
||||
|
||||
if (impl.custom) |func| {
|
||||
func(b, lib, mod, base_mod, vulkan, base_c_mod, target, optimize, use_llvm) catch continue;
|
||||
}
|
||||
|
||||
lib_mod.addImport(impl.name, mod);
|
||||
}
|
||||
}
|
||||
|
||||
fn customSoft(
|
||||
b: *std.Build,
|
||||
_: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
_: std.Build.ResolvedTarget,
|
||||
_: std.builtin.OptimizeMode,
|
||||
use_llvm: bool,
|
||||
) !void {
|
||||
const spv = b.lazyDependency("SPIRV_Interpreter", .{
|
||||
.@"no-example" = true,
|
||||
.@"no-test" = true,
|
||||
.@"use-llvm" = use_llvm,
|
||||
}) orelse return error.UnresolvedDependency;
|
||||
|
||||
lib_mod.addImport("soft_c", base_c_mod);
|
||||
lib_mod.addImport("spv", spv.module("spv"));
|
||||
}
|
||||
|
||||
fn optionsSoft(b: *std.Build, options: *Step.Options) !void {
|
||||
const single_threaded_option = b.option(bool, "soft-single-threaded", "Single threaded runtime mode") orelse false;
|
||||
const debug_allocator_option = b.option(bool, "soft-debug-allocator", "Debug device allocator") orelse false;
|
||||
const shaders_simd_option = b.option(bool, "soft-shader-simd", "Shaders SIMD acceleration") orelse true;
|
||||
const compute_dump_early_results_table_option = b.option(u32, "soft-compute-dump-early-results-table", "Dump compute shaders results table before invocation");
|
||||
const compute_dump_final_results_table_option = b.option(u32, "soft-compute-dump-final-results-table", "Dump compute shaders results table after invocation");
|
||||
const approxiamte_rgb_option = b.option(bool, "soft-approximates-rgb", "Approximate sRGB <-> RGB conversions") orelse true;
|
||||
|
||||
options.addOption(bool, "soft_single_threaded", single_threaded_option);
|
||||
options.addOption(bool, "soft_debug_allocator", debug_allocator_option);
|
||||
options.addOption(bool, "soft_shaders_simd", shaders_simd_option);
|
||||
options.addOption(?u32, "soft_compute_dump_early_results_table", compute_dump_early_results_table_option);
|
||||
options.addOption(?u32, "soft_compute_dump_final_results_table", compute_dump_final_results_table_option);
|
||||
options.addOption(bool, "soft_approximates_rgb", approxiamte_rgb_option);
|
||||
}
|
||||
|
||||
fn customFlint(
|
||||
_: *std.Build,
|
||||
_: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
_: std.Build.ResolvedTarget,
|
||||
_: std.builtin.OptimizeMode,
|
||||
_: bool,
|
||||
) !void {
|
||||
lib_mod.addImport("intel_c", base_c_mod);
|
||||
|
||||
lib_mod.addImport("soft_c", base_c_mod);
|
||||
}
|
||||
|
||||
fn optionsFlint(b: *std.Build, options: *Step.Options) !void {
|
||||
_ = b;
|
||||
_ = options;
|
||||
}
|
||||
|
||||
fn addCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *const ImplementationDesc, impl_lib: *Step.Compile, comptime mode: RunningMode) !*Step {
|
||||
const cts = b.dependency("cts_bin", .{});
|
||||
|
||||
@@ -432,7 +387,8 @@ fn addMultithreadedCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *c
|
||||
}
|
||||
|
||||
run.addArg("run");
|
||||
run.addArg("--verbose");
|
||||
run.addArg("--timeout");
|
||||
run.addArg("60");
|
||||
run.addArg("--deqp");
|
||||
run.addArg(cts_exe_path);
|
||||
run.addArg("--caselist");
|
||||
@@ -469,3 +425,254 @@ fn addMultithreadedCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *c
|
||||
|
||||
return &run.step;
|
||||
}
|
||||
|
||||
// Ape specialized functions
|
||||
|
||||
fn customApe(
|
||||
b: *std.Build,
|
||||
lib: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
base_mod: *std.Build.Module,
|
||||
vulkan: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
use_llvm: bool,
|
||||
) !void {
|
||||
for (implementations) |impl| {
|
||||
if (std.mem.eql(u8, impl.name, "ape"))
|
||||
continue;
|
||||
|
||||
const mod = b.createModule(.{
|
||||
.root_source_file = b.path(impl.root_source_file),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "base", .module = base_mod },
|
||||
.{ .name = "vulkan", .module = vulkan },
|
||||
},
|
||||
});
|
||||
|
||||
if (impl.custom) |func| {
|
||||
func(b, lib, mod, base_mod, vulkan, base_c_mod, target, optimize, use_llvm) catch continue;
|
||||
}
|
||||
|
||||
lib_mod.addImport(impl.name, mod);
|
||||
}
|
||||
}
|
||||
|
||||
// Soft specialized functions
|
||||
|
||||
fn customSoft(
|
||||
b: *std.Build,
|
||||
_: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
use_llvm: bool,
|
||||
) !void {
|
||||
const spv = b.lazyDependency("SPIRV_Interpreter", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.@"use-llvm" = use_llvm,
|
||||
}) orelse return error.UnresolvedDependency;
|
||||
|
||||
lib_mod.addImport("soft_c", base_c_mod);
|
||||
lib_mod.addImport("spv", spv.module("spv"));
|
||||
}
|
||||
|
||||
fn optionsSoft(b: *std.Build, options: *Step.Options) !void {
|
||||
const single_threaded_option = b.option(bool, "soft-single-threaded", "Single threaded runtime mode") orelse false;
|
||||
const shaders_simd_option = b.option(bool, "soft-shader-simd", "Shaders SIMD acceleration") orelse true;
|
||||
const compute_dump_early_results_table_option = b.option(u32, "soft-compute-dump-early-results-table", "Dump compute shaders results table before invocation");
|
||||
const compute_dump_final_results_table_option = b.option(u32, "soft-compute-dump-final-results-table", "Dump compute shaders results table after invocation");
|
||||
const approxiamte_rgb_option = b.option(bool, "soft-approximates-rgb", "Approximate sRGB <-> RGB conversions") orelse true;
|
||||
|
||||
options.addOption(bool, "soft_single_threaded", single_threaded_option);
|
||||
options.addOption(bool, "soft_shaders_simd", shaders_simd_option);
|
||||
options.addOption(?u32, "soft_compute_dump_early_results_table", compute_dump_early_results_table_option);
|
||||
options.addOption(?u32, "soft_compute_dump_final_results_table", compute_dump_final_results_table_option);
|
||||
options.addOption(bool, "soft_approximates_rgb", approxiamte_rgb_option);
|
||||
}
|
||||
|
||||
// Flint specialized functions
|
||||
|
||||
fn customFlint(
|
||||
b: *std.Build,
|
||||
_: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
_: bool,
|
||||
) !void {
|
||||
lib_mod.addImport("intel_c", base_c_mod);
|
||||
lib_mod.addImport("shader_compiler", b.createModule(.{
|
||||
.root_source_file = b.path("src/compiler/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}));
|
||||
}
|
||||
|
||||
fn optionsFlint(b: *std.Build, options: *Step.Options) !void {
|
||||
_ = b;
|
||||
_ = options;
|
||||
}
|
||||
|
||||
// Phi specialized functions
|
||||
|
||||
fn customPhi(
|
||||
b: *std.Build,
|
||||
lib: *Step.Compile,
|
||||
lib_mod: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
_: *std.Build.Module,
|
||||
base_c_mod: *std.Build.Module,
|
||||
target: std.Build.ResolvedTarget,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
use_llvm: bool,
|
||||
) !void {
|
||||
lib_mod.addImport("phi_c", base_c_mod);
|
||||
|
||||
const miclib = b.lazyDependency("miclib", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.@"use-llvm" = use_llvm,
|
||||
}) orelse return error.UnresolvedDependency;
|
||||
|
||||
lib_mod.addImport("miclib", miclib.module("miclib"));
|
||||
|
||||
const phi_protocol_c = b.addTranslateC(.{
|
||||
.root_source_file = b.path("src/phi/shared/Protocol.h"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = false,
|
||||
});
|
||||
|
||||
lib_mod.addImport("phi_protocol_c", phi_protocol_c.createModule());
|
||||
|
||||
// To avoid duplicated options due to Ape's custom function
|
||||
if (!std.mem.eql(u8, lib.name, "vulkan_phi")) {
|
||||
const daemon = try addPhiCardDaemon(b, optimize, "k1om-mpss-linux-gcc", null);
|
||||
const embedded_daemon = addEmbeddedPhiDaemon(b, daemon);
|
||||
lib_mod.addAnonymousImport("phi_daemon", .{
|
||||
.root_source_file = embedded_daemon,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const build_card = b.option(
|
||||
bool,
|
||||
"phi-build-daemon",
|
||||
"Build Xeon Phi card daemon",
|
||||
) orelse true;
|
||||
|
||||
if (!build_card)
|
||||
return;
|
||||
|
||||
const cc = b.option(
|
||||
[]const u8,
|
||||
"phi-card-cc",
|
||||
"Path to k1om-mpss-linux-gcc",
|
||||
) orelse "k1om-mpss-linux-gcc";
|
||||
|
||||
const sysroot = b.option(
|
||||
[]const u8,
|
||||
"phi-card-sysroot",
|
||||
"MPSS sysroot path",
|
||||
);
|
||||
|
||||
const daemon = try addPhiCardDaemon(b, optimize, cc, sysroot);
|
||||
const install_daemon = b.addInstallFile(daemon, "lib/phi_device.mic");
|
||||
lib.step.dependOn(&install_daemon.step);
|
||||
|
||||
const embedded_daemon = addEmbeddedPhiDaemon(b, daemon);
|
||||
lib_mod.addAnonymousImport("phi_daemon", .{
|
||||
.root_source_file = embedded_daemon,
|
||||
});
|
||||
}
|
||||
|
||||
fn optionsPhi(b: *std.Build, options: *Step.Options) !void {
|
||||
const daemon_remote_path = b.option(
|
||||
[]const u8,
|
||||
"phi-daemon-remote-path",
|
||||
"Path where the Xeon Phi daemon is copied on the card",
|
||||
) orelse "/tmp/phi_device.mic";
|
||||
|
||||
const daemon_host_prefix = b.option(
|
||||
[]const u8,
|
||||
"phi-daemon-host-prefix",
|
||||
"Host prefix used to reach cards over ssh/scp; card N uses <prefix>N",
|
||||
) orelse "mic";
|
||||
|
||||
options.addOption([]const u8, "phi_daemon_remote_path", daemon_remote_path);
|
||||
options.addOption([]const u8, "phi_daemon_host_prefix", daemon_host_prefix);
|
||||
}
|
||||
|
||||
fn addPhiCardDaemon(
|
||||
b: *std.Build,
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
cc: []const u8,
|
||||
sysroot: ?[]const u8,
|
||||
) !std.Build.LazyPath {
|
||||
const cmd = b.addSystemCommand(&.{cc});
|
||||
|
||||
cmd.addArgs(&.{
|
||||
"-std=c11",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Wno-unused-parameter",
|
||||
"-pthread",
|
||||
});
|
||||
|
||||
cmd.addArg("-I");
|
||||
cmd.addDirectoryArg(b.path("src/phi/mic"));
|
||||
cmd.addArg("-I");
|
||||
cmd.addDirectoryArg(b.path("src/phi/shared"));
|
||||
|
||||
if (sysroot) |path| {
|
||||
cmd.addArg("--sysroot");
|
||||
cmd.addArg(path);
|
||||
}
|
||||
|
||||
switch (optimize) {
|
||||
.Debug => cmd.addArgs(&.{ "-O0", "-g3" }),
|
||||
.ReleaseSafe => cmd.addArgs(&.{ "-O2", "-g", "-DNDEBUG" }),
|
||||
.ReleaseFast => cmd.addArgs(&.{ "-O3", "-DNDEBUG" }),
|
||||
.ReleaseSmall => cmd.addArgs(&.{ "-Os", "-DNDEBUG" }),
|
||||
}
|
||||
|
||||
const sources = [_][]const u8{
|
||||
"src/phi/mic/main.c",
|
||||
"src/phi/mic/Buffer.c",
|
||||
"src/phi/mic/CommandBuffer.c",
|
||||
"src/phi/mic/Daemon.c",
|
||||
"src/phi/mic/Logger.c",
|
||||
"src/phi/mic/Memory.c",
|
||||
// Add new files here
|
||||
};
|
||||
|
||||
for (sources) |source| {
|
||||
cmd.addFileArg(b.path(source));
|
||||
}
|
||||
|
||||
cmd.addArgs(&.{
|
||||
"-lscif",
|
||||
"-o",
|
||||
});
|
||||
|
||||
return cmd.addOutputFileArg("phi_device.mic");
|
||||
}
|
||||
|
||||
fn addEmbeddedPhiDaemon(b: *std.Build, daemon: std.Build.LazyPath) std.Build.LazyPath {
|
||||
const wf = b.addWriteFiles();
|
||||
_ = wf.addCopyFile(daemon, "phi_device.mic");
|
||||
return wf.add("phi_daemon.zig",
|
||||
\\pub const data = @embedFile("phi_device.mic");
|
||||
);
|
||||
}
|
||||
|
||||
+12
-4
@@ -1,6 +1,7 @@
|
||||
.{
|
||||
.name = .VulkanDriver,
|
||||
.version = "0.0.1",
|
||||
.version = "2026.0.0",
|
||||
|
||||
.fingerprint = 0x52cb73649f1107de,
|
||||
.minimum_zig_version = "0.16.0",
|
||||
|
||||
@@ -26,14 +27,14 @@
|
||||
.hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59",
|
||||
},
|
||||
.drm = .{
|
||||
.url = "git+https://git.kbz8.me/kbz_8/zig-drm.git#409f58daa8f5174b2fcb8897f1c30f0b0729b611",
|
||||
.url = "git+https://github.com/Kbz-8/zig-drm#409f58daa8f5174b2fcb8897f1c30f0b0729b611",
|
||||
.hash = "drm-0.0.1-uWWar5YwAQCiTNOcqtYqt_yL19B0_Q-YLjPNkRysFSnR",
|
||||
},
|
||||
|
||||
// Soft dependencies
|
||||
.SPIRV_Interpreter = .{
|
||||
.url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#f82e3e1629af4dc751a80a3a4b04ff1b43725c24",
|
||||
.hash = "SPIRV_Interpreter-0.0.1-ajmpn6mrBwDl75NH4NndKIOjnn-1ooUY-0NRyWR2eW-P",
|
||||
.url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#92e86ba34fccf32a30e6f717a9cafe0b9e95bb2a",
|
||||
.hash = "SPIRV_Interpreter-1.0.0-ajmpnwSKCQDjTAGgiw6N0rWdZiD3VdEMAE352RSpN4vl",
|
||||
.lazy = true,
|
||||
},
|
||||
//.SPIRV_Interpreter = .{
|
||||
@@ -41,6 +42,13 @@
|
||||
// .path = "../SPIRV-Interpreter",
|
||||
// .lazy = true,
|
||||
//},
|
||||
|
||||
// Phi dependencies
|
||||
.miclib = .{
|
||||
.url = "git+https://git.kbz8.me/kbz_8/miclib-zig.git#f46bd2a058db7acbc9012a657f3fbe79d49c017f",
|
||||
.hash = "miclib-1.0.0-AbDwSzB6AgDVMQ3muWEwaBdfYJBDgm3pJKNf2EEUnmNp",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
||||
.paths = .{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-xc
|
||||
-std=c11
|
||||
-Isrc/phi/shared
|
||||
-Isrc/phi/mic
|
||||
-isystem/opt/mpss/3.8.6/sysroots/k1om-mpss-linux/usr/include/
|
||||
+10
-6
@@ -4,8 +4,8 @@ const base = @import("base");
|
||||
|
||||
const soft = @import("soft");
|
||||
const flint = @import("flint");
|
||||
const phi = @import("phi");
|
||||
|
||||
const Dispatchable = base.Dispatchable;
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
@@ -14,7 +14,7 @@ pub const Interface = base.Instance;
|
||||
interface: Interface,
|
||||
backend_instances: std.ArrayList(*Interface),
|
||||
|
||||
pub const EXTENSIONS = soft.Instance.EXTENSIONS;
|
||||
pub const extensions = soft.Instance.extensions;
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
@@ -36,13 +36,17 @@ pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo)
|
||||
};
|
||||
|
||||
const soft_instance = try soft.Instance.create(allocator, infos);
|
||||
errdefer soft_instance.deinit(allocator) catch {};
|
||||
errdefer soft_instance.deinit(allocator) catch @panic("Caught an error while handling an error");
|
||||
self.backend_instances.append(allocator, soft_instance) catch return VkError.OutOfHostMemory;
|
||||
|
||||
const flint_instance = try flint.Instance.create(allocator, infos);
|
||||
errdefer flint_instance.deinit(allocator) catch {};
|
||||
errdefer flint_instance.deinit(allocator) catch @panic("Caught an error while handling an error");
|
||||
self.backend_instances.append(allocator, flint_instance) catch return VkError.OutOfHostMemory;
|
||||
|
||||
const phi_instance = try phi.Instance.create(allocator, infos);
|
||||
errdefer phi_instance.deinit(allocator) catch @panic("Caught an error while handling an error");
|
||||
self.backend_instances.append(allocator, phi_instance) catch return VkError.OutOfHostMemory;
|
||||
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
@@ -57,13 +61,13 @@ fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator, _
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
for (self.backend_instances.items) |backend| {
|
||||
try appendBackendPhysicalDevices(self, allocator, backend);
|
||||
appendBackendPhysicalDevices(self, allocator, backend) catch continue;
|
||||
}
|
||||
}
|
||||
|
||||
fn appendBackendPhysicalDevices(self: *Self, allocator: std.mem.Allocator, backend: *Interface) VkError!void {
|
||||
try backend.requestPhysicalDevices(allocator);
|
||||
errdefer backend.releasePhysicalDevices(allocator) catch {};
|
||||
errdefer backend.releasePhysicalDevices(allocator) catch @panic("Caught an error while handling an error");
|
||||
|
||||
self.interface.physical_devices.appendSlice(allocator, backend.physical_devices.items) catch return VkError.OutOfHostMemory;
|
||||
backend.physical_devices.deinit(allocator);
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@ pub const ApeInstance = @import("ApeInstance.zig");
|
||||
|
||||
pub const Instance = ApeInstance;
|
||||
|
||||
pub const DRIVER_NAME = "Ape";
|
||||
pub const driver_name = "Ape";
|
||||
|
||||
pub const VULKAN_VERSION = vk.makeApiVersion(
|
||||
pub const vulkan_version = vk.makeApiVersion(
|
||||
0,
|
||||
config.ape_vulkan_version.major,
|
||||
config.ape_vulkan_version.minor,
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
# Backend-Agnostic Shader IR
|
||||
|
||||
> Note: this IR is still foundational and incomplete. Its representation may
|
||||
> change as the compiler gains features. Backends should not treat it as a
|
||||
> stable ABI yet.
|
||||
|
||||
This directory contains the backend-agnostic shader intermediate representation.
|
||||
It sits between SPIR-V and the backends that consume it.
|
||||
Format-specific details are removed while types, values, control flow,
|
||||
interfaces, and semantic operations remain.
|
||||
|
||||
## Overview
|
||||
|
||||
- One `Module` describes one selected shader entry point.
|
||||
- The current stages are `vertex`, `fragment`, and `compute`.
|
||||
- Values are typed and use static single assignment (SSA).
|
||||
- Blocks act as control-flow graph labels, but also own instructions,
|
||||
parameters, a terminator, and structured-control metadata.
|
||||
- Block parameters serve the same purpose as `OpPhi` in SPIR-V.
|
||||
- Instructions speak normalized meanings, not source-format opcodes.
|
||||
- Types and constants are interned. Equal things share one identity.
|
||||
- IDs are stable. Erasure leaves a tombstone; no dead ID is reused for another object.
|
||||
- The printer is for debugging and tests. Its output can be parsed back into a
|
||||
validated module, but it is not yet a stable interchange format.
|
||||
|
||||
## Printer syntax
|
||||
|
||||
The printer uses these prefixes:
|
||||
|
||||
| Prefix | Meaning | Example |
|
||||
| ------- | ---------------------------------------------------------------- | --------------------- |
|
||||
| `%id` | An SSA value, whether constant, parameter, or instruction result | `%3`, `%merged_value` |
|
||||
| `@name` | A function or interface declaration | `@main`, `@out_color` |
|
||||
| `.name` | A basic block | `.entry`, `.merge` |
|
||||
| `#N` | A constant-store identity used within composite constants | `#2` |
|
||||
|
||||
Names are annotations rather than identity. When a name is absent, invalid for
|
||||
the textual grammar, or duplicated, the printer uses a numeric `%N` value
|
||||
reference. Other unnamed objects use forms such as `@fn0`, `@interface1`, and
|
||||
`.b2`. The parser accepts both numeric and identifier-shaped value references.
|
||||
|
||||
An instruction that produces a value prints its result type explicitly:
|
||||
|
||||
```text
|
||||
%result: <type> = <opcode> <operands>
|
||||
```
|
||||
|
||||
The type annotation makes operations such as `bitcast` and heterogeneous
|
||||
`composite_construct` unambiguous when the text is parsed. Constants, function
|
||||
parameters, and block parameters already carry their types in their own forms.
|
||||
|
||||
The outer structure has this shape:
|
||||
|
||||
```text
|
||||
shader <stage> @<entry-point>
|
||||
{
|
||||
<interface declarations>
|
||||
<constant declarations>
|
||||
|
||||
fn @<name>(<parameters>) -> <type>
|
||||
{
|
||||
.<block>(<block parameters>):
|
||||
<instructions>
|
||||
<terminator>
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Execution modes, resources, source locations, and structured-control metadata
|
||||
exist in memory, but the printer does not display them yet.
|
||||
|
||||
## Parsing
|
||||
|
||||
`ir.parser` accepts the complete syntax emitted by the printer:
|
||||
|
||||
```zig
|
||||
var from_string = try ir.parser.parseString(allocator, source);
|
||||
defer from_string.deinit();
|
||||
|
||||
var from_file = try ir.parser.parseFile(allocator, io, "shader.ir");
|
||||
defer from_file.deinit();
|
||||
```
|
||||
|
||||
Use `parseFileInDir` when the path is relative to an existing `std.Io.Dir`.
|
||||
Each parser entry point owns the returned module with the supplied allocator and
|
||||
runs the IR validator before returning it. Parse, reference-resolution, or
|
||||
validation failures are returned as errors. Because the printer omits the
|
||||
metadata listed above, a print/parse round trip preserves the displayed IR but
|
||||
cannot recover those hidden fields.
|
||||
|
||||
## Types
|
||||
|
||||
Types are interned in the module and printed inline; their `TypeId` is hidden.
|
||||
|
||||
| Kind | Printed form | Meaning |
|
||||
| ---------------- | ------------------------------- | ------------------------------------------------------------------------- |
|
||||
| Void | `void` | No value. Used mainly for functions that return nothing. |
|
||||
| Boolean | `bool` | A truth value. |
|
||||
| Signed integer | `i32` | A signed integer of the written bit width. |
|
||||
| Unsigned integer | `u32` | An unsigned integer of the written bit width. |
|
||||
| Floating point | `f32` | A floating value of the written bit width. |
|
||||
| Vector | `vec4[f32]` | A fixed number of equal scalar elements. Its length must be at least two. |
|
||||
| Array | `array[u32, 8]` | A fixed number of equal elements. Its length must not be zero. |
|
||||
| Structure | `struct[f32, vec4[f32]]` | An ordered sequence of potentially different member types. |
|
||||
| Pointer | `ptr[workgroup, u32]` | A pointer to a type within an address space. |
|
||||
| Resource handle | `resourceHandle[sampled_image]` | An opaque handle for later resource operations. |
|
||||
|
||||
The current address spaces are `function`, `private`, `workgroup`,
|
||||
`input`, `output`, `uniform`, `storage`, `push_constant`, and `physical`.
|
||||
|
||||
The current resource kinds are `uniform_buffer`, `storage_buffer`,
|
||||
`sampled_image`, `storage_image`, and `sampler`. A resource handle may also
|
||||
carry an optional data type in memory, although the printer omits that type.
|
||||
|
||||
## Constants
|
||||
|
||||
Constants live at module scope and also have ordinary numeric or named `%id` value identities.
|
||||
The parser accepts direct decimal integer and floating-point values, including
|
||||
signed values and floating-point exponents, as source-level convenience syntax:
|
||||
|
||||
```text
|
||||
%0: constant u32 = 42
|
||||
%1: constant i32 = -7
|
||||
%2: constant f32 = 1.5e2
|
||||
```
|
||||
|
||||
Direct integers must fit their declared width and signedness. Direct floats are
|
||||
rounded to their declared `f16`, `f32`, or `f64` representation. The canonical
|
||||
printer always emits integer and float bit patterns so reparsing cannot silently
|
||||
change the stored value.
|
||||
|
||||
```text
|
||||
%id: constant <type> = <value>
|
||||
```
|
||||
|
||||
| Form | Meaning | Printed example |
|
||||
| ------------ | ----------------------------------- | --------------------------------------- |
|
||||
| Boolean | `true` or `false` | `%0: constant bool = true` |
|
||||
| Integer bits | The fixed-width integer bit pattern | `%1: constant u32 = bits(0x2a)` |
|
||||
| Float bits | The IEEE-like bit pattern as stored | `%2: constant f32 = bits(0x3f800000)` |
|
||||
| Null | The null value of its type | `%3: constant ptr[private, u32] = null` |
|
||||
| Undef | An unconstrained value | `%4: constant u32 = undef` |
|
||||
| Composite | A sequence of other constants | `%5: constant vec2[u32] = [#1, #1]` |
|
||||
|
||||
## Functions, blocks, and SSA
|
||||
|
||||
A function owns typed parameters, an ordered list of blocks, one entry block,
|
||||
and one return type. The first block created by the builder becomes the entry
|
||||
block. The validator requires the entry block to have no predecessor.
|
||||
|
||||
Every block must end in exactly one terminator. A value defined by an instruction
|
||||
must dominate every use, and within one block it must be written before it is
|
||||
used. Constants and standalone undef values are module-wide; function and block
|
||||
parameters cannot be used by another function.
|
||||
|
||||
### Block parameters and Phi lowering
|
||||
|
||||
A merge block does not contain a `phi` instruction. Instead, it declares a
|
||||
parameter, and every incoming edge passes one argument of the same type:
|
||||
|
||||
```text
|
||||
.left():
|
||||
branch .merge(%3)
|
||||
|
||||
.right():
|
||||
branch .merge(%4)
|
||||
|
||||
.merge(%5: u32):
|
||||
%6: u32 = integer_multiply %5, %2
|
||||
return
|
||||
```
|
||||
|
||||
`%5` therefore receives the value supplied by the selected edge. The number and
|
||||
types of edge arguments must exactly match the target block's parameters.
|
||||
|
||||
### Structured control
|
||||
|
||||
A block may carry one of these unprinted metadata values:
|
||||
|
||||
- `none`: no structured-control promise.
|
||||
- `selection`: names one merge block.
|
||||
- `loop`: names both merge and continue blocks.
|
||||
|
||||
The SPIR-V translator preserves `OpSelectionMerge` and `OpLoopMerge` in this
|
||||
metadata. They are not terminators and do not create graph edges themselves.
|
||||
|
||||
## Common instruction rules
|
||||
|
||||
An instruction belongs to one block, has zero or one result, and may carry a
|
||||
source location. Except for `store_interface` and `call`, current operations are
|
||||
treated as side-effect free by the rewriter. A block's terminator is stored
|
||||
separately from its ordinary instructions.
|
||||
|
||||
Most arithmetic operations are intended for scalars or vectors of their named
|
||||
category and act component by component where vectors are allowed. The current
|
||||
foundational validator often checks only that operand and result types match.
|
||||
The stricter integer, float, boolean, bit-width, and vector-shape requirements
|
||||
below describe semantic intent and still need more complete validation.
|
||||
|
||||
## Unary opcodes
|
||||
|
||||
Form:
|
||||
|
||||
```text
|
||||
%result: <type> = <opcode> %operand
|
||||
```
|
||||
|
||||
| Opcode | Arity | Description | Usage | Small printed example |
|
||||
| ------------- | ----: | ---------------------------- | ----------------------------------------------------------------- | --------------------------- |
|
||||
| `negate` | 1 | Changes the arithmetic sign. | Signed integer or floating operand; the result has the same type. | `%2: i32 = negate %1` |
|
||||
| `logical_not` | 1 | Inverts a boolean value. | Boolean operand and boolean result. | `%2: bool = logical_not %1` |
|
||||
| `bitwise_not` | 1 | Inverts every bit. | Integer operand; the result has the same type. | `%2: u32 = bitwise_not %1` |
|
||||
|
||||
`negate` is one normalized opcode: the operand type distinguishes integer
|
||||
negation from floating negation.
|
||||
|
||||
## Binary opcodes
|
||||
|
||||
Form:
|
||||
|
||||
```text
|
||||
%result: <type> = <opcode> %lhs, %rhs
|
||||
```
|
||||
|
||||
The two operands and result currently must have the same IR type.
|
||||
|
||||
### Integer arithmetic
|
||||
|
||||
| Opcode | Description | Usage | Small printed example |
|
||||
| ------------------ | ------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `integer_add` | Adds fixed-width integers. | Integer operands of one type. | `%3: u32 = integer_add %1, %2` |
|
||||
| `integer_subtract` | Subtracts the right operand from the left. | Integer operands of one type. | `%3: u32 = integer_subtract %1, %2` |
|
||||
| `integer_multiply` | Multiplies fixed-width integers. | Integer operands of one type. | `%3: u32 = integer_multiply %1, %2` |
|
||||
| `unsigned_divide` | Divides unsigned integers. | Unsigned integer operands. | `%3: u32 = unsigned_divide %1, %2` |
|
||||
| `signed_divide` | Divides signed integers. | Signed integer operands. | `%3: i32 = signed_divide %1, %2` |
|
||||
| `unsigned_modulo` | Produces the unsigned remainder. | Unsigned integer operands. | `%3: u32 = unsigned_modulo %1, %2` |
|
||||
| `signed_modulo` | Produces signed modulo, whose sign follows the divisor. | Signed integer operands; this corresponds to SPIR-V `OpSMod`, not `OpSRem`. | `%3: i32 = signed_modulo %1, %2` |
|
||||
|
||||
Integer addition, subtraction, and multiplication are signedness-neutral at the
|
||||
opcode level; the type retains signedness. Exceptional division, overflow,
|
||||
and poison rules are not yet separately recorded by the IR.
|
||||
|
||||
### Floating arithmetic
|
||||
|
||||
| Opcode | Description | Usage | Small printed example |
|
||||
| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------------- | --------------------------------- |
|
||||
| `float_add` | Adds floating-point values. | Floating operands of one type. | `%3: f32 = float_add %1, %2` |
|
||||
| `float_subtract` | Subtracts the right operand from the left. | Floating operands of one type. | `%3: f32 = float_subtract %1, %2` |
|
||||
| `float_multiply` | Multiplies floating-point values. | Floating operands of one type. | `%3: f32 = float_multiply %1, %2` |
|
||||
| `float_divide` | Divides the left operand by the right. | Floating operands of one type. | `%3: f32 = float_divide %1, %2` |
|
||||
| `float_modulo` | Produces floating modulo, similar to `x - y * floor(x / y)`. | Floating operands of one type; this corresponds to SPIR-V `OpFMod`. | `%3: f32 = float_modulo %1, %2` |
|
||||
|
||||
Instructions do not yet carry fast-math flags, rounding modes, contraction
|
||||
permission, or NaN guarantees.
|
||||
|
||||
### Shifts and bitwise arithmetic
|
||||
|
||||
| Opcode | Description | Usage | Small printed example |
|
||||
| ------------------------ | -------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------- |
|
||||
| `shift_left` | Shifts bits left and fills the low bits with zero. | Integer operands; the right operand supplies the shift count. | `%3: u32 = shift_left %1, %2` |
|
||||
| `logical_shift_right` | Shifts right and fills high bits with zero. | Integer operands interpreted without sign extension. | `%3: u32 = logical_shift_right %1, %2` |
|
||||
| `arithmetic_shift_right` | Shifts right while repeating the sign bit. | Signed integer value and integer shift count. | `%3: i32 = arithmetic_shift_right %1, %2` |
|
||||
| `bitwise_and` | Keeps bits set in both operands. | Integer operands of one type. | `%3: u32 = bitwise_and %1, %2` |
|
||||
| `bitwise_or` | Keeps bits set in either operand. | Integer operands of one type. | `%3: u32 = bitwise_or %1, %2` |
|
||||
| `bitwise_xor` | Keeps bits set in exactly one operand. | Integer operands of one type. | `%3: u32 = bitwise_xor %1, %2` |
|
||||
|
||||
The validator currently requires the shift count to have the same complete IR
|
||||
type as the shifted value. More flexible shift typing is not implemented yet.
|
||||
|
||||
### Boolean conjunction
|
||||
|
||||
| Opcode | Description | Usage | Small printed example |
|
||||
| ------------- | ----------------------------------------- | ---------------------------- | ------------------------------- |
|
||||
| `logical_and` | Is true only when both operands are true. | Boolean operands and result. | `%3: bool = logical_and %1, %2` |
|
||||
| `logical_or` | Is true when either operand is true. | Boolean operands and result. | `%3: bool = logical_or %1, %2` |
|
||||
|
||||
## Comparison opcodes
|
||||
|
||||
All comparisons are printed with the `cmp_` prefix as one opcode token:
|
||||
|
||||
```text
|
||||
%result: bool = cmp_<opcode> %lhs, %rhs
|
||||
```
|
||||
|
||||
The operands must share one type, and the current validator requires the result
|
||||
to be the scalar `bool` type.
|
||||
|
||||
| Opcode | Description | Usage | Small printed example |
|
||||
| ------------------------------- | ------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------- |
|
||||
| `cmp_equal` | Tests whether two booleans or integers are equal. | Equal-typed boolean or integer operands. | `%3: bool = cmp_equal %1, %2` |
|
||||
| `cmp_not_equal` | Tests whether two booleans or integers differ. | Equal-typed boolean or integer operands. | `%3: bool = cmp_not_equal %1, %2` |
|
||||
| `cmp_unsigned_less` | Compares integer bit patterns as unsigned. | Unsigned integer operands. | `%3: bool = cmp_unsigned_less %1, %2` |
|
||||
| `cmp_signed_less` | Compares integers as signed. | Signed integer operands. | `%3: bool = cmp_signed_less %1, %2` |
|
||||
| `cmp_ordered_float_equal` | Is true when neither operand is NaN and they are equal. | Floating operands. | `%3: bool = cmp_ordered_float_equal %1, %2` |
|
||||
| `cmp_unordered_float_equal` | Is true when either operand is NaN, or they are equal. | Floating operands. | `%3: bool = cmp_unordered_float_equal %1, %2` |
|
||||
| `cmp_ordered_float_not_equal` | Is true when neither operand is NaN and they differ. | Floating operands. | `%3: bool = cmp_ordered_float_not_equal %1, %2` |
|
||||
| `cmp_unordered_float_not_equal` | Is true when either operand is NaN, or they differ. | Floating operands. | `%3: bool = cmp_unordered_float_not_equal %1, %2` |
|
||||
| `cmp_ordered_float_less` | Is true when neither operand is NaN and left is less. | Floating operands. | `%3: bool = cmp_ordered_float_less %1, %2` |
|
||||
| `cmp_unordered_float_less` | Is true when either operand is NaN, or left is less. | Floating operands. | `%3: bool = cmp_unordered_float_less %1, %2` |
|
||||
|
||||
There are no greater-than opcodes in the current instruction set. Swap the
|
||||
operands and use the appropriate less-than form. Less-or-equal forms are also
|
||||
not defined yet.
|
||||
|
||||
## Other opcodes
|
||||
|
||||
### `select`
|
||||
|
||||
Selects one of two equal-typed values using a boolean condition. It does not
|
||||
change control flow.
|
||||
|
||||
```text
|
||||
%4: u32 = select %1, %2, %3
|
||||
```
|
||||
|
||||
Here `%1` is `bool`; `%2`, `%3`, and `%4` share one type.
|
||||
|
||||
### `bitcast`
|
||||
|
||||
Reinterprets an operand's bits as the result type without performing a numeric
|
||||
conversion.
|
||||
|
||||
```text
|
||||
%2: f32 = bitcast %1
|
||||
```
|
||||
|
||||
The intended source and destination have equal total bit width. The current
|
||||
validator only requires that both values exist; it does not yet prove equal
|
||||
width.
|
||||
|
||||
### `composite_construct`
|
||||
|
||||
Constructs a vector or structure from its immediate elements.
|
||||
|
||||
```text
|
||||
%5: vec4[f32] = composite_construct %1, %2, %3, %4
|
||||
```
|
||||
|
||||
For a vector, every element must have the vector's element type and their count
|
||||
must equal its length. For a structure, each element must match the member at
|
||||
the same position. The validator does not support array construction yet.
|
||||
|
||||
### `composite_extract`
|
||||
|
||||
Traverses one or more literal indices through a vector, array, or structure and
|
||||
returns the selected nested member.
|
||||
|
||||
```text
|
||||
%4: f32 = composite_extract %3[1][0]
|
||||
```
|
||||
|
||||
At least one index is required. Every index must lie within its composite, and
|
||||
the result type must equal the selected member type.
|
||||
|
||||
### `load_interface`
|
||||
|
||||
Reads one declared shader input. It cannot read an output declaration.
|
||||
|
||||
```text
|
||||
%1: vec4[f32] = load_interface @in_color
|
||||
```
|
||||
|
||||
The result type must equal the interface variable's type. An optional dynamic
|
||||
`element_index` exists in memory for future arrayed interfaces, but the current
|
||||
printer does not show it and the validator does not use it to change the
|
||||
result type.
|
||||
|
||||
### `store_interface`
|
||||
|
||||
Writes one declared shader output. It produces no SSA result and cannot write an
|
||||
input declaration.
|
||||
|
||||
```text
|
||||
store_interface @out_color, %1
|
||||
```
|
||||
|
||||
The stored value must equal the interface variable's type. As with
|
||||
`load_interface`, an optional unprinted `element_index` is reserved for later
|
||||
arrayed-interface work. This operation has side effects.
|
||||
|
||||
### `call`
|
||||
|
||||
Invokes another IR function. Arguments must match the callee's parameters in
|
||||
number, order, and type.
|
||||
|
||||
```text
|
||||
%4: vec4[f32] = call @shade(%1, %2)
|
||||
call @observe(%4)
|
||||
```
|
||||
|
||||
A non-void callee requires a result of its return type; a void callee forbids
|
||||
one. Calls are conservatively treated as side-effecting. The operation exists in
|
||||
the common IR, although the current SPIR-V translator rejects
|
||||
`OpFunctionCall`.
|
||||
|
||||
## Terminators
|
||||
|
||||
Terminators yield no ordinary instruction result. They alone determine outgoing
|
||||
control-flow edges.
|
||||
|
||||
| Terminator | Description | Usage | Small printed example |
|
||||
| -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------ |
|
||||
| `branch` | Unconditionally transfers control to one block. | Pass exactly one argument for every target parameter. | `branch .merge(%3)` |
|
||||
| `conditional_branch` | Selects one of two edges using a boolean. | The condition is `bool`; each edge independently matches its target parameters. | `conditional_branch %1, .yes(%2), .no(%3)` |
|
||||
| `return` (void) | Ends a void function. | The enclosing return type is `void`. | `return` |
|
||||
| `return` (value) | Ends a function and returns a value. | The value type equals the function return type. | `return %3` |
|
||||
| `discard` | Discards the fragment invocation. | Fragment stage only. | `discard` |
|
||||
| `unreachable` | States that execution cannot reach this point. | Any function; no successors. | `unreachable` |
|
||||
|
||||
The Zig union names the return forms `return_void` and `return_value`; the
|
||||
printer renders both as the overloaded `return` spelling shown above.
|
||||
|
||||
## Interfaces and builtins
|
||||
|
||||
An interface variable has a type, a direction (`input` or `output`), and one
|
||||
semantic. Its semantic attributes are enclosed in the direction's brackets:
|
||||
|
||||
- A location: `location(N), component(C), index(I)`.
|
||||
- A builtin: `builtin(name)`.
|
||||
|
||||
The currently supported builtins are `position`, `vertex_index`, `instance_index`,
|
||||
`frag_coord`, `frag_depth`, and `global_invocation_id`.
|
||||
|
||||
Printed declarations resemble these:
|
||||
|
||||
```text
|
||||
@in_color: vec4[f32] = input[location(0), component(0), index(0)]
|
||||
@position: vec4[f32] = output[builtin(position)]
|
||||
```
|
||||
|
||||
## Complete examples
|
||||
|
||||
These examples use the printer's exact grammar and indentation. Their numeric
|
||||
value IDs are illustrative but follow the same module-wide numbering used by
|
||||
the printer.
|
||||
|
||||
### A compute shader that adds two constants
|
||||
|
||||
```text
|
||||
shader compute @main
|
||||
{
|
||||
%0: constant u32 = bits(0x1)
|
||||
%1: constant u32 = bits(0x2)
|
||||
|
||||
fn @main() -> void
|
||||
{
|
||||
.entry():
|
||||
%2: u32 = integer_add %0, %1
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### A vertex interface passed through
|
||||
|
||||
```text
|
||||
shader vertex @main
|
||||
{
|
||||
@in_color: vec4[f32] = input[location(0), component(0), index(0)]
|
||||
@out_color: vec4[f32] = output[location(0), component(0), index(0)]
|
||||
|
||||
fn @main() -> void
|
||||
{
|
||||
.entry():
|
||||
%0: vec4[f32] = load_interface @in_color
|
||||
store_interface @out_color, %0
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### A selection whose Phi becomes a block parameter
|
||||
|
||||
```text
|
||||
shader compute @main
|
||||
{
|
||||
%0: constant bool = true
|
||||
%1: constant u32 = bits(0x1)
|
||||
%2: constant u32 = bits(0x2)
|
||||
|
||||
fn @main() -> void
|
||||
{
|
||||
.entry():
|
||||
conditional_branch %0, .left(), .right()
|
||||
|
||||
.left():
|
||||
%3: u32 = integer_add %1, %2
|
||||
branch .merge(%3)
|
||||
|
||||
.right():
|
||||
%4: u32 = integer_subtract %2, %1
|
||||
branch .merge(%4)
|
||||
|
||||
.merge(%5: u32):
|
||||
%6: u32 = integer_multiply %5, %2
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The selection's merge metadata is not visible in this output, although it
|
||||
remains attached to `.entry` in memory.
|
||||
|
||||
## Validator guarantees
|
||||
|
||||
When `validator.validate` succeeds, it has proved the following:
|
||||
|
||||
- The module has a live entry point.
|
||||
- All referenced types, constants, values, functions, blocks, and instructions
|
||||
are live.
|
||||
- Parent links and SSA definition links agree in both directions.
|
||||
- Every function has an entry block, and no edge targets that entry block.
|
||||
- Every block has a terminator.
|
||||
- Edges remain within their function and exactly match target block parameters.
|
||||
- Returns agree with function return types; `discard` appears only in a fragment shader.
|
||||
- Interface loads read inputs; interface stores write outputs.
|
||||
- Operation-specific result presence and the foundational type equalities hold.
|
||||
- Structured merge and continue targets belong to the same function.
|
||||
|
||||
The validator does not yet prove every semantic category listed in the opcode
|
||||
reference. In particular, several arithmetic opcodes can currently be built
|
||||
with an inappropriate but equal operand type; bitcast widths are not compared;
|
||||
shift-count rules are rudimentary; and floating-point execution modes are not
|
||||
attached to operations. Backends should explicitly require the properties and
|
||||
validation needed by their lowering.
|
||||
|
||||
## Properties and passes
|
||||
|
||||
The module carries independent property bits:
|
||||
|
||||
- `valid_cfg`
|
||||
- `valid_ssa`
|
||||
- `structured_control_flow`
|
||||
- `no_function_calls`
|
||||
- `no_local_memory`
|
||||
- `no_matrix_types`
|
||||
- `no_large_composites`
|
||||
- `explicit_resource_offsets`
|
||||
|
||||
A pass declares properties that it requires, produces, and invalidates. The pass
|
||||
manager rejects a pass whose requirements are missing, applies its property
|
||||
changes, and runs the validator after every pass by default.
|
||||
|
||||
## Builder, rewriter, and visitor
|
||||
|
||||
`Builder.zig` is the normal entry point for construction. It interns types and
|
||||
constants, stores copied slices in the module arena, adds functions and blocks,
|
||||
appends instructions, and assigns block terminators.
|
||||
|
||||
`parser/root.zig` owns the public parsing entry points and recursive-descent grammar.
|
||||
Its implementation details are split by responsibility: `parser/Lexer.zig`
|
||||
tokenizes input, `parser/ast.zig` holds the temporary syntax model, and
|
||||
`parser/lower.zig` resolves that model into the common IR.
|
||||
|
||||
`Rewriter.zig` provides the first safe mutation operations:
|
||||
|
||||
- Count and replace SSA uses without changing definitions.
|
||||
- Erase a dead, side-effect-free instruction.
|
||||
- Redirect edges with a complete new argument list.
|
||||
- Add a block parameter while adding every incoming edge argument.
|
||||
- Remove a block parameter while removing its incoming arguments.
|
||||
|
||||
`visitor.zig` walks module declarations, functions, blocks, instructions,
|
||||
terminators, and SSA uses in hierarchical order. `cfg.zig` computes
|
||||
predecessors, reachability, and dominance with a deliberately simple quadratic
|
||||
matrix. `validator/dominance.zig` contains the SSA dominance checks built on
|
||||
that analysis. This is suitable for the foundation but is not intended as the
|
||||
final large-shader implementation.
|
||||
|
||||
## SPIR-V frontend
|
||||
|
||||
The compiler currently provides a word parser and an initial translator in
|
||||
`spirv/`. The parser validates the header, word counts, truncation, and literal
|
||||
strings. The translator selects one entry point and lowers a defined subset:
|
||||
|
||||
- Vertex, fragment, and compute stages.
|
||||
- Basic scalar, vector, array, structure, pointer, and function types.
|
||||
- Ordinary and composite constants; unapplied specialization constants are
|
||||
refused.
|
||||
- Functions, blocks, branches, structured merge marks, and returns.
|
||||
- `OpPhi` into block parameters and edge arguments.
|
||||
- The arithmetic, comparison, select, bitcast, and composite operations named
|
||||
in the reference above where mappings currently exist.
|
||||
- Decorated stage inputs and outputs, with interface load and store.
|
||||
- `OpName` debug names for functions, blocks, parameters, constants, and
|
||||
instruction results when they are valid textual IR identifiers.
|
||||
|
||||
Symbolic identifiers in SPIR-V assembly are assembler syntax and are not stored
|
||||
in the binary by `spirv-as`; add `OpName` instructions when those names must
|
||||
survive translation. Unsupported source instructions return an error; they are
|
||||
not preserved as opaque SPIR-V. This prevents silent mistranslation.
|
||||
|
||||
## Running tests
|
||||
|
||||
From the repository root, run:
|
||||
|
||||
```sh
|
||||
zig build test-ir
|
||||
```
|
||||
|
||||
The SPIR-V translation tests keep their assembly as multiline strings beside
|
||||
their assertions and pipe it through `spirv-as`. Therefore SPIRV-Tools must be
|
||||
on `PATH` when those tests run. Only malformed-binary parser tests use raw words,
|
||||
because an assembler cannot produce intentionally malformed instructions.
|
||||
|
||||
## Documentation
|
||||
|
||||
A complete codebase documentation can be found [here](https://vulkan-driver.kbz8.me/docs/ir/).
|
||||
@@ -0,0 +1,227 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
const type_ir = @import("type.zig");
|
||||
const constant_ir = @import("constant.zig");
|
||||
const inst_ir = @import("instruction.zig");
|
||||
const module_ir = @import("module.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
module: *module_ir.Module,
|
||||
|
||||
pub fn init(module: *module_ir.Module) Self {
|
||||
return .{ .module = module };
|
||||
}
|
||||
|
||||
fn copyName(self: *Self, name: ?[]const u8) !?[]const u8 {
|
||||
return if (name) |text| try self.module.allocator().dupe(u8, text) else null;
|
||||
}
|
||||
|
||||
pub fn internType(self: *Self, ty: type_ir.Type) !ids.TypeId {
|
||||
return self.module.internType(ty);
|
||||
}
|
||||
|
||||
pub fn internConstant(self: *Self, ty: ids.TypeId, candidate: constant_ir.ConstantValue) !ids.ValueId {
|
||||
for (self.module.constants.entries.items, 0..) |entry, index| {
|
||||
const existing = entry orelse continue;
|
||||
|
||||
if (existing.type == ty and constantEql(existing.value, candidate)) {
|
||||
const constant_id = ids.ConstantId.fromIndex(index);
|
||||
|
||||
for (self.module.values.entries.items, 0..) |value_entry, value_index| {
|
||||
const value = value_entry orelse continue;
|
||||
|
||||
if (value.definition == .constant and value.definition.constant == constant_id)
|
||||
return ids.ValueId.fromIndex(value_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var owned = candidate;
|
||||
if (candidate == .composite)
|
||||
owned.composite = try self.module.allocator().dupe(ids.ConstantId, candidate.composite);
|
||||
|
||||
const constant_id = try self.module.constants.add(self.module.allocator(), .{ .type = ty, .value = owned });
|
||||
return self.module.values.add(self.module.allocator(), .{
|
||||
.type = ty,
|
||||
.definition = .{ .constant = constant_id },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setValueName(self: *Self, value_id: ids.ValueId, name: ?[]const u8) !void {
|
||||
const text = name orelse return;
|
||||
const value = self.module.values.getMut(value_id) orelse return error.InvalidValue;
|
||||
|
||||
if (value.name == null)
|
||||
value.name = try self.copyName(text);
|
||||
}
|
||||
|
||||
pub fn addFunction(self: *Self, return_type: ids.TypeId, name: ?[]const u8) !ids.FunctionId {
|
||||
return self.module.functions.add(self.module.allocator(), .{
|
||||
.return_type = return_type,
|
||||
.name = try self.copyName(name),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setEntryPoint(self: *Self, function: ids.FunctionId) void {
|
||||
self.module.entry_point = function;
|
||||
}
|
||||
|
||||
pub fn addFunctionParameter(self: *Self, function_id: ids.FunctionId, ty: ids.TypeId, name: ?[]const u8) !ids.ValueId {
|
||||
const function = self.module.functions.getMut(function_id) orelse return error.InvalidFunction;
|
||||
const index: u32 = @intCast(function.parameters.items.len);
|
||||
|
||||
const value_id = try self.module.values.add(self.module.allocator(), .{
|
||||
.type = ty,
|
||||
.definition = .{ .function_parameter = .{ .function = function_id, .index = index } },
|
||||
.name = try self.copyName(name),
|
||||
});
|
||||
|
||||
try function.parameter_types.append(self.module.allocator(), ty);
|
||||
try function.parameters.append(self.module.allocator(), value_id);
|
||||
|
||||
return value_id;
|
||||
}
|
||||
|
||||
pub fn addBlock(self: *Self, function_id: ids.FunctionId, name: ?[]const u8) !ids.BlockId {
|
||||
const function = self.module.functions.getMut(function_id) orelse return error.InvalidFunction;
|
||||
|
||||
const block_id = try self.module.blocks.add(self.module.allocator(), .{
|
||||
.parent_function = function_id,
|
||||
.name = try self.copyName(name),
|
||||
});
|
||||
|
||||
try function.blocks.append(self.module.allocator(), block_id);
|
||||
if (function.entry_block == null)
|
||||
function.entry_block = block_id;
|
||||
|
||||
return block_id;
|
||||
}
|
||||
|
||||
pub fn addBlockParameter(self: *Self, block_id: ids.BlockId, ty: ids.TypeId, name: ?[]const u8) !ids.ValueId {
|
||||
const block = self.module.blocks.getMut(block_id) orelse return error.InvalidBlock;
|
||||
const index: u32 = @intCast(block.parameters.items.len);
|
||||
|
||||
const value_id = try self.module.values.add(self.module.allocator(), .{
|
||||
.type = ty,
|
||||
.definition = .{ .block_parameter = .{ .block = block_id, .index = index } },
|
||||
.name = try self.copyName(name),
|
||||
});
|
||||
|
||||
try block.parameters.append(self.module.allocator(), value_id);
|
||||
|
||||
return value_id;
|
||||
}
|
||||
|
||||
pub fn appendInstruction(
|
||||
self: *Self,
|
||||
block_id: ids.BlockId,
|
||||
result_type: ?ids.TypeId,
|
||||
operation: inst_ir.Operation,
|
||||
name: ?[]const u8,
|
||||
) !?ids.ValueId {
|
||||
const block = self.module.blocks.getMut(block_id) orelse return error.InvalidBlock;
|
||||
const owned_operation = try self.copyOperation(operation);
|
||||
|
||||
const instruction_id = try self.module.instructions.add(self.module.allocator(), .{
|
||||
.parent_block = block_id,
|
||||
.result = null,
|
||||
.operation = owned_operation,
|
||||
});
|
||||
errdefer _ = self.module.instructions.remove(instruction_id);
|
||||
|
||||
const result = if (result_type) |ty|
|
||||
try self.module.values.add(self.module.allocator(), .{
|
||||
.type = ty,
|
||||
.definition = .{ .instruction = instruction_id },
|
||||
.name = try self.copyName(name),
|
||||
})
|
||||
else
|
||||
null;
|
||||
|
||||
self.module.instructions.getMut(instruction_id).?.result = result;
|
||||
try block.instructions.append(self.module.allocator(), instruction_id);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn setTerminator(self: *Self, block_id: ids.BlockId, terminator: module_ir.Terminator) !void {
|
||||
const block = self.module.blocks.getMut(block_id) orelse return error.InvalidBlock;
|
||||
|
||||
if (block.terminator != null)
|
||||
return error.TerminatorAlreadySet;
|
||||
|
||||
block.terminator = try self.copyTerminator(terminator);
|
||||
}
|
||||
|
||||
pub fn addInterfaceVariable(
|
||||
self: *Self,
|
||||
ty: ids.TypeId,
|
||||
direction: module_ir.InterfaceDirection,
|
||||
semantic: module_ir.InterfaceSemantic,
|
||||
name: ?[]const u8,
|
||||
) !ids.InterfaceVariableId {
|
||||
return self.module.interface_variables.add(self.module.allocator(), .{
|
||||
.type = ty,
|
||||
.direction = direction,
|
||||
.semantic = semantic,
|
||||
.name = try self.copyName(name),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn edge(self: *Self, target: ids.BlockId, arguments: []const ids.ValueId) !module_ir.Edge {
|
||||
return .{
|
||||
.target = target,
|
||||
.arguments = try self.module.allocator().dupe(ids.ValueId, arguments),
|
||||
};
|
||||
}
|
||||
|
||||
fn copyOperation(self: *Self, operation: inst_ir.Operation) !inst_ir.Operation {
|
||||
return switch (operation) {
|
||||
.composite_construct => |op| .{
|
||||
.composite_construct = .{
|
||||
.elements = try self.module.allocator().dupe(ids.ValueId, op.elements),
|
||||
},
|
||||
},
|
||||
.composite_extract => |op| .{
|
||||
.composite_extract = .{
|
||||
.composite = op.composite,
|
||||
.indices = try self.module.allocator().dupe(u32, op.indices),
|
||||
},
|
||||
},
|
||||
.call => |op| .{
|
||||
.call = .{
|
||||
.function = op.function,
|
||||
.arguments = try self.module.allocator().dupe(ids.ValueId, op.arguments),
|
||||
},
|
||||
},
|
||||
else => operation,
|
||||
};
|
||||
}
|
||||
|
||||
fn copyTerminator(self: *Self, terminator: module_ir.Terminator) !module_ir.Terminator {
|
||||
return switch (terminator) {
|
||||
.branch => |edge_value| .{
|
||||
.branch = try self.edge(edge_value.target, edge_value.arguments),
|
||||
},
|
||||
.conditional_branch => |branch| .{
|
||||
.conditional_branch = .{
|
||||
.condition = branch.condition,
|
||||
.true_edge = try self.edge(branch.true_edge.target, branch.true_edge.arguments),
|
||||
.false_edge = try self.edge(branch.false_edge.target, branch.false_edge.arguments),
|
||||
},
|
||||
},
|
||||
else => terminator,
|
||||
};
|
||||
}
|
||||
|
||||
fn constantEql(a: constant_ir.ConstantValue, b: constant_ir.ConstantValue) bool {
|
||||
return switch (a) {
|
||||
.boolean => |value| b == .boolean and value == b.boolean,
|
||||
.integer_bits => |value| b == .integer_bits and value == b.integer_bits,
|
||||
.float_bits => |value| b == .float_bits and value == b.float_bits,
|
||||
.null => b == .null,
|
||||
.undef => b == .undef,
|
||||
.composite => |value| b == .composite and std.mem.eql(ids.ConstantId, value, b.composite),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
const module_ir = @import("module.zig");
|
||||
const Builder = @import("Builder.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = std.mem.Allocator.Error || error{
|
||||
InvalidValue,
|
||||
InvalidInstruction,
|
||||
InvalidBlock,
|
||||
InvalidFunction,
|
||||
TypeMismatch,
|
||||
ResultStillUsed,
|
||||
SideEffectingInstruction,
|
||||
InstructionNotOwnedByBlock,
|
||||
InvalidParameterIndex,
|
||||
MissingIncomingValue,
|
||||
UnexpectedIncomingValue,
|
||||
};
|
||||
|
||||
pub const IncomingValue = struct {
|
||||
predecessor: ids.BlockId,
|
||||
value: ids.ValueId,
|
||||
};
|
||||
|
||||
const UseCountContext = struct {
|
||||
needle: ids.ValueId,
|
||||
count: usize = 0,
|
||||
};
|
||||
|
||||
module: *module_ir.Module,
|
||||
|
||||
pub fn init(module: *module_ir.Module) Self {
|
||||
return .{ .module = module };
|
||||
}
|
||||
|
||||
pub fn countUses(self: *const Self, value: ids.ValueId) usize {
|
||||
var context: UseCountContext = .{ .needle = value };
|
||||
for (self.module.instructions.entries.items) |entry| {
|
||||
const instruction = entry orelse continue;
|
||||
instruction.operation.visitValueUses(&context, countUse);
|
||||
}
|
||||
|
||||
for (self.module.blocks.entries.items) |entry| {
|
||||
const block = entry orelse continue;
|
||||
if (block.terminator) |terminator|
|
||||
module_ir.visitTerminatorValueUses(terminator, &context, countUse);
|
||||
}
|
||||
|
||||
return context.count;
|
||||
}
|
||||
|
||||
pub fn replaceAllUses(self: *Self, old: ids.ValueId, replacement: ids.ValueId) Error!usize {
|
||||
const old_value = self.module.values.get(old) orelse return error.InvalidValue;
|
||||
const replacement_value = self.module.values.get(replacement) orelse return error.InvalidValue;
|
||||
|
||||
if (old_value.type != replacement_value.type)
|
||||
return error.TypeMismatch;
|
||||
|
||||
if (old == replacement)
|
||||
return 0;
|
||||
|
||||
var count: usize = 0;
|
||||
for (self.module.instructions.entries.items) |*entry| {
|
||||
const instruction = if (entry.*) |*value| value else continue;
|
||||
count += try instruction.operation.replaceValueUses(self.module.allocator(), old, replacement);
|
||||
}
|
||||
|
||||
for (self.module.blocks.entries.items) |*entry| {
|
||||
const block = if (entry.*) |*value| value else continue;
|
||||
if (block.terminator) |*terminator|
|
||||
count += try module_ir.replaceTerminatorValueUses(self.module.allocator(), terminator, old, replacement);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
pub fn eraseInstruction(self: *Self, instruction_id: ids.InstructionId) Error!void {
|
||||
const instruction = self.module.instructions.get(instruction_id) orelse return error.InvalidInstruction;
|
||||
|
||||
if (instruction.operation.hasSideEffects())
|
||||
return error.SideEffectingInstruction;
|
||||
|
||||
if (instruction.result) |result| {
|
||||
if (self.countUses(result) != 0)
|
||||
return error.ResultStillUsed;
|
||||
}
|
||||
|
||||
const block = self.module.blocks.getMut(instruction.parent_block) orelse return error.InvalidBlock;
|
||||
var owned_index: ?usize = null;
|
||||
|
||||
for (block.instructions.items, 0..) |candidate, index| {
|
||||
if (candidate == instruction_id) {
|
||||
owned_index = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ = block.instructions.orderedRemove(owned_index orelse return error.InstructionNotOwnedByBlock);
|
||||
if (instruction.result) |result|
|
||||
_ = self.module.values.remove(result);
|
||||
_ = self.module.instructions.remove(instruction_id);
|
||||
}
|
||||
|
||||
pub fn redirectEdges(
|
||||
self: *Self,
|
||||
source: ids.BlockId,
|
||||
old_target: ids.BlockId,
|
||||
new_target: ids.BlockId,
|
||||
new_arguments: []const ids.ValueId,
|
||||
) Error!usize {
|
||||
const source_block = self.module.blocks.get(source) orelse return error.InvalidBlock;
|
||||
const target_block = self.module.blocks.get(new_target) orelse return error.InvalidBlock;
|
||||
|
||||
if (source_block.parent_function != target_block.parent_function)
|
||||
return error.InvalidFunction;
|
||||
|
||||
try self.validateArguments(target_block, new_arguments);
|
||||
|
||||
const mutable_source = self.module.blocks.getMut(source).?;
|
||||
const terminator = if (mutable_source.terminator) |*value| value else return error.InvalidBlock;
|
||||
|
||||
var count: usize = 0;
|
||||
|
||||
switch (terminator.*) {
|
||||
.branch => |*edge| {
|
||||
if (try self.redirectOne(edge, old_target, new_target, new_arguments))
|
||||
count += 1;
|
||||
},
|
||||
.conditional_branch => |*branch| {
|
||||
if (try self.redirectOne(&branch.true_edge, old_target, new_target, new_arguments))
|
||||
count += 1;
|
||||
|
||||
if (try self.redirectOne(&branch.false_edge, old_target, new_target, new_arguments))
|
||||
count += 1;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
pub fn addBlockParameter(
|
||||
self: *Self,
|
||||
block_id: ids.BlockId,
|
||||
ty: ids.TypeId,
|
||||
name: ?[]const u8,
|
||||
incoming: []const IncomingValue,
|
||||
) Error!ids.ValueId {
|
||||
const block = self.module.blocks.get(block_id) orelse return error.InvalidBlock;
|
||||
const function = self.module.functions.get(block.parent_function) orelse return error.InvalidFunction;
|
||||
|
||||
for (incoming) |item| {
|
||||
const value = self.module.values.get(item.value) orelse return error.InvalidValue;
|
||||
|
||||
if (value.type != ty)
|
||||
return error.TypeMismatch;
|
||||
|
||||
if (!functionHasEdgeTo(self.module, function, item.predecessor, block_id))
|
||||
return error.UnexpectedIncomingValue;
|
||||
}
|
||||
for (function.blocks.items) |predecessor| {
|
||||
const edge_count = countEdgesTo(self.module.blocks.get(predecessor).?, block_id);
|
||||
if (edge_count != 0 and findIncoming(incoming, predecessor) == null)
|
||||
return error.MissingIncomingValue;
|
||||
}
|
||||
|
||||
var builder = Builder.init(self.module);
|
||||
const parameter = try builder.addBlockParameter(block_id, ty, name);
|
||||
|
||||
for (function.blocks.items) |predecessor| {
|
||||
const incoming_value = findIncoming(incoming, predecessor) orelse continue;
|
||||
try self.appendArgumentToEdges(predecessor, block_id, incoming_value);
|
||||
}
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
pub fn removeBlockParameter(
|
||||
self: *Self,
|
||||
block_id: ids.BlockId,
|
||||
parameter_index: usize,
|
||||
replacement: ids.ValueId,
|
||||
) Error!void {
|
||||
const block = self.module.blocks.get(block_id) orelse return error.InvalidBlock;
|
||||
if (parameter_index >= block.parameters.items.len) return error.InvalidParameterIndex;
|
||||
const parameter = block.parameters.items[parameter_index];
|
||||
if (parameter == replacement) return error.InvalidValue;
|
||||
_ = try self.replaceAllUses(parameter, replacement);
|
||||
|
||||
const function = self.module.functions.get(block.parent_function) orelse return error.InvalidFunction;
|
||||
for (function.blocks.items) |predecessor| {
|
||||
try self.removeArgumentFromEdges(predecessor, block_id, parameter_index);
|
||||
}
|
||||
|
||||
const mutable_block = self.module.blocks.getMut(block_id).?;
|
||||
_ = mutable_block.parameters.orderedRemove(parameter_index);
|
||||
|
||||
for (mutable_block.parameters.items[parameter_index..], parameter_index..) |value_id, index| {
|
||||
const value = self.module.values.getMut(value_id) orelse return error.InvalidValue;
|
||||
value.definition.block_parameter.index = @intCast(index);
|
||||
}
|
||||
|
||||
_ = self.module.values.remove(parameter);
|
||||
}
|
||||
|
||||
fn validateArguments(self: *const Self, target: *const module_ir.Block, arguments: []const ids.ValueId) Error!void {
|
||||
if (arguments.len != target.parameters.items.len) return error.TypeMismatch;
|
||||
for (arguments, target.parameters.items) |argument, parameter| {
|
||||
const argument_value = self.module.values.get(argument) orelse return error.InvalidValue;
|
||||
const parameter_value = self.module.values.get(parameter) orelse return error.InvalidValue;
|
||||
if (argument_value.type != parameter_value.type) return error.TypeMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
fn redirectOne(
|
||||
self: *Self,
|
||||
edge: *module_ir.Edge,
|
||||
old_target: ids.BlockId,
|
||||
new_target: ids.BlockId,
|
||||
arguments: []const ids.ValueId,
|
||||
) !bool {
|
||||
if (edge.target != old_target)
|
||||
return false;
|
||||
|
||||
edge.target = new_target;
|
||||
edge.arguments = try self.module.allocator().dupe(ids.ValueId, arguments);
|
||||
return true;
|
||||
}
|
||||
|
||||
fn appendArgumentToEdges(self: *Self, predecessor: ids.BlockId, target: ids.BlockId, value: ids.ValueId) !void {
|
||||
const block = self.module.blocks.getMut(predecessor) orelse return error.InvalidBlock;
|
||||
const terminator = if (block.terminator) |*item| item else return error.InvalidBlock;
|
||||
|
||||
switch (terminator.*) {
|
||||
.branch => |*edge| {
|
||||
if (edge.target == target)
|
||||
try self.appendEdgeArgument(edge, value);
|
||||
},
|
||||
.conditional_branch => |*branch| {
|
||||
if (branch.true_edge.target == target)
|
||||
try self.appendEdgeArgument(&branch.true_edge, value);
|
||||
|
||||
if (branch.false_edge.target == target)
|
||||
try self.appendEdgeArgument(&branch.false_edge, value);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn appendEdgeArgument(self: *Self, edge: *module_ir.Edge, value: ids.ValueId) !void {
|
||||
const arguments = try self.module.allocator().alloc(ids.ValueId, edge.arguments.len + 1);
|
||||
@memcpy(arguments[0..edge.arguments.len], edge.arguments);
|
||||
arguments[edge.arguments.len] = value;
|
||||
edge.arguments = arguments;
|
||||
}
|
||||
|
||||
fn removeArgumentFromEdges(self: *Self, predecessor: ids.BlockId, target: ids.BlockId, index: usize) !void {
|
||||
const block = self.module.blocks.getMut(predecessor) orelse return error.InvalidBlock;
|
||||
const terminator = if (block.terminator) |*item| item else return error.InvalidBlock;
|
||||
|
||||
switch (terminator.*) {
|
||||
.branch => |*edge| {
|
||||
if (edge.target == target)
|
||||
try self.removeEdgeArgument(edge, index);
|
||||
},
|
||||
.conditional_branch => |*branch| {
|
||||
if (branch.true_edge.target == target)
|
||||
try self.removeEdgeArgument(&branch.true_edge, index);
|
||||
|
||||
if (branch.false_edge.target == target)
|
||||
try self.removeEdgeArgument(&branch.false_edge, index);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn removeEdgeArgument(self: *Self, edge: *module_ir.Edge, index: usize) !void {
|
||||
if (index >= edge.arguments.len)
|
||||
return error.InvalidParameterIndex;
|
||||
|
||||
const arguments = try self.module.allocator().alloc(ids.ValueId, edge.arguments.len - 1);
|
||||
@memcpy(arguments[0..index], edge.arguments[0..index]);
|
||||
@memcpy(arguments[index..], edge.arguments[index + 1 ..]);
|
||||
edge.arguments = arguments;
|
||||
}
|
||||
|
||||
fn countUse(context: *UseCountContext, value: ids.ValueId) void {
|
||||
if (value == context.needle)
|
||||
context.count += 1;
|
||||
}
|
||||
|
||||
fn findIncoming(incoming: []const IncomingValue, predecessor: ids.BlockId) ?ids.ValueId {
|
||||
for (incoming) |item| {
|
||||
if (item.predecessor == predecessor)
|
||||
return item.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn functionHasEdgeTo(
|
||||
module: *const module_ir.Module,
|
||||
function: *const module_ir.Function,
|
||||
predecessor: ids.BlockId,
|
||||
target: ids.BlockId,
|
||||
) bool {
|
||||
for (function.blocks.items) |block_id| {
|
||||
if (block_id != predecessor)
|
||||
continue;
|
||||
return countEdgesTo(module.blocks.get(block_id) orelse return false, target) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn countEdgesTo(block: *const module_ir.Block, target: ids.BlockId) usize {
|
||||
const terminator = block.terminator orelse return 0;
|
||||
return switch (terminator) {
|
||||
.branch => |edge| @intFromBool(edge.target == target),
|
||||
.conditional_branch => |branch| @intFromBool(branch.true_edge.target == target) + @intFromBool(branch.false_edge.target == target),
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
const module_ir = @import("module.zig");
|
||||
|
||||
pub const Self = @This();
|
||||
|
||||
pub const Error = std.mem.Allocator.Error || error{
|
||||
InvalidFunction,
|
||||
MissingEntryBlock,
|
||||
InvalidBlock,
|
||||
MissingTerminator,
|
||||
CrossFunctionEdge,
|
||||
};
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
blocks: []ids.BlockId,
|
||||
predecessors_by_block: []std.ArrayList(ids.BlockId),
|
||||
reachable: []bool,
|
||||
dominators: []bool,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, module: *const module_ir.Module, function_id: ids.FunctionId) Error!Self {
|
||||
const function = module.functions.get(function_id) orelse return error.InvalidFunction;
|
||||
const entry = function.entry_block orelse return error.MissingEntryBlock;
|
||||
const blocks = try allocator.dupe(ids.BlockId, function.blocks.items);
|
||||
errdefer allocator.free(blocks);
|
||||
|
||||
const predecessor_lists = try allocator.alloc(std.ArrayList(ids.BlockId), blocks.len);
|
||||
errdefer allocator.free(predecessor_lists);
|
||||
|
||||
for (predecessor_lists) |*list|
|
||||
list.* = .empty;
|
||||
|
||||
errdefer for (predecessor_lists) |*list| list.deinit(allocator);
|
||||
|
||||
const reachable = try allocator.alloc(bool, blocks.len);
|
||||
errdefer allocator.free(reachable);
|
||||
@memset(reachable, false);
|
||||
|
||||
const dominators = try allocator.alloc(bool, blocks.len * blocks.len);
|
||||
errdefer allocator.free(dominators);
|
||||
@memset(dominators, false);
|
||||
|
||||
var self: Self = .{
|
||||
.allocator = allocator,
|
||||
.blocks = blocks,
|
||||
.predecessors_by_block = predecessor_lists,
|
||||
.reachable = reachable,
|
||||
.dominators = dominators,
|
||||
};
|
||||
|
||||
try self.buildPredecessors(module);
|
||||
try self.buildReachability(module, entry);
|
||||
self.buildDominators(entry);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
for (self.predecessors_by_block) |*list|
|
||||
list.deinit(self.allocator);
|
||||
|
||||
self.allocator.free(self.predecessors_by_block);
|
||||
self.allocator.free(self.blocks);
|
||||
self.allocator.free(self.reachable);
|
||||
self.allocator.free(self.dominators);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn predecessors(self: *const Self, block: ids.BlockId) ?[]const ids.BlockId {
|
||||
const index = self.indexOf(block) orelse return null;
|
||||
return self.predecessors_by_block[index].items;
|
||||
}
|
||||
|
||||
pub fn isReachable(self: *const Self, block: ids.BlockId) bool {
|
||||
const index = self.indexOf(block) orelse return false;
|
||||
return self.reachable[index];
|
||||
}
|
||||
|
||||
pub fn dominates(self: *const Self, dominator: ids.BlockId, block: ids.BlockId) bool {
|
||||
const dominator_index = self.indexOf(dominator) orelse return false;
|
||||
const block_index = self.indexOf(block) orelse return false;
|
||||
return self.dominators[block_index * self.blocks.len + dominator_index];
|
||||
}
|
||||
|
||||
fn buildPredecessors(self: *Self, module: *const module_ir.Module) Error!void {
|
||||
for (self.blocks) |source| {
|
||||
const block = module.blocks.get(source) orelse return error.InvalidBlock;
|
||||
const terminator = block.terminator orelse return error.MissingTerminator;
|
||||
switch (terminator) {
|
||||
.branch => |edge| try self.addPredecessor(edge.target, source),
|
||||
.conditional_branch => |branch| {
|
||||
try self.addPredecessor(branch.true_edge.target, source);
|
||||
try self.addPredecessor(branch.false_edge.target, source);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buildReachability(self: *Self, module: *const module_ir.Module, entry: ids.BlockId) Error!void {
|
||||
var queue: std.ArrayList(ids.BlockId) = .empty;
|
||||
defer queue.deinit(self.allocator);
|
||||
try queue.append(self.allocator, entry);
|
||||
self.reachable[self.indexOf(entry) orelse return error.InvalidBlock] = true;
|
||||
|
||||
var cursor: usize = 0;
|
||||
while (cursor < queue.items.len) : (cursor += 1) {
|
||||
const block = module.blocks.get(queue.items[cursor]) orelse return error.InvalidBlock;
|
||||
const terminator = block.terminator orelse return error.MissingTerminator;
|
||||
switch (terminator) {
|
||||
.branch => |edge| try self.markReachable(&queue, edge.target),
|
||||
.conditional_branch => |branch| {
|
||||
try self.markReachable(&queue, branch.true_edge.target);
|
||||
try self.markReachable(&queue, branch.false_edge.target);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buildDominators(self: *Self, entry: ids.BlockId) void {
|
||||
const entry_index = self.indexOf(entry).?;
|
||||
const count = self.blocks.len;
|
||||
|
||||
for (0..count) |block_index| {
|
||||
if (!self.reachable[block_index]) {
|
||||
self.setDominates(block_index, block_index, true);
|
||||
} else if (block_index == entry_index) {
|
||||
self.setDominates(block_index, entry_index, true);
|
||||
} else {
|
||||
for (0..count) |candidate| {
|
||||
if (self.reachable[candidate])
|
||||
self.setDominates(block_index, candidate, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (0..count) |block_index| {
|
||||
if (!self.reachable[block_index] or block_index == entry_index)
|
||||
continue;
|
||||
|
||||
for (0..count) |candidate| {
|
||||
var new_value = candidate == block_index;
|
||||
if (!new_value) {
|
||||
var saw_reachable_predecessor = false;
|
||||
new_value = true;
|
||||
for (self.predecessors_by_block[block_index].items) |predecessor| {
|
||||
const predecessor_index = self.indexOf(predecessor).?;
|
||||
|
||||
if (!self.reachable[predecessor_index])
|
||||
continue;
|
||||
|
||||
saw_reachable_predecessor = true;
|
||||
new_value = new_value and self.getDominates(predecessor_index, candidate);
|
||||
}
|
||||
new_value = new_value and saw_reachable_predecessor;
|
||||
}
|
||||
if (self.getDominates(block_index, candidate) != new_value) {
|
||||
self.setDominates(block_index, candidate, new_value);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn addPredecessor(self: *Self, target: ids.BlockId, source: ids.BlockId) Error!void {
|
||||
const target_index = self.indexOf(target) orelse return error.CrossFunctionEdge;
|
||||
try self.predecessors_by_block[target_index].append(self.allocator, source);
|
||||
}
|
||||
|
||||
fn markReachable(self: *Self, queue: *std.ArrayList(ids.BlockId), target: ids.BlockId) Error!void {
|
||||
const target_index = self.indexOf(target) orelse return error.CrossFunctionEdge;
|
||||
|
||||
if (self.reachable[target_index])
|
||||
return;
|
||||
|
||||
self.reachable[target_index] = true;
|
||||
try queue.append(self.allocator, target);
|
||||
}
|
||||
|
||||
fn indexOf(self: *const Self, block: ids.BlockId) ?usize {
|
||||
for (self.blocks, 0..) |candidate, index| {
|
||||
if (candidate == block) return index;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getDominates(self: *const Self, block_index: usize, candidate_index: usize) bool {
|
||||
return self.dominators[block_index * self.blocks.len + candidate_index];
|
||||
}
|
||||
|
||||
fn setDominates(self: *Self, block_index: usize, candidate_index: usize, value: bool) void {
|
||||
self.dominators[block_index * self.blocks.len + candidate_index] = value;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const ids = @import("id.zig");
|
||||
|
||||
pub const ConstantId = ids.ConstantId;
|
||||
pub const TypeId = ids.TypeId;
|
||||
|
||||
pub const ConstantValue = union(enum) {
|
||||
boolean: bool,
|
||||
integer_bits: u64,
|
||||
float_bits: u64,
|
||||
null,
|
||||
undef,
|
||||
composite: []const ConstantId,
|
||||
};
|
||||
|
||||
pub const Constant = struct {
|
||||
type: TypeId,
|
||||
value: ConstantValue,
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const TypeTag = opaque {};
|
||||
pub const ConstantTag = opaque {};
|
||||
pub const ValueTag = opaque {};
|
||||
pub const InstructionTag = opaque {};
|
||||
pub const BlockTag = opaque {};
|
||||
pub const FunctionTag = opaque {};
|
||||
pub const InterfaceVariableTag = opaque {};
|
||||
pub const ResourceTag = opaque {};
|
||||
|
||||
pub const TypeId = Id(TypeTag);
|
||||
pub const ConstantId = Id(ConstantTag);
|
||||
pub const ValueId = Id(ValueTag);
|
||||
pub const InstructionId = Id(InstructionTag);
|
||||
pub const BlockId = Id(BlockTag);
|
||||
pub const FunctionId = Id(FunctionTag);
|
||||
pub const InterfaceVariableId = Id(InterfaceVariableTag);
|
||||
pub const ResourceId = Id(ResourceTag);
|
||||
|
||||
pub fn Id(comptime Tag: type) type {
|
||||
return enum(u32) {
|
||||
_,
|
||||
|
||||
pub const tag_type = Tag;
|
||||
|
||||
pub fn fromIndex(item_index: usize) @This() {
|
||||
std.debug.assert(item_index <= std.math.maxInt(u32));
|
||||
return @enumFromInt(item_index);
|
||||
}
|
||||
|
||||
pub fn index(self: @This()) usize {
|
||||
return @intFromEnum(self);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Store(comptime IdType: type, comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
entries: std.ArrayList(?T) = .empty,
|
||||
|
||||
pub fn add(self: *Self, allocator: std.mem.Allocator, value: T) !IdType {
|
||||
const id = IdType.fromIndex(self.entries.items.len);
|
||||
try self.entries.append(allocator, value);
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn get(self: *const Self, id: IdType) ?*const T {
|
||||
if (id.index() >= self.entries.items.len)
|
||||
return null;
|
||||
|
||||
const entry = &self.entries.items[id.index()];
|
||||
return if (entry.*) |*value| value else null;
|
||||
}
|
||||
|
||||
pub fn getMut(self: *Self, id: IdType) ?*T {
|
||||
if (id.index() >= self.entries.items.len)
|
||||
return null;
|
||||
|
||||
const entry = &self.entries.items[id.index()];
|
||||
return if (entry.*) |*value| value else null;
|
||||
}
|
||||
|
||||
/// Removing an object leaves a tombstone as IDs are deliberately not recycled
|
||||
/// so they are never silently redirected to a different object.
|
||||
pub fn remove(self: *Self, id: IdType) bool {
|
||||
if (id.index() >= self.entries.items.len)
|
||||
return false;
|
||||
|
||||
const entry = &self.entries.items[id.index()];
|
||||
if (entry.* == null)
|
||||
return false;
|
||||
|
||||
entry.* = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn isLive(self: *const Self, id: IdType) bool {
|
||||
return self.get(id) != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
|
||||
pub const TypeId = ids.TypeId;
|
||||
pub const ValueId = ids.ValueId;
|
||||
pub const BlockId = ids.BlockId;
|
||||
pub const FunctionId = ids.FunctionId;
|
||||
pub const InterfaceVariableId = ids.InterfaceVariableId;
|
||||
|
||||
pub const SourceLocation = struct {
|
||||
file: ?[]const u8 = null,
|
||||
line: u32,
|
||||
column: u32,
|
||||
};
|
||||
|
||||
pub const UnaryOpcode = enum {
|
||||
negate,
|
||||
logical_not,
|
||||
bitwise_not,
|
||||
};
|
||||
|
||||
pub const BinaryOpcode = enum {
|
||||
integer_add,
|
||||
integer_subtract,
|
||||
integer_multiply,
|
||||
unsigned_divide,
|
||||
signed_divide,
|
||||
unsigned_modulo,
|
||||
signed_modulo,
|
||||
float_add,
|
||||
float_subtract,
|
||||
float_multiply,
|
||||
float_divide,
|
||||
float_modulo,
|
||||
shift_left,
|
||||
logical_shift_right,
|
||||
arithmetic_shift_right,
|
||||
bitwise_and,
|
||||
bitwise_or,
|
||||
bitwise_xor,
|
||||
logical_and,
|
||||
logical_or,
|
||||
};
|
||||
|
||||
pub const CompareOpcode = enum {
|
||||
equal,
|
||||
not_equal,
|
||||
unsigned_less,
|
||||
signed_less,
|
||||
ordered_float_equal,
|
||||
unordered_float_equal,
|
||||
ordered_float_not_equal,
|
||||
unordered_float_not_equal,
|
||||
ordered_float_less,
|
||||
unordered_float_less,
|
||||
};
|
||||
|
||||
pub const Unary = struct {
|
||||
opcode: UnaryOpcode,
|
||||
operand: ValueId,
|
||||
};
|
||||
|
||||
pub const Binary = struct {
|
||||
opcode: BinaryOpcode,
|
||||
lhs: ValueId,
|
||||
rhs: ValueId,
|
||||
};
|
||||
|
||||
pub const Compare = struct {
|
||||
opcode: CompareOpcode,
|
||||
lhs: ValueId,
|
||||
rhs: ValueId,
|
||||
};
|
||||
|
||||
pub const Select = struct {
|
||||
condition: ValueId,
|
||||
true_value: ValueId,
|
||||
false_value: ValueId,
|
||||
};
|
||||
|
||||
pub const CompositeConstruct = struct {
|
||||
elements: []const ValueId,
|
||||
};
|
||||
|
||||
pub const CompositeExtract = struct {
|
||||
composite: ValueId,
|
||||
indices: []const u32,
|
||||
};
|
||||
|
||||
pub const LoadInterface = struct {
|
||||
variable: InterfaceVariableId,
|
||||
element_index: ?ValueId = null,
|
||||
};
|
||||
|
||||
pub const StoreInterface = struct {
|
||||
variable: InterfaceVariableId,
|
||||
value: ValueId,
|
||||
element_index: ?ValueId = null,
|
||||
};
|
||||
|
||||
pub const Call = struct {
|
||||
function: FunctionId,
|
||||
arguments: []const ValueId,
|
||||
};
|
||||
|
||||
pub const Operation = union(enum) {
|
||||
unary: Unary,
|
||||
binary: Binary,
|
||||
compare: Compare,
|
||||
select: Select,
|
||||
bitcast: ValueId,
|
||||
composite_construct: CompositeConstruct,
|
||||
composite_extract: CompositeExtract,
|
||||
load_interface: LoadInterface,
|
||||
store_interface: StoreInterface,
|
||||
call: Call,
|
||||
|
||||
pub fn visitValueUses(self: Operation, context: anytype, comptime visitor: anytype) void {
|
||||
switch (self) {
|
||||
.unary => |op| visitor(context, op.operand),
|
||||
.binary => |op| {
|
||||
visitor(context, op.lhs);
|
||||
visitor(context, op.rhs);
|
||||
},
|
||||
.compare => |op| {
|
||||
visitor(context, op.lhs);
|
||||
visitor(context, op.rhs);
|
||||
},
|
||||
.select => |op| {
|
||||
visitor(context, op.condition);
|
||||
visitor(context, op.true_value);
|
||||
visitor(context, op.false_value);
|
||||
},
|
||||
.bitcast => |operand| visitor(context, operand),
|
||||
.composite_construct => |op| for (op.elements) |element| visitor(context, element),
|
||||
.composite_extract => |op| visitor(context, op.composite),
|
||||
.load_interface => |op| if (op.element_index) |index| visitor(context, index),
|
||||
.store_interface => |op| {
|
||||
visitor(context, op.value);
|
||||
if (op.element_index) |index|
|
||||
visitor(context, index);
|
||||
},
|
||||
.call => |op| {
|
||||
for (op.arguments) |argument|
|
||||
visitor(context, argument);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replaceValueUses(self: *Operation, allocator: std.mem.Allocator, old: ValueId, replacement: ValueId) !usize {
|
||||
var count: usize = 0;
|
||||
switch (self.*) {
|
||||
.unary => |*op| replaceOne(&op.operand, old, replacement, &count),
|
||||
.binary => |*op| {
|
||||
replaceOne(&op.lhs, old, replacement, &count);
|
||||
replaceOne(&op.rhs, old, replacement, &count);
|
||||
},
|
||||
.compare => |*op| {
|
||||
replaceOne(&op.lhs, old, replacement, &count);
|
||||
replaceOne(&op.rhs, old, replacement, &count);
|
||||
},
|
||||
.select => |*op| {
|
||||
replaceOne(&op.condition, old, replacement, &count);
|
||||
replaceOne(&op.true_value, old, replacement, &count);
|
||||
replaceOne(&op.false_value, old, replacement, &count);
|
||||
},
|
||||
.bitcast => |*operand| replaceOne(operand, old, replacement, &count),
|
||||
.composite_construct => |*op| op.elements = try replaceSlice(allocator, op.elements, old, replacement, &count),
|
||||
.composite_extract => |*op| replaceOne(&op.composite, old, replacement, &count),
|
||||
.load_interface => |*op| {
|
||||
if (op.element_index) |*index|
|
||||
replaceOne(index, old, replacement, &count);
|
||||
},
|
||||
.store_interface => |*op| {
|
||||
replaceOne(&op.value, old, replacement, &count);
|
||||
if (op.element_index) |*index|
|
||||
replaceOne(index, old, replacement, &count);
|
||||
},
|
||||
.call => |*op| op.arguments = try replaceSlice(allocator, op.arguments, old, replacement, &count),
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
pub fn hasSideEffects(self: Operation) bool {
|
||||
return switch (self) {
|
||||
.store_interface, .call => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Instruction = struct {
|
||||
parent_block: BlockId,
|
||||
result: ?ValueId,
|
||||
operation: Operation,
|
||||
source: ?SourceLocation = null,
|
||||
};
|
||||
|
||||
fn replaceOne(operand: *ValueId, old: ValueId, replacement: ValueId, count: *usize) void {
|
||||
if (operand.* != old) return;
|
||||
operand.* = replacement;
|
||||
count.* += 1;
|
||||
}
|
||||
|
||||
fn replaceSlice(
|
||||
allocator: std.mem.Allocator,
|
||||
operands: []const ValueId,
|
||||
old: ValueId,
|
||||
replacement: ValueId,
|
||||
count: *usize,
|
||||
) ![]const ValueId {
|
||||
var occurrences: usize = 0;
|
||||
for (operands) |operand| if (operand == old) {
|
||||
occurrences += 1;
|
||||
};
|
||||
|
||||
if (occurrences == 0)
|
||||
return operands;
|
||||
|
||||
const copy = try allocator.dupe(ValueId, operands);
|
||||
for (copy) |*operand| {
|
||||
if (operand.* == old)
|
||||
operand.* = replacement;
|
||||
}
|
||||
|
||||
count.* += occurrences;
|
||||
return copy;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//! ## Intermediate Representation
|
||||
//!
|
||||
//! The IR is the compiler's target-independent shader representation. It keeps
|
||||
//! shader structure explicit while hiding SPIR-V's binary encoding and large
|
||||
//! instruction surface.
|
||||
//!
|
||||
//! `module.Module` owns types, constants, values, instructions, blocks, functions,
|
||||
//! interfaces, and resources in typed ID stores. Values are in SSA form: each value
|
||||
//! is defined by a constant, parameter, instruction, or `undef`.
|
||||
//!
|
||||
//! Control flow is represented with basic blocks and terminators. Phi-like values
|
||||
//! are modeled as block parameters, with branch edge arguments supplying incoming
|
||||
//! values. Blocks can also record structured selection or loop metadata.
|
||||
//!
|
||||
//! Use `Builder` to construct modules, `validator.validate` to check invariants,
|
||||
//! `cfg` for control-flow queries, `Rewriter` for common edits, and
|
||||
//! `parser`/`printer` for the textual IR format used by tests and debugging.
|
||||
//!
|
||||
//! Here's a simple text representation of a shader module that `parser.Parser` and `printer` can handle/produce:
|
||||
//! ```
|
||||
//! shader vertex @main
|
||||
//! {
|
||||
//! @color: vec4[f32] = input[location(0), component(0), index(0)]
|
||||
//! @out_color: vec4[f32] = output[location(0), component(0), index(0)]
|
||||
//! %0: constant bool = true
|
||||
//! %1: constant f32 = bits(0x3f800000)
|
||||
//!
|
||||
//! fn @main() -> void
|
||||
//! {
|
||||
//! .entry():
|
||||
//! %3: vec4[f32] = load_interface @color
|
||||
//! conditional_branch %0, .pass(), .merge(%3)
|
||||
//!
|
||||
//! .pass():
|
||||
//! %4: vec4[f32] = composite_construct %1, %1, %1, %1
|
||||
//! branch .merge(%4)
|
||||
//!
|
||||
//! .merge(%2: vec4[f32]):
|
||||
//! store_interface @out_color, %2
|
||||
//! return
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub const Builder = @import("Builder.zig");
|
||||
pub const Rewriter = @import("Rewriter.zig");
|
||||
pub const cfg = @import("cfg.zig");
|
||||
pub const constant = @import("constant.zig");
|
||||
pub const id = @import("id.zig");
|
||||
pub const instruction = @import("instruction.zig");
|
||||
pub const module = @import("module.zig");
|
||||
pub const parser = @import("parser/parser.zig");
|
||||
pub const pass_manager = @import("pass_manager.zig");
|
||||
pub const printer = @import("printer.zig");
|
||||
pub const types = @import("type.zig");
|
||||
pub const validator = @import("validator/validator.zig");
|
||||
pub const value = @import("value.zig");
|
||||
pub const visitor = @import("visitor.zig");
|
||||
@@ -0,0 +1,235 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
const types = @import("type.zig");
|
||||
const constants = @import("constant.zig");
|
||||
const values = @import("value.zig");
|
||||
const instructions = @import("instruction.zig");
|
||||
|
||||
pub const Stage = enum {
|
||||
vertex,
|
||||
fragment,
|
||||
compute,
|
||||
};
|
||||
|
||||
pub const ExecutionModes = struct {
|
||||
workgroup_size: ?[3]u32 = null,
|
||||
early_fragment_tests: bool = false,
|
||||
};
|
||||
|
||||
pub const Properties = packed struct {
|
||||
valid_cfg: bool = false,
|
||||
valid_ssa: bool = false,
|
||||
structured_control_flow: bool = false,
|
||||
no_function_calls: bool = false,
|
||||
no_local_memory: bool = false,
|
||||
no_matrix_types: bool = false,
|
||||
no_large_composites: bool = false,
|
||||
explicit_resource_offsets: bool = false,
|
||||
_padding: u24 = 0,
|
||||
};
|
||||
|
||||
pub const ConstantStore = ids.Store(ids.ConstantId, constants.Constant);
|
||||
pub const ValueStore = ids.Store(ids.ValueId, values.Value);
|
||||
pub const InstructionStore = ids.Store(ids.InstructionId, instructions.Instruction);
|
||||
pub const BlockStore = ids.Store(ids.BlockId, Block);
|
||||
pub const FunctionStore = ids.Store(ids.FunctionId, Function);
|
||||
pub const InterfaceVariableStore = ids.Store(ids.InterfaceVariableId, InterfaceVariable);
|
||||
pub const ResourceStore = ids.Store(ids.ResourceId, Resource);
|
||||
pub const TypeStore = ids.Store(ids.TypeId, types.Type);
|
||||
|
||||
pub const Edge = struct {
|
||||
target: ids.BlockId,
|
||||
arguments: []const ids.ValueId,
|
||||
};
|
||||
|
||||
pub const Terminator = union(enum) {
|
||||
branch: Edge,
|
||||
conditional_branch: struct {
|
||||
condition: ids.ValueId,
|
||||
true_edge: Edge,
|
||||
false_edge: Edge,
|
||||
},
|
||||
return_void,
|
||||
return_value: ids.ValueId,
|
||||
discard,
|
||||
@"unreachable",
|
||||
};
|
||||
|
||||
pub const StructuredControl = union(enum) {
|
||||
none,
|
||||
selection: struct { merge_block: ids.BlockId },
|
||||
loop: struct {
|
||||
merge_block: ids.BlockId,
|
||||
continue_block: ids.BlockId,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Block = struct {
|
||||
parent_function: ids.FunctionId,
|
||||
parameters: std.ArrayList(ids.ValueId) = .empty,
|
||||
instructions: std.ArrayList(ids.InstructionId) = .empty,
|
||||
terminator: ?Terminator = null,
|
||||
structured_control: StructuredControl = .none,
|
||||
name: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Function = struct {
|
||||
return_type: ids.TypeId,
|
||||
parameter_types: std.ArrayList(ids.TypeId) = .empty,
|
||||
parameters: std.ArrayList(ids.ValueId) = .empty,
|
||||
blocks: std.ArrayList(ids.BlockId) = .empty,
|
||||
entry_block: ?ids.BlockId = null,
|
||||
name: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const InterfaceDirection = enum {
|
||||
input,
|
||||
output,
|
||||
};
|
||||
|
||||
pub const Builtin = enum {
|
||||
position,
|
||||
vertex_index,
|
||||
instance_index,
|
||||
frag_coord,
|
||||
frag_depth,
|
||||
global_invocation_id,
|
||||
};
|
||||
|
||||
pub const InterfaceSemantic = union(enum) {
|
||||
location: struct { location: u32, component: u8 = 0, index: u8 = 0 },
|
||||
builtin: Builtin,
|
||||
};
|
||||
|
||||
pub const InterfaceVariable = struct {
|
||||
type: ids.TypeId,
|
||||
direction: InterfaceDirection,
|
||||
semantic: InterfaceSemantic,
|
||||
name: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Resource = struct {
|
||||
kind: types.ResourceKind,
|
||||
set: u32,
|
||||
binding: u32,
|
||||
type: ids.TypeId,
|
||||
name: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Module = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
stage: Stage,
|
||||
entry_point: ?ids.FunctionId = null,
|
||||
execution_modes: ExecutionModes = .{},
|
||||
types: TypeStore = .{},
|
||||
constants: ConstantStore = .{},
|
||||
values: ValueStore = .{},
|
||||
instructions: InstructionStore = .{},
|
||||
blocks: BlockStore = .{},
|
||||
functions: FunctionStore = .{},
|
||||
interface_variables: InterfaceVariableStore = .{},
|
||||
resources: ResourceStore = .{},
|
||||
properties: Properties = .{},
|
||||
|
||||
pub fn init(backing_allocator: std.mem.Allocator, stage: Stage) Module {
|
||||
return .{
|
||||
.arena = std.heap.ArenaAllocator.init(backing_allocator),
|
||||
.stage = stage,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Module) void {
|
||||
self.arena.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn allocator(self: *Module) std.mem.Allocator {
|
||||
return self.arena.allocator();
|
||||
}
|
||||
|
||||
pub fn backingAllocator(self: *const Module) std.mem.Allocator {
|
||||
return self.arena.child_allocator;
|
||||
}
|
||||
|
||||
pub fn internType(self: *Module, candidate: types.Type) !ids.TypeId {
|
||||
for (self.types.entries.items, 0..) |entry, index| {
|
||||
if (entry) |existing| {
|
||||
if (existing.eql(candidate))
|
||||
return ids.TypeId.fromIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
var owned = candidate;
|
||||
if (candidate == .structure) {
|
||||
owned.structure.members = try self.allocator().dupe(ids.TypeId, candidate.structure.members);
|
||||
}
|
||||
|
||||
return self.types.add(self.allocator(), owned);
|
||||
}
|
||||
|
||||
pub fn typeOf(self: *const Module, value_id: ids.ValueId) ?ids.TypeId {
|
||||
const value = self.values.get(value_id) orelse return null;
|
||||
return value.type;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn visitTerminatorValueUses(terminator: Terminator, context: anytype, comptime visitor: anytype) void {
|
||||
switch (terminator) {
|
||||
.branch => |edge| {
|
||||
for (edge.arguments) |argument|
|
||||
visitor(context, argument);
|
||||
},
|
||||
.conditional_branch => |branch| {
|
||||
visitor(context, branch.condition);
|
||||
|
||||
for (branch.true_edge.arguments) |argument|
|
||||
visitor(context, argument);
|
||||
|
||||
for (branch.false_edge.arguments) |argument|
|
||||
visitor(context, argument);
|
||||
},
|
||||
.return_value => |value| visitor(context, value),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replaceTerminatorValueUses(allocator: std.mem.Allocator, terminator: *Terminator, old: ids.ValueId, replacement: ids.ValueId) !usize {
|
||||
var count: usize = 0;
|
||||
switch (terminator.*) {
|
||||
.branch => |*edge| try replaceEdgeUses(allocator, edge, old, replacement, &count),
|
||||
.conditional_branch => |*branch| {
|
||||
replaceOne(&branch.condition, old, replacement, &count);
|
||||
try replaceEdgeUses(allocator, &branch.true_edge, old, replacement, &count);
|
||||
try replaceEdgeUses(allocator, &branch.false_edge, old, replacement, &count);
|
||||
},
|
||||
.return_value => |*value| replaceOne(value, old, replacement, &count),
|
||||
else => {},
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
fn replaceEdgeUses(allocator: std.mem.Allocator, edge: *Edge, old: ids.ValueId, replacement: ids.ValueId, count: *usize) !void {
|
||||
var occurrences: usize = 0;
|
||||
for (edge.arguments) |argument| if (argument == old) {
|
||||
occurrences += 1;
|
||||
};
|
||||
|
||||
if (occurrences == 0)
|
||||
return;
|
||||
|
||||
const copy = try allocator.dupe(ids.ValueId, edge.arguments);
|
||||
for (copy) |*argument| {
|
||||
if (argument.* == old)
|
||||
argument.* = replacement;
|
||||
}
|
||||
|
||||
edge.arguments = copy;
|
||||
count.* += occurrences;
|
||||
}
|
||||
|
||||
fn replaceOne(operand: *ids.ValueId, old: ids.ValueId, replacement: ids.ValueId, count: *usize) void {
|
||||
if (operand.* != old)
|
||||
return;
|
||||
operand.* = replacement;
|
||||
count.* += 1;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
source: []const u8,
|
||||
cursor: usize = 0,
|
||||
lookahead: ?Token = null,
|
||||
|
||||
pub const TokenTag = enum {
|
||||
eof,
|
||||
invalid,
|
||||
identifier,
|
||||
number,
|
||||
value_ref,
|
||||
constant_ref,
|
||||
at_name,
|
||||
dot_name,
|
||||
left_brace,
|
||||
right_brace,
|
||||
left_paren,
|
||||
right_paren,
|
||||
left_square,
|
||||
right_square,
|
||||
colon,
|
||||
comma,
|
||||
equal,
|
||||
arrow,
|
||||
};
|
||||
|
||||
pub const Token = struct {
|
||||
tag: TokenTag,
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
pub fn init(source: []const u8) Self {
|
||||
return .{
|
||||
.source = source,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn peek(self: *Self) Token {
|
||||
if (self.lookahead == null)
|
||||
self.lookahead = self.lex();
|
||||
return self.lookahead.?;
|
||||
}
|
||||
|
||||
pub fn take(self: *Self) Token {
|
||||
const token = self.peek();
|
||||
self.lookahead = null;
|
||||
return token;
|
||||
}
|
||||
|
||||
fn lex(self: *Self) Token {
|
||||
while (self.cursor < self.source.len and std.ascii.isWhitespace(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
|
||||
if (self.cursor == self.source.len) {
|
||||
return .{
|
||||
.tag = .eof,
|
||||
.text = self.source[self.cursor..self.cursor],
|
||||
};
|
||||
}
|
||||
|
||||
const start = self.cursor;
|
||||
const byte = self.source[self.cursor];
|
||||
self.cursor += 1;
|
||||
|
||||
switch (byte) {
|
||||
'{' => return self.simpleToken(.left_brace, start),
|
||||
'}' => return self.simpleToken(.right_brace, start),
|
||||
'(' => return self.simpleToken(.left_paren, start),
|
||||
')' => return self.simpleToken(.right_paren, start),
|
||||
'[' => return self.simpleToken(.left_square, start),
|
||||
']' => return self.simpleToken(.right_square, start),
|
||||
':' => return self.simpleToken(.colon, start),
|
||||
',' => return self.simpleToken(.comma, start),
|
||||
'=' => return self.simpleToken(.equal, start),
|
||||
'-', '+' => {
|
||||
if (byte == '-' and self.cursor < self.source.len and self.source[self.cursor] == '>') {
|
||||
self.cursor += 1;
|
||||
return .{
|
||||
.tag = .arrow,
|
||||
.text = self.source[start..self.cursor],
|
||||
};
|
||||
}
|
||||
|
||||
if (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor]))
|
||||
return self.numberToken(start);
|
||||
|
||||
return self.simpleToken(.invalid, start);
|
||||
},
|
||||
'%', '@', '.' => {
|
||||
const tag: TokenTag = switch (byte) {
|
||||
'%' => .value_ref,
|
||||
'@' => .at_name,
|
||||
'.' => .dot_name,
|
||||
else => unreachable,
|
||||
};
|
||||
const content_start = self.cursor;
|
||||
|
||||
if (byte == '%' and self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor])) {
|
||||
while (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor])) self.cursor += 1;
|
||||
} else {
|
||||
while (self.cursor < self.source.len and isNameByte(self.source[self.cursor])) self.cursor += 1;
|
||||
}
|
||||
|
||||
if (self.cursor == content_start)
|
||||
return self.simpleToken(.invalid, start);
|
||||
|
||||
return .{
|
||||
.tag = tag,
|
||||
.text = self.source[content_start..self.cursor],
|
||||
};
|
||||
},
|
||||
'#' => {
|
||||
const number_start = self.cursor;
|
||||
while (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
|
||||
if (self.cursor == number_start)
|
||||
return self.simpleToken(.invalid, start);
|
||||
|
||||
return .{
|
||||
.tag = .constant_ref,
|
||||
.text = self.source[number_start..self.cursor],
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (std.ascii.isDigit(byte))
|
||||
return self.numberToken(start);
|
||||
|
||||
if (isNameStart(byte)) {
|
||||
while (self.cursor < self.source.len and isNameByte(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
|
||||
return .{
|
||||
.tag = .identifier,
|
||||
.text = self.source[start..self.cursor],
|
||||
};
|
||||
}
|
||||
|
||||
return self.simpleToken(.invalid, start);
|
||||
}
|
||||
|
||||
fn numberToken(self: *Self, start: usize) Token {
|
||||
var number_start = start;
|
||||
if (self.source[number_start] == '-' or self.source[number_start] == '+')
|
||||
number_start += 1;
|
||||
|
||||
self.cursor = number_start;
|
||||
|
||||
if (self.source[number_start] == '0' and number_start + 1 < self.source.len and self.source[number_start + 1] == 'x') {
|
||||
self.cursor = number_start + 2;
|
||||
while (self.cursor < self.source.len and std.ascii.isHex(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
} else {
|
||||
while (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
|
||||
if (self.cursor < self.source.len and self.source[self.cursor] == '.') {
|
||||
self.cursor += 1;
|
||||
while (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
if (self.cursor < self.source.len and (self.source[self.cursor] == 'e' or self.source[self.cursor] == 'E')) {
|
||||
self.cursor += 1;
|
||||
if (self.cursor < self.source.len and (self.source[self.cursor] == '-' or self.source[self.cursor] == '+'))
|
||||
self.cursor += 1;
|
||||
while (self.cursor < self.source.len and std.ascii.isDigit(self.source[self.cursor]))
|
||||
self.cursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.tag = .number,
|
||||
.text = self.source[start..self.cursor],
|
||||
};
|
||||
}
|
||||
|
||||
fn simpleToken(self: *Self, tag: TokenTag, start: usize) Token {
|
||||
return .{
|
||||
.tag = tag,
|
||||
.text = self.source[start..self.cursor],
|
||||
};
|
||||
}
|
||||
|
||||
fn isNameStart(byte: u8) bool {
|
||||
return std.ascii.isAlphabetic(byte) or byte == '_';
|
||||
}
|
||||
|
||||
fn isNameByte(byte: u8) bool {
|
||||
return std.ascii.isAlphanumeric(byte) or byte == '_';
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("../id.zig");
|
||||
const inst_ir = @import("../instruction.zig");
|
||||
const module_ir = @import("../module.zig");
|
||||
|
||||
pub const ValueRef = []const u8;
|
||||
|
||||
pub const ParsedModule = struct {
|
||||
entry_point_name: ?[]const u8,
|
||||
interfaces: std.ArrayList(ParsedInterface) = .empty,
|
||||
constants: std.ArrayList(ParsedConstant) = .empty,
|
||||
functions: std.ArrayList(ParsedFunction) = .empty,
|
||||
};
|
||||
|
||||
pub const ParsedInterface = struct {
|
||||
direction: module_ir.InterfaceDirection,
|
||||
name: []const u8,
|
||||
ty: ids.TypeId,
|
||||
semantic: module_ir.InterfaceSemantic,
|
||||
};
|
||||
|
||||
pub const ParsedConstantValue = union(enum) {
|
||||
boolean: bool,
|
||||
integer_bits: u64,
|
||||
float_bits: u64,
|
||||
null_value,
|
||||
undef,
|
||||
composite: []const u32,
|
||||
};
|
||||
|
||||
pub const ParsedConstant = struct {
|
||||
printed_value: ValueRef,
|
||||
ty: ids.TypeId,
|
||||
value: ParsedConstantValue,
|
||||
};
|
||||
|
||||
pub const ParsedParameter = struct {
|
||||
printed_value: ValueRef,
|
||||
ty: ids.TypeId,
|
||||
};
|
||||
|
||||
pub const ParsedInstruction = struct {
|
||||
printed_result: ?ValueRef,
|
||||
result_type: ?ids.TypeId,
|
||||
operation: ParsedOperation,
|
||||
};
|
||||
|
||||
pub const ParsedBlock = struct {
|
||||
name: []const u8,
|
||||
parameters: std.ArrayList(ParsedParameter) = .empty,
|
||||
instructions: std.ArrayList(ParsedInstruction) = .empty,
|
||||
terminator: ?ParsedTerminator = null,
|
||||
actual: ?ids.BlockId = null,
|
||||
};
|
||||
|
||||
pub const ParsedFunction = struct {
|
||||
name: []const u8,
|
||||
return_type: ids.TypeId,
|
||||
parameters: std.ArrayList(ParsedParameter) = .empty,
|
||||
blocks: std.ArrayList(ParsedBlock) = .empty,
|
||||
actual: ?ids.FunctionId = null,
|
||||
};
|
||||
|
||||
pub const ParsedEdge = struct {
|
||||
block_name: []const u8,
|
||||
arguments: []const ValueRef,
|
||||
};
|
||||
|
||||
pub const ParsedTerminator = union(enum) {
|
||||
branch: ParsedEdge,
|
||||
conditional_branch: struct {
|
||||
condition: ValueRef,
|
||||
true_edge: ParsedEdge,
|
||||
false_edge: ParsedEdge,
|
||||
},
|
||||
return_void,
|
||||
return_value: ValueRef,
|
||||
discard,
|
||||
unreachable_value,
|
||||
};
|
||||
|
||||
pub const ParsedOperation = union(enum) {
|
||||
unary: struct { opcode: inst_ir.UnaryOpcode, operand: ValueRef },
|
||||
binary: struct { opcode: inst_ir.BinaryOpcode, lhs: ValueRef, rhs: ValueRef },
|
||||
compare: struct { opcode: inst_ir.CompareOpcode, lhs: ValueRef, rhs: ValueRef },
|
||||
select: struct { condition: ValueRef, true_value: ValueRef, false_value: ValueRef },
|
||||
bitcast: ValueRef,
|
||||
composite_construct: []const ValueRef,
|
||||
composite_extract: struct { composite: ValueRef, indices: []const u32 },
|
||||
load_interface: []const u8,
|
||||
store_interface: struct { interface_name: []const u8, value: ValueRef },
|
||||
call: struct { function_name: []const u8, arguments: []const ValueRef },
|
||||
};
|
||||
@@ -0,0 +1,380 @@
|
||||
const std = @import("std");
|
||||
const Builder = @import("../Builder.zig");
|
||||
const constant_ir = @import("../constant.zig");
|
||||
const ids = @import("../id.zig");
|
||||
const inst_ir = @import("../instruction.zig");
|
||||
const module_ir = @import("../module.zig");
|
||||
const ast = @import("ast.zig");
|
||||
|
||||
const ValueRef = ast.ValueRef;
|
||||
const ParsedModule = ast.ParsedModule;
|
||||
const ParsedOperation = ast.ParsedOperation;
|
||||
const ParsedTerminator = ast.ParsedTerminator;
|
||||
const ParsedEdge = ast.ParsedEdge;
|
||||
|
||||
const LoweredOperation = struct {
|
||||
operation: inst_ir.Operation,
|
||||
inferred_type: ?ids.TypeId,
|
||||
};
|
||||
|
||||
pub fn lower(allocator: std.mem.Allocator, module: *module_ir.Module, parsed: *ParsedModule) !void {
|
||||
var builder = Builder.init(module);
|
||||
var values: std.StringHashMapUnmanaged(ids.ValueId) = .empty;
|
||||
var constants: std.AutoHashMapUnmanaged(u32, ids.ConstantId) = .empty;
|
||||
var interfaces: std.StringHashMapUnmanaged(ids.InterfaceVariableId) = .empty;
|
||||
var functions: std.StringHashMapUnmanaged(ids.FunctionId) = .empty;
|
||||
|
||||
for (parsed.interfaces.items) |interface| {
|
||||
if (interfaces.contains(interface.name))
|
||||
return error.DuplicateName;
|
||||
|
||||
const id = try builder.addInterfaceVariable(interface.ty, interface.direction, interface.semantic, interface.name);
|
||||
try interfaces.put(allocator, interface.name, id);
|
||||
}
|
||||
|
||||
for (parsed.constants.items, 0..) |constant, constant_index| {
|
||||
const value: constant_ir.ConstantValue = switch (constant.value) {
|
||||
.boolean => |item| .{ .boolean = item },
|
||||
.integer_bits => |item| .{ .integer_bits = item },
|
||||
.float_bits => |item| .{ .float_bits = item },
|
||||
.null_value => .null,
|
||||
.undef => .undef,
|
||||
.composite => |printed_elements| blk: {
|
||||
var elements: std.ArrayList(ids.ConstantId) = .empty;
|
||||
for (printed_elements) |printed_element| {
|
||||
try elements.append(allocator, constants.get(printed_element) orelse return error.UnknownConstant);
|
||||
}
|
||||
break :blk .{ .composite = elements.items };
|
||||
},
|
||||
};
|
||||
|
||||
const value_id = try builder.internConstant(constant.ty, value);
|
||||
try builder.setValueName(value_id, valueName(constant.printed_value));
|
||||
try putValue(allocator, &values, constant.printed_value, value_id);
|
||||
const definition = module.values.get(value_id).?.definition;
|
||||
|
||||
if (definition != .constant)
|
||||
return error.InvalidResult;
|
||||
|
||||
try constants.put(allocator, @intCast(constant_index), definition.constant);
|
||||
}
|
||||
|
||||
for (parsed.functions.items) |*function| {
|
||||
if (functions.contains(function.name))
|
||||
return error.DuplicateName;
|
||||
|
||||
const function_id = try builder.addFunction(function.return_type, function.name);
|
||||
function.actual = function_id;
|
||||
try functions.put(allocator, function.name, function_id);
|
||||
|
||||
for (function.parameters.items) |parameter| {
|
||||
const value_id = try builder.addFunctionParameter(function_id, parameter.ty, valueName(parameter.printed_value));
|
||||
try putValue(allocator, &values, parameter.printed_value, value_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.entry_point_name) |entry_name|
|
||||
builder.setEntryPoint(functions.get(entry_name) orelse return error.UnknownFunction);
|
||||
|
||||
for (parsed.functions.items) |*function| {
|
||||
var block_names: std.StringHashMapUnmanaged(ids.BlockId) = .empty;
|
||||
for (function.blocks.items) |*block| {
|
||||
if (block_names.contains(block.name))
|
||||
return error.DuplicateName;
|
||||
|
||||
const block_id = try builder.addBlock(function.actual.?, block.name);
|
||||
block.actual = block_id;
|
||||
try block_names.put(allocator, block.name, block_id);
|
||||
|
||||
for (block.parameters.items) |parameter| {
|
||||
const value_id = try builder.addBlockParameter(block_id, parameter.ty, valueName(parameter.printed_value));
|
||||
try putValue(allocator, &values, parameter.printed_value, value_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (function.blocks.items) |block| {
|
||||
for (block.instructions.items) |instruction| {
|
||||
const lowered = try lowerOperation(allocator, module, &values, &interfaces, &functions, instruction.operation);
|
||||
const result_type = instruction.result_type orelse lowered.inferred_type;
|
||||
|
||||
if (instruction.printed_result != null and result_type == null)
|
||||
return error.MissingResultType;
|
||||
|
||||
if (instruction.printed_result == null and result_type != null)
|
||||
return error.InvalidResult;
|
||||
|
||||
const result = try builder.appendInstruction(
|
||||
block.actual.?,
|
||||
result_type,
|
||||
lowered.operation,
|
||||
if (instruction.printed_result) |printed_result| valueName(printed_result) else null,
|
||||
);
|
||||
if (instruction.printed_result) |printed_result|
|
||||
try putValue(allocator, &values, printed_result, result orelse return error.InvalidResult)
|
||||
else if (result != null)
|
||||
return error.InvalidResult;
|
||||
}
|
||||
const terminator = try lowerTerminator(allocator, &builder, &values, &block_names, block.terminator orelse return error.MissingTerminator);
|
||||
try builder.setTerminator(block.actual.?, terminator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerOperation(
|
||||
allocator: std.mem.Allocator,
|
||||
module: *module_ir.Module,
|
||||
values: *const std.StringHashMapUnmanaged(ids.ValueId),
|
||||
interfaces: *const std.StringHashMapUnmanaged(ids.InterfaceVariableId),
|
||||
functions: *const std.StringHashMapUnmanaged(ids.FunctionId),
|
||||
parsed: ParsedOperation,
|
||||
) !LoweredOperation {
|
||||
return switch (parsed) {
|
||||
.unary => |op| blk: {
|
||||
const operand = resolveValue(values, op.operand) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.unary = .{
|
||||
.opcode = op.opcode,
|
||||
.operand = operand,
|
||||
},
|
||||
},
|
||||
.inferred_type = module.typeOf(operand),
|
||||
};
|
||||
},
|
||||
.binary => |op| blk: {
|
||||
const lhs = resolveValue(values, op.lhs) orelse return error.UnknownValue;
|
||||
const rhs = resolveValue(values, op.rhs) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.binary = .{
|
||||
.opcode = op.opcode,
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
},
|
||||
},
|
||||
.inferred_type = module.typeOf(lhs),
|
||||
};
|
||||
},
|
||||
.compare => |op| blk: {
|
||||
const lhs = resolveValue(values, op.lhs) orelse return error.UnknownValue;
|
||||
const rhs = resolveValue(values, op.rhs) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.compare = .{
|
||||
.opcode = op.opcode,
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
},
|
||||
},
|
||||
.inferred_type = try module.internType(.boolean),
|
||||
};
|
||||
},
|
||||
.select => |op| blk: {
|
||||
const condition = resolveValue(values, op.condition) orelse return error.UnknownValue;
|
||||
const true_value = resolveValue(values, op.true_value) orelse return error.UnknownValue;
|
||||
const false_value = resolveValue(values, op.false_value) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.select = .{
|
||||
.condition = condition,
|
||||
.true_value = true_value,
|
||||
.false_value = false_value,
|
||||
},
|
||||
},
|
||||
.inferred_type = module.typeOf(true_value),
|
||||
};
|
||||
},
|
||||
.bitcast => |printed_operand| blk: {
|
||||
const operand = resolveValue(values, printed_operand) orelse return error.UnknownValue;
|
||||
break :blk .{ .operation = .{ .bitcast = operand }, .inferred_type = module.typeOf(operand) };
|
||||
},
|
||||
.composite_construct => |printed_elements| blk: {
|
||||
var elements: std.ArrayList(ids.ValueId) = .empty;
|
||||
var element_types: std.ArrayList(ids.TypeId) = .empty;
|
||||
for (printed_elements) |printed_element| {
|
||||
const element = resolveValue(values, printed_element) orelse return error.UnknownValue;
|
||||
try elements.append(allocator, element);
|
||||
try element_types.append(allocator, module.typeOf(element) orelse return error.UnknownValue);
|
||||
}
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.composite_construct = .{
|
||||
.elements = elements.items,
|
||||
},
|
||||
},
|
||||
.inferred_type = try inferCompositeType(module, element_types.items),
|
||||
};
|
||||
},
|
||||
.composite_extract => |op| blk: {
|
||||
const composite = resolveValue(values, op.composite) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.composite_extract = .{
|
||||
.composite = composite,
|
||||
.indices = op.indices,
|
||||
},
|
||||
},
|
||||
.inferred_type = try extractedType(module, module.typeOf(composite) orelse return error.UnknownValue, op.indices),
|
||||
};
|
||||
},
|
||||
.load_interface => |name| blk: {
|
||||
const interface_id = interfaces.get(name) orelse return error.UnknownInterface;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.load_interface = .{
|
||||
.variable = interface_id,
|
||||
},
|
||||
},
|
||||
.inferred_type = module.interface_variables.get(interface_id).?.type,
|
||||
};
|
||||
},
|
||||
.store_interface => |op| blk: {
|
||||
const interface_id = interfaces.get(op.interface_name) orelse return error.UnknownInterface;
|
||||
const value = resolveValue(values, op.value) orelse return error.UnknownValue;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.store_interface = .{
|
||||
.variable = interface_id,
|
||||
.value = value,
|
||||
},
|
||||
},
|
||||
.inferred_type = null,
|
||||
};
|
||||
},
|
||||
.call => |op| blk: {
|
||||
const function_id = functions.get(op.function_name) orelse return error.UnknownFunction;
|
||||
var arguments: std.ArrayList(ids.ValueId) = .empty;
|
||||
|
||||
for (op.arguments) |printed_argument|
|
||||
try arguments.append(allocator, resolveValue(values, printed_argument) orelse return error.UnknownValue);
|
||||
|
||||
const return_type = module.functions.get(function_id).?.return_type;
|
||||
const return_ir_type = module.types.get(return_type) orelse return error.InvalidType;
|
||||
|
||||
break :blk .{
|
||||
.operation = .{
|
||||
.call = .{
|
||||
.function = function_id,
|
||||
.arguments = arguments.items,
|
||||
},
|
||||
},
|
||||
.inferred_type = if (return_ir_type.* == .void) null else return_type,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn lowerTerminator(
|
||||
allocator: std.mem.Allocator,
|
||||
builder: *Builder,
|
||||
values: *const std.StringHashMapUnmanaged(ids.ValueId),
|
||||
blocks: *const std.StringHashMapUnmanaged(ids.BlockId),
|
||||
parsed: ParsedTerminator,
|
||||
) !module_ir.Terminator {
|
||||
return switch (parsed) {
|
||||
.branch => |edge| .{
|
||||
.branch = try lowerEdge(allocator, builder, values, blocks, edge),
|
||||
},
|
||||
.conditional_branch => |branch| .{
|
||||
.conditional_branch = .{
|
||||
.condition = resolveValue(values, branch.condition) orelse return error.UnknownValue,
|
||||
.true_edge = try lowerEdge(allocator, builder, values, blocks, branch.true_edge),
|
||||
.false_edge = try lowerEdge(allocator, builder, values, blocks, branch.false_edge),
|
||||
},
|
||||
},
|
||||
.return_void => .return_void,
|
||||
.return_value => |printed_value| .{
|
||||
.return_value = resolveValue(values, printed_value) orelse return error.UnknownValue,
|
||||
},
|
||||
.discard => .discard,
|
||||
.unreachable_value => .@"unreachable",
|
||||
};
|
||||
}
|
||||
|
||||
fn lowerEdge(
|
||||
allocator: std.mem.Allocator,
|
||||
builder: *Builder,
|
||||
values: *const std.StringHashMapUnmanaged(ids.ValueId),
|
||||
blocks: *const std.StringHashMapUnmanaged(ids.BlockId),
|
||||
parsed: ParsedEdge,
|
||||
) !module_ir.Edge {
|
||||
var arguments: std.ArrayList(ids.ValueId) = .empty;
|
||||
for (parsed.arguments) |printed_argument|
|
||||
try arguments.append(allocator, resolveValue(values, printed_argument) orelse return error.UnknownValue);
|
||||
|
||||
return builder.edge(blocks.get(parsed.block_name) orelse return error.UnknownBlock, arguments.items);
|
||||
}
|
||||
|
||||
fn inferCompositeType(module: *module_ir.Module, element_types: []const ids.TypeId) !ids.TypeId {
|
||||
if (element_types.len >= 2 and element_types.len <= std.math.maxInt(u8)) {
|
||||
const first = element_types[0];
|
||||
for (element_types[1..]) |element_type| {
|
||||
if (element_type != first)
|
||||
return module.internType(.{
|
||||
.structure = .{
|
||||
.members = element_types,
|
||||
},
|
||||
});
|
||||
}
|
||||
return module.internType(.{
|
||||
.vector = .{
|
||||
.element_type = first,
|
||||
.length = @intCast(element_types.len),
|
||||
},
|
||||
});
|
||||
}
|
||||
return module.internType(.{
|
||||
.structure = .{
|
||||
.members = element_types,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn extractedType(module: *const module_ir.Module, root_type: ids.TypeId, indices: []const u32) !ids.TypeId {
|
||||
var current = root_type;
|
||||
for (indices) |index| {
|
||||
const ty = module.types.get(current) orelse return error.InvalidType;
|
||||
current = switch (ty.*) {
|
||||
.vector => |vector| if (index < vector.length)
|
||||
vector.element_type
|
||||
else
|
||||
return error.InvalidCompositeIndex,
|
||||
.array => |array| if (index < array.length)
|
||||
array.element_type
|
||||
else
|
||||
return error.InvalidCompositeIndex,
|
||||
.structure => |structure| if (index < structure.members.len)
|
||||
structure.members[index]
|
||||
else
|
||||
return error.InvalidCompositeIndex,
|
||||
else => return error.InvalidCompositeIndex,
|
||||
};
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
fn putValue(allocator: std.mem.Allocator, values: *std.StringHashMapUnmanaged(ids.ValueId), printed: ValueRef, actual: ids.ValueId) !void {
|
||||
if (values.contains(printed))
|
||||
return error.DuplicateValue;
|
||||
try values.put(allocator, printed, actual);
|
||||
}
|
||||
|
||||
fn resolveValue(values: *const std.StringHashMapUnmanaged(ids.ValueId), printed: ValueRef) ?ids.ValueId {
|
||||
return values.get(printed);
|
||||
}
|
||||
|
||||
fn valueName(reference: ValueRef) ?[]const u8 {
|
||||
for (reference) |byte| {
|
||||
if (!std.ascii.isDigit(byte))
|
||||
return reference;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("../id.zig");
|
||||
const inst_ir = @import("../instruction.zig");
|
||||
const module_ir = @import("../module.zig");
|
||||
const type_ir = @import("../type.zig");
|
||||
const validator = @import("../validator/validator.zig");
|
||||
const Lexer = @import("Lexer.zig");
|
||||
|
||||
const ast = @import("ast.zig");
|
||||
const lowerer = @import("lower.zig");
|
||||
|
||||
pub const Error = error{
|
||||
UnexpectedToken,
|
||||
InvalidNumber,
|
||||
InvalidStage,
|
||||
InvalidType,
|
||||
InvalidOpcode,
|
||||
InvalidSemantic,
|
||||
DuplicateValue,
|
||||
DuplicateName,
|
||||
UnknownValue,
|
||||
UnknownConstant,
|
||||
UnknownInterface,
|
||||
UnknownFunction,
|
||||
UnknownBlock,
|
||||
MissingTerminator,
|
||||
MissingResultType,
|
||||
InvalidResult,
|
||||
InvalidCompositeIndex,
|
||||
};
|
||||
|
||||
pub const max_file_size = 64 * 1024 * 1024;
|
||||
|
||||
const ValueRef = ast.ValueRef;
|
||||
const ParsedModule = ast.ParsedModule;
|
||||
const ParsedInterface = ast.ParsedInterface;
|
||||
const ParsedConstantValue = ast.ParsedConstantValue;
|
||||
const ParsedConstant = ast.ParsedConstant;
|
||||
const ParsedParameter = ast.ParsedParameter;
|
||||
const ParsedInstruction = ast.ParsedInstruction;
|
||||
const ParsedBlock = ast.ParsedBlock;
|
||||
const ParsedFunction = ast.ParsedFunction;
|
||||
const ParsedEdge = ast.ParsedEdge;
|
||||
const ParsedTerminator = ast.ParsedTerminator;
|
||||
const ParsedOperation = ast.ParsedOperation;
|
||||
const Token = Lexer.Token;
|
||||
const TokenTag = Lexer.TokenTag;
|
||||
|
||||
const Parser = struct {
|
||||
lexer: Lexer,
|
||||
allocator: std.mem.Allocator,
|
||||
module: ?*module_ir.Module = null,
|
||||
|
||||
fn parseInterface(self: *Parser) !ParsedInterface {
|
||||
const name = (try self.expect(.at_name)).text;
|
||||
|
||||
try self.expectDiscard(.colon);
|
||||
const ty = try self.parseType();
|
||||
try self.expectDiscard(.equal);
|
||||
|
||||
const direction_token = try self.expect(.identifier);
|
||||
const direction = std.meta.stringToEnum(module_ir.InterfaceDirection, direction_token.text) orelse return error.InvalidSemantic;
|
||||
try self.expectDiscard(.left_square);
|
||||
|
||||
const semantic_name = (try self.expect(.identifier)).text;
|
||||
const semantic: module_ir.InterfaceSemantic = if (std.mem.eql(u8, semantic_name, "location")) blk: {
|
||||
try self.expectDiscard(.left_paren);
|
||||
const location = try self.parseUnsigned(u32, .number);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
try self.expectDiscard(.comma);
|
||||
try self.expectIdentifier("component");
|
||||
try self.expectDiscard(.left_paren);
|
||||
const component = try self.parseUnsigned(u8, .number);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
try self.expectDiscard(.comma);
|
||||
try self.expectIdentifier("index");
|
||||
try self.expectDiscard(.left_paren);
|
||||
const index = try self.parseUnsigned(u8, .number);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
break :blk .{
|
||||
.location = .{
|
||||
.location = location,
|
||||
.component = component,
|
||||
.index = index,
|
||||
},
|
||||
};
|
||||
} else if (std.mem.eql(u8, semantic_name, "builtin")) blk: {
|
||||
try self.expectDiscard(.left_paren);
|
||||
const builtin_name = (try self.expect(.identifier)).text;
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
const builtin = std.meta.stringToEnum(module_ir.Builtin, builtin_name) orelse return error.InvalidSemantic;
|
||||
break :blk .{ .builtin = builtin };
|
||||
} else return error.InvalidSemantic;
|
||||
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return .{
|
||||
.direction = direction,
|
||||
.name = name,
|
||||
.ty = ty,
|
||||
.semantic = semantic,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseConstant(self: *Parser) !ParsedConstant {
|
||||
const printed_value = try self.parseValueRef();
|
||||
|
||||
try self.expectDiscard(.colon);
|
||||
try self.expectIdentifier("constant");
|
||||
|
||||
const ty = try self.parseType();
|
||||
try self.expectDiscard(.equal);
|
||||
|
||||
const token = try self.peek();
|
||||
|
||||
const value: ParsedConstantValue = switch (token.tag) {
|
||||
.identifier => blk: {
|
||||
const word = (try self.take()).text;
|
||||
if (std.mem.eql(u8, word, "true"))
|
||||
break :blk .{ .boolean = true };
|
||||
|
||||
if (std.mem.eql(u8, word, "false"))
|
||||
break :blk .{ .boolean = false };
|
||||
|
||||
if (std.mem.eql(u8, word, "null"))
|
||||
break :blk .null_value;
|
||||
|
||||
if (std.mem.eql(u8, word, "undef"))
|
||||
break :blk .undef;
|
||||
|
||||
if (std.mem.eql(u8, word, "bits")) {
|
||||
try self.expectDiscard(.left_paren);
|
||||
const bits = try self.parseUnsigned(u64, .number);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
const ir_type = self.module.?.types.get(ty) orelse return error.InvalidType;
|
||||
|
||||
break :blk switch (ir_type.*) {
|
||||
.integer => .{
|
||||
.integer_bits = bits,
|
||||
},
|
||||
.floating => .{
|
||||
.float_bits = bits,
|
||||
},
|
||||
else => return error.InvalidType,
|
||||
};
|
||||
}
|
||||
return error.UnexpectedToken;
|
||||
},
|
||||
.left_square => .{
|
||||
.composite = try self.parseConstantList(),
|
||||
},
|
||||
.number => try self.parseDirectConstant(ty),
|
||||
else => return error.UnexpectedToken,
|
||||
};
|
||||
|
||||
return .{
|
||||
.printed_value = printed_value,
|
||||
.ty = ty,
|
||||
.value = value,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseDirectConstant(self: *Parser, ty: ids.TypeId) !ParsedConstantValue {
|
||||
const text = (try self.expect(.number)).text;
|
||||
const ir_type = self.module.?.types.get(ty) orelse return error.InvalidType;
|
||||
|
||||
return switch (ir_type.*) {
|
||||
.integer => |integer| .{ .integer_bits = try parseIntegerLiteral(integer, text) },
|
||||
.floating => |float| .{ .float_bits = try parseFloatLiteral(float.bits, text) },
|
||||
else => error.InvalidType,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseFunction(self: *Parser) !ParsedFunction {
|
||||
try self.expectIdentifier("fn");
|
||||
const name = (try self.expect(.at_name)).text;
|
||||
try self.expectDiscard(.left_paren);
|
||||
|
||||
var parameters: std.ArrayList(ParsedParameter) = .empty;
|
||||
if ((try self.peek()).tag != .right_paren) {
|
||||
while (true) {
|
||||
const printed_value = try self.parseValueRef();
|
||||
try self.expectDiscard(.colon);
|
||||
|
||||
const ty = try self.parseType();
|
||||
try parameters.append(self.allocator, .{ .printed_value = printed_value, .ty = ty });
|
||||
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try self.expectDiscard(.right_paren);
|
||||
try self.expectDiscard(.arrow);
|
||||
|
||||
const return_type = try self.parseType();
|
||||
|
||||
var function: ParsedFunction = .{
|
||||
.name = name,
|
||||
.return_type = return_type,
|
||||
.parameters = parameters,
|
||||
};
|
||||
|
||||
try self.expectDiscard(.left_brace);
|
||||
|
||||
while ((try self.peek()).tag != .right_brace) {
|
||||
if ((try self.peek()).tag != .dot_name)
|
||||
return error.UnexpectedToken;
|
||||
try function.blocks.append(self.allocator, try self.parseBlock());
|
||||
}
|
||||
|
||||
try self.expectDiscard(.right_brace);
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
fn parseBlock(self: *Parser) !ParsedBlock {
|
||||
var block: ParsedBlock = .{
|
||||
.name = (try self.expect(.dot_name)).text,
|
||||
};
|
||||
|
||||
try self.expectDiscard(.left_paren);
|
||||
|
||||
if ((try self.peek()).tag != .right_paren) {
|
||||
while (true) {
|
||||
const printed_value = try self.parseValueRef();
|
||||
|
||||
try self.expectDiscard(.colon);
|
||||
const ty = try self.parseType();
|
||||
try block.parameters.append(self.allocator, .{ .printed_value = printed_value, .ty = ty });
|
||||
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try self.expectDiscard(.right_paren);
|
||||
try self.expectDiscard(.colon);
|
||||
|
||||
while (block.terminator == null) {
|
||||
const token = try self.peek();
|
||||
|
||||
if (token.tag == .right_brace or token.tag == .dot_name)
|
||||
return error.MissingTerminator;
|
||||
|
||||
if (token.tag == .value_ref) {
|
||||
try block.instructions.append(self.allocator, try self.parseInstruction(true));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.tag != .identifier)
|
||||
return error.UnexpectedToken;
|
||||
|
||||
if (isTerminatorName(token.text)) {
|
||||
block.terminator = try self.parseTerminator();
|
||||
} else {
|
||||
try block.instructions.append(self.allocator, try self.parseInstruction(false));
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
fn parseInstruction(self: *Parser, has_result: bool) !ParsedInstruction {
|
||||
const printed_result = if (has_result) try self.parseValueRef() else null;
|
||||
const result_type = if (has_result and try self.consume(.colon)) try self.parseType() else null;
|
||||
|
||||
if (has_result)
|
||||
try self.expectDiscard(.equal);
|
||||
|
||||
return .{
|
||||
.printed_result = printed_result,
|
||||
.result_type = result_type,
|
||||
.operation = try self.parseOperation(),
|
||||
};
|
||||
}
|
||||
|
||||
fn parseOperation(self: *Parser) !ParsedOperation {
|
||||
const name = (try self.expect(.identifier)).text;
|
||||
|
||||
if (std.mem.startsWith(u8, name, "cmp_")) {
|
||||
const opcode_name = name["cmp_".len..];
|
||||
const opcode = std.meta.stringToEnum(inst_ir.CompareOpcode, opcode_name) orelse return error.InvalidOpcode;
|
||||
const lhs = try self.parseValueRef();
|
||||
|
||||
try self.expectDiscard(.comma);
|
||||
|
||||
return .{
|
||||
.compare = .{
|
||||
.opcode = opcode,
|
||||
.lhs = lhs,
|
||||
.rhs = try self.parseValueRef(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (std.meta.stringToEnum(inst_ir.UnaryOpcode, name)) |opcode| {
|
||||
return .{ .unary = .{ .opcode = opcode, .operand = try self.parseValueRef() } };
|
||||
}
|
||||
|
||||
if (std.meta.stringToEnum(inst_ir.BinaryOpcode, name)) |opcode| {
|
||||
const lhs = try self.parseValueRef();
|
||||
try self.expectDiscard(.comma);
|
||||
return .{ .binary = .{ .opcode = opcode, .lhs = lhs, .rhs = try self.parseValueRef() } };
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "select")) {
|
||||
const condition = try self.parseValueRef();
|
||||
try self.expectDiscard(.comma);
|
||||
const true_value = try self.parseValueRef();
|
||||
try self.expectDiscard(.comma);
|
||||
return .{ .select = .{
|
||||
.condition = condition,
|
||||
.true_value = true_value,
|
||||
.false_value = try self.parseValueRef(),
|
||||
} };
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "bitcast"))
|
||||
return .{ .bitcast = try self.parseValueRef() };
|
||||
|
||||
if (std.mem.eql(u8, name, "composite_construct"))
|
||||
return .{ .composite_construct = try self.parseTrailingValueList() };
|
||||
|
||||
if (std.mem.eql(u8, name, "composite_extract")) {
|
||||
const composite = try self.parseValueRef();
|
||||
var indices: std.ArrayList(u32) = .empty;
|
||||
|
||||
while (try self.consume(.left_square)) {
|
||||
try indices.append(self.allocator, try self.parseUnsigned(u32, .number));
|
||||
try self.expectDiscard(.right_square);
|
||||
}
|
||||
|
||||
if (indices.items.len == 0)
|
||||
return error.InvalidCompositeIndex;
|
||||
return .{
|
||||
.composite_extract = .{
|
||||
.composite = composite,
|
||||
.indices = indices.items,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "load_interface"))
|
||||
return .{
|
||||
.load_interface = (try self.expect(.at_name)).text,
|
||||
};
|
||||
|
||||
if (std.mem.eql(u8, name, "store_interface")) {
|
||||
const interface_name = (try self.expect(.at_name)).text;
|
||||
try self.expectDiscard(.comma);
|
||||
return .{
|
||||
.store_interface = .{
|
||||
.interface_name = interface_name,
|
||||
.value = try self.parseValueRef(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "call")) {
|
||||
const function_name = (try self.expect(.at_name)).text;
|
||||
try self.expectDiscard(.left_paren);
|
||||
|
||||
const arguments = try self.parseDelimitedValueList(.right_paren);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
return .{
|
||||
.call = .{
|
||||
.function_name = function_name,
|
||||
.arguments = arguments,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return error.InvalidOpcode;
|
||||
}
|
||||
|
||||
fn parseTerminator(self: *Parser) !ParsedTerminator {
|
||||
const name = (try self.expect(.identifier)).text;
|
||||
|
||||
if (std.mem.eql(u8, name, "branch"))
|
||||
return .{ .branch = try self.parseEdge() };
|
||||
|
||||
if (std.mem.eql(u8, name, "conditional_branch")) {
|
||||
const condition = try self.parseValueRef();
|
||||
try self.expectDiscard(.comma);
|
||||
|
||||
const true_edge = try self.parseEdge();
|
||||
try self.expectDiscard(.comma);
|
||||
|
||||
return .{
|
||||
.conditional_branch = .{
|
||||
.condition = condition,
|
||||
.true_edge = true_edge,
|
||||
.false_edge = try self.parseEdge(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "return")) {
|
||||
if ((try self.peek()).tag == .value_ref)
|
||||
return .{ .return_value = try self.parseValueRef() };
|
||||
return .return_void;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "discard"))
|
||||
return .discard;
|
||||
|
||||
if (std.mem.eql(u8, name, "unreachable"))
|
||||
return .unreachable_value;
|
||||
|
||||
return error.UnexpectedToken;
|
||||
}
|
||||
|
||||
fn parseEdge(self: *Parser) !ParsedEdge {
|
||||
const block_name = (try self.expect(.dot_name)).text;
|
||||
try self.expectDiscard(.left_paren);
|
||||
|
||||
const arguments = try self.parseDelimitedValueList(.right_paren);
|
||||
try self.expectDiscard(.right_paren);
|
||||
|
||||
return .{
|
||||
.block_name = block_name,
|
||||
.arguments = arguments,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseType(self: *Parser) !ids.TypeId {
|
||||
const token = try self.expect(.identifier);
|
||||
const module = self.module.?;
|
||||
|
||||
if (std.mem.eql(u8, token.text, "void"))
|
||||
return module.internType(.void);
|
||||
|
||||
if (std.mem.eql(u8, token.text, "bool"))
|
||||
return module.internType(.boolean);
|
||||
|
||||
if (std.mem.startsWith(u8, token.text, "vec")) {
|
||||
const length = parseTextUnsigned(u8, token.text[3..]) catch return error.InvalidType;
|
||||
try self.expectDiscard(.left_square);
|
||||
|
||||
const element_type = try self.parseType();
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return module.internType(.{
|
||||
.vector = .{
|
||||
.element_type = element_type,
|
||||
.length = length,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, token.text, "array")) {
|
||||
try self.expectDiscard(.left_square);
|
||||
|
||||
const element_type = try self.parseType();
|
||||
try self.expectDiscard(.comma);
|
||||
|
||||
const length = try self.parseUnsigned(u32, .number);
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return module.internType(.{
|
||||
.array = .{
|
||||
.element_type = element_type,
|
||||
.length = length,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, token.text, "struct")) {
|
||||
try self.expectDiscard(.left_square);
|
||||
var members: std.ArrayList(ids.TypeId) = .empty;
|
||||
|
||||
if ((try self.peek()).tag != .right_square) {
|
||||
while (true) {
|
||||
try members.append(self.allocator, try self.parseType());
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return module.internType(.{
|
||||
.structure = .{
|
||||
.members = members.items,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, token.text, "ptr")) {
|
||||
try self.expectDiscard(.left_square);
|
||||
|
||||
const address_name = (try self.expect(.identifier)).text;
|
||||
const address_space = std.meta.stringToEnum(type_ir.AddressSpace, address_name) orelse return error.InvalidType;
|
||||
try self.expectDiscard(.comma);
|
||||
|
||||
const pointee_type = try self.parseType();
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return module.internType(.{
|
||||
.pointer = .{
|
||||
.address_space = address_space,
|
||||
.pointee_type = pointee_type,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, token.text, "resourceHandle")) {
|
||||
try self.expectDiscard(.left_square);
|
||||
|
||||
const kind_name = (try self.expect(.identifier)).text;
|
||||
const kind = std.meta.stringToEnum(type_ir.ResourceKind, kind_name) orelse return error.InvalidType;
|
||||
|
||||
try self.expectDiscard(.right_square);
|
||||
|
||||
return module.internType(.{
|
||||
.resource_handle = .{
|
||||
.kind = kind,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (token.text.len > 1 and (token.text[0] == 'i' or token.text[0] == 'u')) {
|
||||
const bits = parseTextUnsigned(u16, token.text[1..]) catch return error.InvalidType;
|
||||
return module.internType(.{ .integer = .{
|
||||
.bits = bits,
|
||||
.signedness = if (token.text[0] == 'i') .signed else .unsigned,
|
||||
} });
|
||||
}
|
||||
|
||||
if (token.text.len > 1 and token.text[0] == 'f') {
|
||||
const bits = parseTextUnsigned(u16, token.text[1..]) catch return error.InvalidType;
|
||||
return module.internType(.{
|
||||
.floating = .{
|
||||
.bits = bits,
|
||||
},
|
||||
});
|
||||
}
|
||||
return error.InvalidType;
|
||||
}
|
||||
|
||||
fn parseConstantList(self: *Parser) ![]const u32 {
|
||||
try self.expectDiscard(.left_square);
|
||||
var values: std.ArrayList(u32) = .empty;
|
||||
|
||||
if ((try self.peek()).tag != .right_square) {
|
||||
while (true) {
|
||||
try values.append(self.allocator, try self.parseUnsigned(u32, .constant_ref));
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try self.expectDiscard(.right_square);
|
||||
return values.items;
|
||||
}
|
||||
|
||||
fn parseTrailingValueList(self: *Parser) ![]const ValueRef {
|
||||
var values: std.ArrayList(ValueRef) = .empty;
|
||||
if ((try self.peek()).tag != .value_ref)
|
||||
return values.items;
|
||||
|
||||
while (true) {
|
||||
try values.append(self.allocator, try self.parseValueRef());
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
|
||||
return values.items;
|
||||
}
|
||||
|
||||
fn parseDelimitedValueList(self: *Parser, closing: TokenTag) ![]const ValueRef {
|
||||
var values: std.ArrayList(ValueRef) = .empty;
|
||||
if ((try self.peek()).tag == closing)
|
||||
return values.items;
|
||||
|
||||
while (true) {
|
||||
try values.append(self.allocator, try self.parseValueRef());
|
||||
if (!try self.consume(.comma))
|
||||
break;
|
||||
}
|
||||
|
||||
return values.items;
|
||||
}
|
||||
|
||||
fn parseValueRef(self: *Parser) !ValueRef {
|
||||
return (try self.expect(.value_ref)).text;
|
||||
}
|
||||
|
||||
fn parseUnsigned(self: *Parser, comptime T: type, tag: TokenTag) !T {
|
||||
const token = try self.expect(tag);
|
||||
return parseTextUnsigned(T, token.text) catch error.InvalidNumber;
|
||||
}
|
||||
|
||||
fn expectIdentifier(self: *Parser, expected: []const u8) !void {
|
||||
const token = try self.expect(.identifier);
|
||||
if (!std.mem.eql(u8, token.text, expected))
|
||||
return error.UnexpectedToken;
|
||||
}
|
||||
|
||||
fn expectDiscard(self: *Parser, tag: TokenTag) !void {
|
||||
_ = try self.expect(tag);
|
||||
}
|
||||
|
||||
fn expect(self: *Parser, tag: TokenTag) !Token {
|
||||
const token = try self.take();
|
||||
if (token.tag != tag)
|
||||
return error.UnexpectedToken;
|
||||
return token;
|
||||
}
|
||||
|
||||
fn consume(self: *Parser, tag: TokenTag) !bool {
|
||||
if ((try self.peek()).tag != tag)
|
||||
return false;
|
||||
_ = try self.take();
|
||||
return true;
|
||||
}
|
||||
|
||||
fn peek(self: *Parser) !Token {
|
||||
return self.lexer.peek();
|
||||
}
|
||||
|
||||
fn take(self: *Parser) !Token {
|
||||
const token = self.lexer.take();
|
||||
if (token.tag == .invalid)
|
||||
return error.UnexpectedToken;
|
||||
return token;
|
||||
}
|
||||
};
|
||||
|
||||
fn isTerminatorName(name: []const u8) bool {
|
||||
return std.mem.eql(u8, name, "branch") or
|
||||
std.mem.eql(u8, name, "conditional_branch") or
|
||||
std.mem.eql(u8, name, "return") or
|
||||
std.mem.eql(u8, name, "discard") or
|
||||
std.mem.eql(u8, name, "unreachable");
|
||||
}
|
||||
|
||||
fn parseIntegerLiteral(integer: type_ir.IntegerType, text: []const u8) !u64 {
|
||||
if (integer.bits == 0 or integer.bits > 64)
|
||||
return error.InvalidType;
|
||||
|
||||
if (integer.signedness == .unsigned) {
|
||||
const value = std.fmt.parseInt(u64, text, 10) catch return error.InvalidNumber;
|
||||
if (integer.bits < 64) {
|
||||
const shift: u6 = @intCast(integer.bits);
|
||||
const maximum = (@as(u64, 1) << shift) - 1;
|
||||
if (value > maximum)
|
||||
return error.InvalidNumber;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const value = std.fmt.parseInt(i64, text, 10) catch return error.InvalidNumber;
|
||||
if (integer.bits < 64) {
|
||||
const sign_shift: u6 = @intCast(integer.bits - 1);
|
||||
const magnitude = @as(i64, 1) << sign_shift;
|
||||
if (value < -magnitude or value > magnitude - 1)
|
||||
return error.InvalidNumber;
|
||||
|
||||
const width: u6 = @intCast(integer.bits);
|
||||
const mask = (@as(u64, 1) << width) - 1;
|
||||
return @as(u64, @bitCast(value)) & mask;
|
||||
}
|
||||
|
||||
return @bitCast(value);
|
||||
}
|
||||
|
||||
fn parseFloatLiteral(bits: u16, text: []const u8) !u64 {
|
||||
return switch (bits) {
|
||||
16 => blk: {
|
||||
const value = std.fmt.parseFloat(f16, text) catch return error.InvalidNumber;
|
||||
break :blk @as(u16, @bitCast(value));
|
||||
},
|
||||
32 => blk: {
|
||||
const value = std.fmt.parseFloat(f32, text) catch return error.InvalidNumber;
|
||||
break :blk @as(u32, @bitCast(value));
|
||||
},
|
||||
64 => blk: {
|
||||
const value = std.fmt.parseFloat(f64, text) catch return error.InvalidNumber;
|
||||
break :blk @as(u64, @bitCast(value));
|
||||
},
|
||||
else => error.InvalidType,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseTextUnsigned(comptime T: type, text: []const u8) !T {
|
||||
const base: u8 = if (std.mem.startsWith(u8, text, "0x")) 16 else 10;
|
||||
const digits = if (base == 16) text[2..] else text;
|
||||
|
||||
if (digits.len == 0)
|
||||
return error.InvalidNumber;
|
||||
|
||||
return std.fmt.parseInt(T, digits, base);
|
||||
}
|
||||
|
||||
pub fn parseString(backing_allocator: std.mem.Allocator, source: []const u8) !module_ir.Module {
|
||||
var temporary = std.heap.ArenaAllocator.init(backing_allocator);
|
||||
defer temporary.deinit();
|
||||
const temporary_allocator = temporary.allocator();
|
||||
|
||||
var parser: Parser = .{
|
||||
.lexer = .init(source),
|
||||
.allocator = temporary_allocator,
|
||||
};
|
||||
|
||||
try parser.expectIdentifier("shader");
|
||||
const stage_token = try parser.expect(.identifier);
|
||||
const stage = std.meta.stringToEnum(module_ir.Stage, stage_token.text) orelse return error.InvalidStage;
|
||||
|
||||
var module = module_ir.Module.init(backing_allocator, stage);
|
||||
errdefer module.deinit();
|
||||
parser.module = &module;
|
||||
|
||||
const entry_point_name = if ((try parser.peek()).tag == .at_name)
|
||||
(try parser.take()).text
|
||||
else
|
||||
null;
|
||||
|
||||
try parser.expectDiscard(.left_brace);
|
||||
|
||||
var parsed: ParsedModule = .{ .entry_point_name = entry_point_name };
|
||||
while ((try parser.peek()).tag != .right_brace) {
|
||||
const token = try parser.peek();
|
||||
switch (token.tag) {
|
||||
.value_ref => try parsed.constants.append(temporary_allocator, try parser.parseConstant()),
|
||||
.at_name => try parsed.interfaces.append(temporary_allocator, try parser.parseInterface()),
|
||||
.identifier => {
|
||||
if (std.mem.eql(u8, token.text, "fn")) {
|
||||
try parsed.functions.append(temporary_allocator, try parser.parseFunction());
|
||||
} else {
|
||||
return error.UnexpectedToken;
|
||||
}
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
}
|
||||
try parser.expectDiscard(.right_brace);
|
||||
try parser.expectDiscard(.eof);
|
||||
|
||||
try lowerer.lower(temporary_allocator, &module, &parsed);
|
||||
try validator.validate(&module);
|
||||
return module;
|
||||
}
|
||||
|
||||
pub fn parseFile(backing_allocator: std.mem.Allocator, io: std.Io, path: []const u8) !module_ir.Module {
|
||||
return parseFileInDir(backing_allocator, io, std.Io.Dir.cwd(), path);
|
||||
}
|
||||
|
||||
pub fn parseFileInDir(backing_allocator: std.mem.Allocator, io: std.Io, directory: std.Io.Dir, path: []const u8) !module_ir.Module {
|
||||
const file = try directory.openFile(io, path, .{});
|
||||
defer file.close(io);
|
||||
|
||||
var buffer: [4096]u8 = @splat(0);
|
||||
var reader = file.reader(io, &buffer);
|
||||
|
||||
const source = try reader.interface.allocRemaining(backing_allocator, .limited(max_file_size));
|
||||
defer backing_allocator.free(source);
|
||||
|
||||
return parseString(backing_allocator, source);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
const std = @import("std");
|
||||
const module_ir = @import("module.zig");
|
||||
const validator = @import("validator/validator.zig");
|
||||
|
||||
pub const Context = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
validate_after_each_pass: bool = true,
|
||||
};
|
||||
|
||||
pub const Pass = struct {
|
||||
name: []const u8,
|
||||
required: module_ir.Properties = .{},
|
||||
produced: module_ir.Properties = .{},
|
||||
invalidated: module_ir.Properties = .{},
|
||||
run: *const fn (module: *module_ir.Module, context: *Context) anyerror!bool,
|
||||
};
|
||||
|
||||
pub const Manager = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
passes: std.ArrayList(Pass) = .empty,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Manager {
|
||||
return .{ .allocator = allocator };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Manager) void {
|
||||
self.passes.deinit(self.allocator);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn add(self: *Manager, pass: Pass) !void {
|
||||
try self.passes.append(self.allocator, pass);
|
||||
}
|
||||
|
||||
pub fn run(self: *Manager, module: *module_ir.Module, context: *Context) !bool {
|
||||
var changed = false;
|
||||
for (self.passes.items) |pass| {
|
||||
if (!satisfies(module.properties, pass.required))
|
||||
return error.RequiredPropertyMissing;
|
||||
|
||||
changed = (try pass.run(module, context)) or changed;
|
||||
applyInvalidated(&module.properties, pass.invalidated);
|
||||
applyProduced(&module.properties, pass.produced);
|
||||
|
||||
if (context.validate_after_each_pass)
|
||||
try validator.validate(module);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
};
|
||||
|
||||
fn satisfies(actual: module_ir.Properties, required: module_ir.Properties) bool {
|
||||
inline for (property_names) |name| {
|
||||
if (@field(required, name) and !@field(actual, name))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn applyProduced(properties: *module_ir.Properties, produced: module_ir.Properties) void {
|
||||
inline for (property_names) |name| {
|
||||
if (@field(produced, name))
|
||||
@field(properties, name) = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn applyInvalidated(properties: *module_ir.Properties, invalidated: module_ir.Properties) void {
|
||||
inline for (property_names) |name| {
|
||||
if (@field(invalidated, name))
|
||||
@field(properties, name) = false;
|
||||
}
|
||||
}
|
||||
|
||||
const property_names = .{
|
||||
"valid_cfg",
|
||||
"valid_ssa",
|
||||
"structured_control_flow",
|
||||
"no_function_calls",
|
||||
"no_local_memory",
|
||||
"no_matrix_types",
|
||||
"no_large_composites",
|
||||
"explicit_resource_offsets",
|
||||
};
|
||||
@@ -0,0 +1,336 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
const inst_ir = @import("instruction.zig");
|
||||
const module_ir = @import("module.zig");
|
||||
|
||||
const indent = " ";
|
||||
|
||||
pub fn write(module: *const module_ir.Module, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
try writer.print("shader {t}", .{module.stage});
|
||||
|
||||
if (module.entry_point) |entry| {
|
||||
try writer.writeByte(' ');
|
||||
try writeFunctionRef(module, writer, entry);
|
||||
}
|
||||
|
||||
try writer.writeAll("\n{\n");
|
||||
|
||||
for (module.interface_variables.entries.items, 0..) |entry, index| {
|
||||
const variable = entry orelse continue;
|
||||
|
||||
try writer.writeAll(indent);
|
||||
try writeNamedRef(writer, variable.name, "interface", index);
|
||||
try writer.writeAll(": ");
|
||||
try writeType(module, writer, variable.type);
|
||||
try writer.print(" = {t}[", .{variable.direction});
|
||||
|
||||
switch (variable.semantic) {
|
||||
.location => |location| try writer.print("location({d}), component({d}), index({d})", .{ location.location, location.component, location.index }),
|
||||
.builtin => |builtin| try writer.print("builtin({t})", .{builtin}),
|
||||
}
|
||||
|
||||
try writer.writeAll("]\n");
|
||||
}
|
||||
|
||||
for (module.constants.entries.items, 0..) |entry, constant_index| {
|
||||
const constant = entry orelse continue;
|
||||
const value_id = constantValueId(module, ids.ConstantId.fromIndex(constant_index)) orelse continue;
|
||||
|
||||
try writer.writeAll(indent);
|
||||
try writeValueRef(module, writer, value_id);
|
||||
try writer.writeAll(": constant ");
|
||||
try writeType(module, writer, constant.type);
|
||||
try writer.writeAll(" = ");
|
||||
|
||||
switch (constant.value) {
|
||||
.boolean => |value| try writer.print("{}", .{value}),
|
||||
.integer_bits => |bits| try writer.print("bits(0x{x})", .{bits}),
|
||||
.float_bits => |bits| try writer.print("bits(0x{x})", .{bits}),
|
||||
.null => try writer.writeAll("null"),
|
||||
.undef => try writer.writeAll("undef"),
|
||||
.composite => |elements| {
|
||||
try writer.writeByte('[');
|
||||
for (elements, 0..) |element, index| {
|
||||
if (index != 0) try writer.writeAll(", ");
|
||||
try writer.print("#{d}", .{element.index()});
|
||||
}
|
||||
try writer.writeByte(']');
|
||||
},
|
||||
}
|
||||
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
|
||||
for (module.functions.entries.items, 0..) |entry, function_index| {
|
||||
const function = entry orelse continue;
|
||||
|
||||
try writer.writeAll("\n" ++ indent ++ "fn ");
|
||||
try writeNamedRef(writer, function.name, "fn", function_index);
|
||||
try writer.writeByte('(');
|
||||
for (function.parameters.items, 0..) |parameter, index| {
|
||||
if (index != 0)
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, parameter);
|
||||
try writer.writeAll(": ");
|
||||
try writeType(module, writer, function.parameter_types.items[index]);
|
||||
}
|
||||
try writer.writeAll(") -> ");
|
||||
try writeType(module, writer, function.return_type);
|
||||
try writer.writeAll("\n" ++ indent ++ "{\n");
|
||||
|
||||
for (function.blocks.items) |block_id| {
|
||||
const block = module.blocks.get(block_id) orelse continue;
|
||||
|
||||
try writer.writeAll(indent ** 2);
|
||||
try writeBlockRef(module, writer, block_id);
|
||||
try writer.writeByte('(');
|
||||
for (block.parameters.items, 0..) |parameter, index| {
|
||||
if (index != 0) try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, parameter);
|
||||
try writer.writeAll(": ");
|
||||
try writeType(module, writer, module.typeOf(parameter).?);
|
||||
}
|
||||
try writer.writeAll("):\n");
|
||||
|
||||
for (block.instructions.items) |instruction_id| {
|
||||
const instruction = module.instructions.get(instruction_id) orelse continue;
|
||||
try writer.writeAll(indent ** 3);
|
||||
if (instruction.result) |result| {
|
||||
try writeValueRef(module, writer, result);
|
||||
try writer.writeAll(": ");
|
||||
try writeType(module, writer, module.typeOf(result).?);
|
||||
try writer.writeAll(" = ");
|
||||
}
|
||||
try writeOperation(module, writer, instruction.operation);
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
|
||||
if (block.terminator) |terminator| {
|
||||
try writer.writeAll(indent ** 3);
|
||||
try writeTerminator(module, writer, terminator);
|
||||
try writer.writeAll("\n\n");
|
||||
} else {
|
||||
try writer.writeAll(indent ** 3 ++ "<missing terminator>\n\n");
|
||||
}
|
||||
}
|
||||
try writer.writeAll(indent ++ "}\n");
|
||||
}
|
||||
try writer.writeAll("}\n");
|
||||
}
|
||||
|
||||
pub fn allocPrint(allocator: std.mem.Allocator, module: *const module_ir.Module) ![]u8 {
|
||||
var output: std.Io.Writer.Allocating = .init(allocator);
|
||||
defer output.deinit();
|
||||
try write(module, &output.writer);
|
||||
return output.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn writeType(module: *const module_ir.Module, writer: *std.Io.Writer, type_id: ids.TypeId) !void {
|
||||
const ty = module.types.get(type_id) orelse {
|
||||
try writer.print("<invalid-type-{d}>", .{type_id.index()});
|
||||
return;
|
||||
};
|
||||
|
||||
switch (ty.*) {
|
||||
.void => try writer.writeAll("void"),
|
||||
.boolean => try writer.writeAll("bool"),
|
||||
.integer => |integer| try writer.print("{s}{d}", .{ if (integer.signedness == .signed) "i" else "u", integer.bits }),
|
||||
.floating => |float| try writer.print("f{d}", .{float.bits}),
|
||||
.vector => |vector| {
|
||||
try writer.print("vec{d}[", .{vector.length});
|
||||
try writeType(module, writer, vector.element_type);
|
||||
try writer.writeByte(']');
|
||||
},
|
||||
.array => |array| {
|
||||
try writer.writeAll("array[");
|
||||
try writeType(module, writer, array.element_type);
|
||||
try writer.print(", {d}]", .{array.length});
|
||||
},
|
||||
.structure => |structure| {
|
||||
try writer.writeAll("struct[");
|
||||
for (structure.members, 0..) |member, index| {
|
||||
if (index != 0) try writer.writeAll(", ");
|
||||
try writeType(module, writer, member);
|
||||
}
|
||||
try writer.writeByte(']');
|
||||
},
|
||||
.pointer => |pointer| {
|
||||
try writer.print("ptr[{t}, ", .{pointer.address_space});
|
||||
try writeType(module, writer, pointer.pointee_type);
|
||||
try writer.writeByte(']');
|
||||
},
|
||||
.resource_handle => |handle| try writer.print("resourceHandle[{t}]", .{handle.kind}),
|
||||
}
|
||||
}
|
||||
|
||||
fn writeOperation(module: *const module_ir.Module, writer: *std.Io.Writer, operation: inst_ir.Operation) !void {
|
||||
switch (operation) {
|
||||
.unary => |op| {
|
||||
try writer.print("{t} ", .{op.opcode});
|
||||
try writeValueRef(module, writer, op.operand);
|
||||
},
|
||||
.binary => |op| {
|
||||
try writer.print("{t} ", .{op.opcode});
|
||||
try writeValueRef(module, writer, op.lhs);
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, op.rhs);
|
||||
},
|
||||
.compare => |op| {
|
||||
try writer.print("cmp_{t} ", .{op.opcode});
|
||||
try writeValueRef(module, writer, op.lhs);
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, op.rhs);
|
||||
},
|
||||
.select => |op| {
|
||||
try writer.writeAll("select ");
|
||||
try writeValueRef(module, writer, op.condition);
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, op.true_value);
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, op.false_value);
|
||||
},
|
||||
.bitcast => |value| {
|
||||
try writer.writeAll("bitcast ");
|
||||
try writeValueRef(module, writer, value);
|
||||
},
|
||||
.composite_construct => |op| {
|
||||
try writer.writeAll("composite_construct ");
|
||||
try writeValueList(module, writer, op.elements);
|
||||
},
|
||||
.composite_extract => |op| {
|
||||
try writer.writeAll("composite_extract ");
|
||||
try writeValueRef(module, writer, op.composite);
|
||||
for (op.indices) |index| try writer.print("[{d}]", .{index});
|
||||
},
|
||||
.load_interface => |op| {
|
||||
try writer.writeAll("load_interface ");
|
||||
const variable = module.interface_variables.get(op.variable);
|
||||
try writeNamedRef(writer, if (variable) |v| v.name else null, "interface", op.variable.index());
|
||||
},
|
||||
.store_interface => |op| {
|
||||
try writer.writeAll("store_interface ");
|
||||
const variable = module.interface_variables.get(op.variable);
|
||||
try writeNamedRef(writer, if (variable) |v| v.name else null, "interface", op.variable.index());
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, op.value);
|
||||
},
|
||||
.call => |op| {
|
||||
try writer.writeAll("call ");
|
||||
try writeFunctionRef(module, writer, op.function);
|
||||
try writer.writeByte('(');
|
||||
try writeValueList(module, writer, op.arguments);
|
||||
try writer.writeByte(')');
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn writeTerminator(module: *const module_ir.Module, writer: *std.Io.Writer, terminator: module_ir.Terminator) !void {
|
||||
switch (terminator) {
|
||||
.branch => |edge| {
|
||||
try writer.writeAll("branch ");
|
||||
try writeEdge(module, writer, edge);
|
||||
},
|
||||
.conditional_branch => |branch| {
|
||||
try writer.writeAll("conditional_branch ");
|
||||
try writeValueRef(module, writer, branch.condition);
|
||||
try writer.writeAll(", ");
|
||||
try writeEdge(module, writer, branch.true_edge);
|
||||
try writer.writeAll(", ");
|
||||
try writeEdge(module, writer, branch.false_edge);
|
||||
},
|
||||
.return_void => try writer.writeAll("return"),
|
||||
.return_value => |value| {
|
||||
try writer.writeAll("return ");
|
||||
try writeValueRef(module, writer, value);
|
||||
},
|
||||
.discard => try writer.writeAll("discard"),
|
||||
.@"unreachable" => try writer.writeAll("unreachable"),
|
||||
}
|
||||
}
|
||||
|
||||
fn writeEdge(module: *const module_ir.Module, writer: *std.Io.Writer, edge: module_ir.Edge) !void {
|
||||
try writeBlockRef(module, writer, edge.target);
|
||||
try writer.writeByte('(');
|
||||
try writeValueList(module, writer, edge.arguments);
|
||||
try writer.writeByte(')');
|
||||
}
|
||||
|
||||
fn writeValueList(module: *const module_ir.Module, writer: *std.Io.Writer, value_ids: []const ids.ValueId) !void {
|
||||
for (value_ids, 0..) |value, index| {
|
||||
if (index != 0)
|
||||
try writer.writeAll(", ");
|
||||
try writeValueRef(module, writer, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn writeValueRef(module: *const module_ir.Module, writer: *std.Io.Writer, value_id: ids.ValueId) !void {
|
||||
try writer.writeByte('%');
|
||||
|
||||
const value = module.values.get(value_id);
|
||||
if (value) |data| {
|
||||
if (data.name) |name| {
|
||||
if (isValidName(name) and isUniqueValueName(module, value_id, name)) {
|
||||
try writer.writeAll(name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try writer.print("{d}", .{value_id.index()});
|
||||
}
|
||||
|
||||
fn isValidName(name: []const u8) bool {
|
||||
if (name.len == 0 or (!std.ascii.isAlphabetic(name[0]) and name[0] != '_'))
|
||||
return false;
|
||||
|
||||
for (name[1..]) |byte| {
|
||||
if (!std.ascii.isAlphanumeric(byte) and byte != '_')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn isUniqueValueName(module: *const module_ir.Module, value_id: ids.ValueId, name: []const u8) bool {
|
||||
for (module.values.entries.items, 0..) |entry, index| {
|
||||
if (index == value_id.index())
|
||||
continue;
|
||||
|
||||
const other = entry orelse continue;
|
||||
if (other.name) |other_name| {
|
||||
if (std.mem.eql(u8, name, other_name))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn writeBlockRef(module: *const module_ir.Module, writer: *std.Io.Writer, block: ids.BlockId) !void {
|
||||
const value = module.blocks.get(block);
|
||||
try writeNamedRef(writer, if (value) |b| b.name else null, "b", block.index());
|
||||
}
|
||||
|
||||
fn writeFunctionRef(module: *const module_ir.Module, writer: *std.Io.Writer, function: ids.FunctionId) !void {
|
||||
const value = module.functions.get(function);
|
||||
try writeNamedRef(writer, if (value) |f| f.name else null, "fn", function.index());
|
||||
}
|
||||
|
||||
fn writeNamedRef(writer: *std.Io.Writer, name: ?[]const u8, fallback: []const u8, index: usize) !void {
|
||||
try writer.writeByte(if (std.mem.eql(u8, fallback, "b")) '.' else '@');
|
||||
if (name) |text| {
|
||||
if (isValidName(text)) {
|
||||
try writer.writeAll(text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try writer.print("{s}{d}", .{ fallback, index });
|
||||
}
|
||||
|
||||
fn constantValueId(module: *const module_ir.Module, constant_id: ids.ConstantId) ?ids.ValueId {
|
||||
for (module.values.entries.items, 0..) |entry, index| {
|
||||
const value = entry orelse continue;
|
||||
if (value.definition == .constant and value.definition.constant == constant_id)
|
||||
return ids.ValueId.fromIndex(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("id.zig");
|
||||
|
||||
pub const TypeId = ids.TypeId;
|
||||
|
||||
pub const Signedness = enum { signed, unsigned };
|
||||
|
||||
pub const IntegerType = struct {
|
||||
bits: u16,
|
||||
signedness: Signedness,
|
||||
};
|
||||
|
||||
pub const FloatType = struct {
|
||||
bits: u16,
|
||||
};
|
||||
|
||||
pub const VectorType = struct {
|
||||
element_type: TypeId,
|
||||
length: u8,
|
||||
};
|
||||
|
||||
pub const ArrayType = struct {
|
||||
element_type: TypeId,
|
||||
length: u32,
|
||||
};
|
||||
|
||||
pub const StructureType = struct {
|
||||
members: []const TypeId,
|
||||
};
|
||||
|
||||
pub const AddressSpace = enum {
|
||||
function,
|
||||
private,
|
||||
workgroup,
|
||||
input,
|
||||
output,
|
||||
uniform,
|
||||
storage,
|
||||
push_constant,
|
||||
physical,
|
||||
};
|
||||
|
||||
pub const PointerType = struct {
|
||||
address_space: AddressSpace,
|
||||
pointee_type: TypeId,
|
||||
};
|
||||
|
||||
pub const ResourceKind = enum {
|
||||
uniform_buffer,
|
||||
storage_buffer,
|
||||
sampled_image,
|
||||
storage_image,
|
||||
sampler,
|
||||
};
|
||||
|
||||
pub const ResourceHandleType = struct {
|
||||
kind: ResourceKind,
|
||||
data_type: ?TypeId = null,
|
||||
};
|
||||
|
||||
pub const Type = union(enum) {
|
||||
void,
|
||||
boolean,
|
||||
integer: IntegerType,
|
||||
floating: FloatType,
|
||||
vector: VectorType,
|
||||
array: ArrayType,
|
||||
structure: StructureType,
|
||||
pointer: PointerType,
|
||||
resource_handle: ResourceHandleType,
|
||||
|
||||
pub fn eql(a: Type, b: Type) bool {
|
||||
return switch (a) {
|
||||
.void => b == .void,
|
||||
.boolean => b == .boolean,
|
||||
.integer => |value| switch (b) {
|
||||
.integer => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
.floating => |value| switch (b) {
|
||||
.floating => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
.vector => |value| switch (b) {
|
||||
.vector => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
.array => |value| switch (b) {
|
||||
.array => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
.structure => |value| switch (b) {
|
||||
.structure => |other| std.mem.eql(TypeId, value.members, other.members),
|
||||
else => false,
|
||||
},
|
||||
.pointer => |value| switch (b) {
|
||||
.pointer => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
.resource_handle => |value| switch (b) {
|
||||
.resource_handle => |other| std.meta.eql(value, other),
|
||||
else => false,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
const std = @import("std");
|
||||
const cfg = @import("../cfg.zig");
|
||||
const ids = @import("../id.zig");
|
||||
const module_ir = @import("../module.zig");
|
||||
|
||||
pub const Error = std.mem.Allocator.Error || error{
|
||||
InvalidBlock,
|
||||
DefinitionDoesNotDominateUse,
|
||||
};
|
||||
|
||||
const DominanceUseContext = struct {
|
||||
module: *const module_ir.Module,
|
||||
analysis: *const cfg,
|
||||
function_id: ids.FunctionId,
|
||||
use_block: ids.BlockId,
|
||||
use_index: usize,
|
||||
valid: bool = true,
|
||||
};
|
||||
|
||||
pub fn validate(module: *const module_ir.Module, function_id: ids.FunctionId) Error!void {
|
||||
var analysis = cfg.init(module.backingAllocator(), module, function_id) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
else => return error.InvalidBlock,
|
||||
};
|
||||
defer analysis.deinit();
|
||||
|
||||
const function = module.functions.get(function_id).?;
|
||||
for (function.blocks.items) |block_id| {
|
||||
const block = module.blocks.get(block_id).?;
|
||||
|
||||
for (block.instructions.items, 0..) |instruction_id, instruction_index| {
|
||||
const instruction = module.instructions.get(instruction_id).?;
|
||||
var context: DominanceUseContext = .{
|
||||
.module = module,
|
||||
.analysis = &analysis,
|
||||
.function_id = function_id,
|
||||
.use_block = block_id,
|
||||
.use_index = instruction_index,
|
||||
};
|
||||
|
||||
instruction.operation.visitValueUses(&context, checkDominanceUse);
|
||||
|
||||
if (!context.valid)
|
||||
return error.DefinitionDoesNotDominateUse;
|
||||
}
|
||||
|
||||
var context: DominanceUseContext = .{
|
||||
.module = module,
|
||||
.analysis = &analysis,
|
||||
.function_id = function_id,
|
||||
.use_block = block_id,
|
||||
.use_index = block.instructions.items.len,
|
||||
};
|
||||
|
||||
module_ir.visitTerminatorValueUses(block.terminator.?, &context, checkDominanceUse);
|
||||
|
||||
if (!context.valid)
|
||||
return error.DefinitionDoesNotDominateUse;
|
||||
}
|
||||
}
|
||||
|
||||
fn checkDominanceUse(context: *DominanceUseContext, value_id: ids.ValueId) void {
|
||||
if (!context.valid)
|
||||
return;
|
||||
|
||||
const value = context.module.values.get(value_id) orelse {
|
||||
context.valid = false;
|
||||
return;
|
||||
};
|
||||
|
||||
context.valid = switch (value.definition) {
|
||||
.constant, .undef => true,
|
||||
.function_parameter => |definition| definition.function == context.function_id,
|
||||
.block_parameter => |definition| context.analysis.dominates(definition.block, context.use_block),
|
||||
.instruction => |instruction_id| blk: {
|
||||
const definition = context.module.instructions.get(instruction_id) orelse break :blk false;
|
||||
|
||||
if (!context.analysis.dominates(definition.parent_block, context.use_block))
|
||||
break :blk false;
|
||||
|
||||
if (definition.parent_block != context.use_block)
|
||||
break :blk true;
|
||||
|
||||
const block = context.module.blocks.get(context.use_block) orelse break :blk false;
|
||||
for (block.instructions.items, 0..) |candidate, definition_index| {
|
||||
if (candidate == instruction_id)
|
||||
break :blk definition_index < context.use_index;
|
||||
}
|
||||
|
||||
break :blk false;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
const std = @import("std");
|
||||
const ids = @import("../id.zig");
|
||||
const type_ir = @import("../type.zig");
|
||||
const inst_ir = @import("../instruction.zig");
|
||||
const module_ir = @import("../module.zig");
|
||||
const dominance = @import("dominance.zig");
|
||||
|
||||
pub const ValidationError = error{
|
||||
MissingEntryPoint,
|
||||
InvalidEntryPoint,
|
||||
InvalidType,
|
||||
InvalidConstant,
|
||||
InvalidValue,
|
||||
InvalidFunction,
|
||||
InvalidBlock,
|
||||
InvalidInstruction,
|
||||
MissingFunctionEntryBlock,
|
||||
MissingTerminator,
|
||||
EntryBlockHasPredecessor,
|
||||
WrongParent,
|
||||
WrongDefinition,
|
||||
WrongParameterIndex,
|
||||
WrongResultPresence,
|
||||
WrongOperandType,
|
||||
WrongResultType,
|
||||
WrongBranchArgumentCount,
|
||||
WrongBranchArgumentType,
|
||||
CrossFunctionReference,
|
||||
WrongReturnType,
|
||||
WrongInterfaceDirection,
|
||||
InvalidStructuredControl,
|
||||
DefinitionDoesNotDominateUse,
|
||||
};
|
||||
|
||||
pub const Error = ValidationError || std.mem.Allocator.Error;
|
||||
|
||||
/// Early validator for the foundational IR. It covers object ownership, CFG
|
||||
/// edges, single definitions, function boundaries, and the currently modeled
|
||||
/// operation types, and SSA dominance.
|
||||
pub fn validate(module: *const module_ir.Module) Error!void {
|
||||
const entry_point = module.entry_point orelse return error.MissingEntryPoint;
|
||||
if (!module.functions.isLive(entry_point))
|
||||
return error.InvalidEntryPoint;
|
||||
|
||||
for (module.types.entries.items) |entry| {
|
||||
const ty = entry orelse continue;
|
||||
try validateType(module, ty);
|
||||
}
|
||||
|
||||
for (module.constants.entries.items) |entry| {
|
||||
const constant = entry orelse continue;
|
||||
|
||||
if (!module.types.isLive(constant.type))
|
||||
return error.InvalidType;
|
||||
|
||||
if (constant.value == .composite) {
|
||||
for (constant.value.composite) |element| {
|
||||
if (!module.constants.isLive(element))
|
||||
return error.InvalidConstant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (module.values.entries.items, 0..) |entry, value_index| {
|
||||
const value = entry orelse continue;
|
||||
|
||||
if (!module.types.isLive(value.type))
|
||||
return error.InvalidType;
|
||||
|
||||
const value_id = ids.ValueId.fromIndex(value_index);
|
||||
|
||||
switch (value.definition) {
|
||||
.constant => |id| {
|
||||
const constant = module.constants.get(id) orelse return error.InvalidConstant;
|
||||
if (constant.type != value.type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.function_parameter => |definition| {
|
||||
const function = module.functions.get(definition.function) orelse return error.InvalidFunction;
|
||||
if (definition.index >= function.parameters.items.len or function.parameters.items[definition.index] != value_id)
|
||||
return error.WrongParameterIndex;
|
||||
},
|
||||
.block_parameter => |definition| {
|
||||
const block = module.blocks.get(definition.block) orelse return error.InvalidBlock;
|
||||
if (definition.index >= block.parameters.items.len or block.parameters.items[definition.index] != value_id)
|
||||
return error.WrongParameterIndex;
|
||||
},
|
||||
.instruction => |instruction_id| {
|
||||
const instruction = module.instructions.get(instruction_id) orelse return error.InvalidInstruction;
|
||||
if (instruction.result != value_id)
|
||||
return error.WrongDefinition;
|
||||
},
|
||||
.undef => {},
|
||||
}
|
||||
}
|
||||
|
||||
for (module.interface_variables.entries.items) |entry| {
|
||||
const variable = entry orelse continue;
|
||||
if (!module.types.isLive(variable.type))
|
||||
return error.InvalidType;
|
||||
}
|
||||
|
||||
for (module.resources.entries.items) |entry| {
|
||||
const resource = entry orelse continue;
|
||||
if (!module.types.isLive(resource.type))
|
||||
return error.InvalidType;
|
||||
}
|
||||
|
||||
for (module.functions.entries.items, 0..) |entry, function_index| {
|
||||
const function = entry orelse continue;
|
||||
const function_id = ids.FunctionId.fromIndex(function_index);
|
||||
|
||||
if (!module.types.isLive(function.return_type))
|
||||
return error.InvalidType;
|
||||
|
||||
if (function.parameter_types.items.len != function.parameters.items.len)
|
||||
return error.WrongParameterIndex;
|
||||
|
||||
for (function.parameter_types.items, function.parameters.items, 0..) |parameter_type, parameter_id, index| {
|
||||
const parameter = module.values.get(parameter_id) orelse return error.InvalidValue;
|
||||
|
||||
if (parameter.type != parameter_type)
|
||||
return error.WrongResultType;
|
||||
|
||||
if (parameter.definition != .function_parameter or
|
||||
parameter.definition.function_parameter.function != function_id or
|
||||
parameter.definition.function_parameter.index != index)
|
||||
return error.WrongDefinition;
|
||||
}
|
||||
|
||||
const entry_block = function.entry_block orelse return error.MissingFunctionEntryBlock;
|
||||
const entry_block_value = module.blocks.get(entry_block) orelse return error.InvalidBlock;
|
||||
if (entry_block_value.parent_function != function_id) return error.WrongParent;
|
||||
|
||||
for (function.blocks.items) |block_id| {
|
||||
const block = module.blocks.get(block_id) orelse return error.InvalidBlock;
|
||||
|
||||
if (block.parent_function != function_id)
|
||||
return error.WrongParent;
|
||||
|
||||
try validateBlock(module, function_id, block_id, block);
|
||||
}
|
||||
|
||||
for (function.blocks.items) |block_id| {
|
||||
const block = module.blocks.get(block_id).?;
|
||||
|
||||
if (block.terminator) |terminator| {
|
||||
if (targetsBlock(terminator, entry_block))
|
||||
return error.EntryBlockHasPredecessor;
|
||||
}
|
||||
}
|
||||
|
||||
try dominance.validate(module, function_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn validateType(module: *const module_ir.Module, ty: type_ir.Type) ValidationError!void {
|
||||
switch (ty) {
|
||||
.vector => |vector| {
|
||||
if (!module.types.isLive(vector.element_type) or vector.length < 2)
|
||||
return error.InvalidType;
|
||||
},
|
||||
.array => |array| {
|
||||
if (!module.types.isLive(array.element_type) or array.length == 0)
|
||||
return error.InvalidType;
|
||||
},
|
||||
.structure => |structure| for (structure.members) |member| {
|
||||
if (!module.types.isLive(member))
|
||||
return error.InvalidType;
|
||||
},
|
||||
.pointer => |pointer| {
|
||||
if (!module.types.isLive(pointer.pointee_type))
|
||||
return error.InvalidType;
|
||||
},
|
||||
.resource_handle => |handle| if (handle.data_type) |data_type| {
|
||||
if (!module.types.isLive(data_type))
|
||||
return error.InvalidType;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn validateBlock(
|
||||
module: *const module_ir.Module,
|
||||
function_id: ids.FunctionId,
|
||||
block_id: ids.BlockId,
|
||||
block: *const module_ir.Block,
|
||||
) ValidationError!void {
|
||||
for (block.parameters.items, 0..) |parameter_id, index| {
|
||||
const parameter = module.values.get(parameter_id) orelse return error.InvalidValue;
|
||||
if (parameter.definition != .block_parameter or
|
||||
parameter.definition.block_parameter.block != block_id or
|
||||
parameter.definition.block_parameter.index != index)
|
||||
return error.WrongDefinition;
|
||||
}
|
||||
|
||||
switch (block.structured_control) {
|
||||
.none => {},
|
||||
.selection => |selection| try validateTarget(module, function_id, selection.merge_block),
|
||||
.loop => |loop| {
|
||||
try validateTarget(module, function_id, loop.merge_block);
|
||||
try validateTarget(module, function_id, loop.continue_block);
|
||||
},
|
||||
}
|
||||
|
||||
for (block.instructions.items) |instruction_id| {
|
||||
const instruction = module.instructions.get(instruction_id) orelse return error.InvalidInstruction;
|
||||
|
||||
if (instruction.parent_block != block_id)
|
||||
return error.WrongParent;
|
||||
|
||||
if (instruction.result) |result_id| {
|
||||
const result = module.values.get(result_id) orelse return error.InvalidValue;
|
||||
|
||||
if (result.definition != .instruction or result.definition.instruction != instruction_id)
|
||||
return error.WrongDefinition;
|
||||
}
|
||||
|
||||
try validateOperation(module, function_id, instruction);
|
||||
}
|
||||
|
||||
const terminator = block.terminator orelse return error.MissingTerminator;
|
||||
try validateTerminator(module, function_id, terminator);
|
||||
}
|
||||
|
||||
fn validateOperation(module: *const module_ir.Module, function_id: ids.FunctionId, instruction: *const inst_ir.Instruction) ValidationError!void {
|
||||
const result_type = if (instruction.result) |result| module.typeOf(result) orelse return error.InvalidValue else null;
|
||||
|
||||
switch (instruction.operation) {
|
||||
.unary => |op| {
|
||||
const operand_type = try operandType(module, function_id, op.operand);
|
||||
|
||||
if (result_type == null)
|
||||
return error.WrongResultPresence;
|
||||
|
||||
if (result_type.? != operand_type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.binary => |op| {
|
||||
const lhs_type = try operandType(module, function_id, op.lhs);
|
||||
const rhs_type = try operandType(module, function_id, op.rhs);
|
||||
|
||||
if (lhs_type != rhs_type)
|
||||
return error.WrongOperandType;
|
||||
|
||||
if (result_type == null or result_type.? != lhs_type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.compare => |op| {
|
||||
const lhs_type = try operandType(module, function_id, op.lhs);
|
||||
if (try operandType(module, function_id, op.rhs) != lhs_type)
|
||||
return error.WrongOperandType;
|
||||
|
||||
const result = result_type orelse return error.WrongResultPresence;
|
||||
if (!isBoolean(module, result))
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.select => |op| {
|
||||
if (!isBoolean(module, try operandType(module, function_id, op.condition)))
|
||||
return error.WrongOperandType;
|
||||
|
||||
const true_type = try operandType(module, function_id, op.true_value);
|
||||
if (try operandType(module, function_id, op.false_value) != true_type)
|
||||
return error.WrongOperandType;
|
||||
|
||||
if (result_type == null or result_type.? != true_type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.bitcast => |operand| {
|
||||
_ = try operandType(module, function_id, operand);
|
||||
if (result_type == null)
|
||||
return error.WrongResultPresence;
|
||||
},
|
||||
.composite_construct => |op| {
|
||||
const result = result_type orelse return error.WrongResultPresence;
|
||||
const ty = module.types.get(result) orelse return error.InvalidType;
|
||||
|
||||
switch (ty.*) {
|
||||
.vector => |vector| {
|
||||
if (op.elements.len != vector.length)
|
||||
return error.WrongOperandType;
|
||||
|
||||
for (op.elements) |element| {
|
||||
if (try operandType(module, function_id, element) != vector.element_type)
|
||||
return error.WrongOperandType;
|
||||
}
|
||||
},
|
||||
.structure => |structure| {
|
||||
if (op.elements.len != structure.members.len)
|
||||
return error.WrongOperandType;
|
||||
|
||||
for (op.elements, structure.members) |element, member_type| {
|
||||
if (try operandType(module, function_id, element) != member_type)
|
||||
return error.WrongOperandType;
|
||||
}
|
||||
},
|
||||
else => return error.WrongResultType,
|
||||
}
|
||||
},
|
||||
.composite_extract => |op| {
|
||||
const composite_type = try operandType(module, function_id, op.composite);
|
||||
const extracted_type = try indexedType(module, composite_type, op.indices);
|
||||
|
||||
if (result_type == null or result_type.? != extracted_type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.load_interface => |op| {
|
||||
const variable = module.interface_variables.get(op.variable) orelse return error.InvalidValue;
|
||||
|
||||
if (variable.direction != .input)
|
||||
return error.WrongInterfaceDirection;
|
||||
|
||||
if (op.element_index) |index|
|
||||
_ = try operandType(module, function_id, index);
|
||||
|
||||
if (result_type == null or result_type.? != variable.type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
.store_interface => |op| {
|
||||
if (result_type != null)
|
||||
return error.WrongResultPresence;
|
||||
|
||||
const variable = module.interface_variables.get(op.variable) orelse return error.InvalidValue;
|
||||
|
||||
if (variable.direction != .output)
|
||||
return error.WrongInterfaceDirection;
|
||||
|
||||
if (try operandType(module, function_id, op.value) != variable.type)
|
||||
return error.WrongOperandType;
|
||||
|
||||
if (op.element_index) |index|
|
||||
_ = try operandType(module, function_id, index);
|
||||
},
|
||||
.call => |op| {
|
||||
const callee = module.functions.get(op.function) orelse return error.InvalidFunction;
|
||||
|
||||
if (op.arguments.len != callee.parameter_types.items.len)
|
||||
return error.WrongOperandType;
|
||||
|
||||
for (op.arguments, callee.parameter_types.items) |argument, parameter_type| {
|
||||
if (try operandType(module, function_id, argument) != parameter_type)
|
||||
return error.WrongOperandType;
|
||||
}
|
||||
|
||||
const return_type = module.types.get(callee.return_type) orelse return error.InvalidType;
|
||||
|
||||
if (return_type.* == .void) {
|
||||
if (result_type != null)
|
||||
return error.WrongResultPresence;
|
||||
} else if (result_type == null or result_type.? != callee.return_type)
|
||||
return error.WrongResultType;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn validateTerminator(module: *const module_ir.Module, function_id: ids.FunctionId, terminator: module_ir.Terminator) ValidationError!void {
|
||||
const function = module.functions.get(function_id) orelse return error.InvalidFunction;
|
||||
|
||||
switch (terminator) {
|
||||
.branch => |edge| try validateEdge(module, function_id, edge),
|
||||
.conditional_branch => |branch| {
|
||||
if (!isBoolean(module, try operandType(module, function_id, branch.condition)))
|
||||
return error.WrongOperandType;
|
||||
|
||||
try validateEdge(module, function_id, branch.true_edge);
|
||||
try validateEdge(module, function_id, branch.false_edge);
|
||||
},
|
||||
.return_void => {
|
||||
if (module.types.get(function.return_type).?.* != .void)
|
||||
return error.WrongReturnType;
|
||||
},
|
||||
.return_value => |value| {
|
||||
if (try operandType(module, function_id, value) != function.return_type)
|
||||
return error.WrongReturnType;
|
||||
},
|
||||
.discard => {
|
||||
if (module.stage != .fragment)
|
||||
return error.WrongReturnType;
|
||||
},
|
||||
.@"unreachable" => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn validateEdge(module: *const module_ir.Module, function_id: ids.FunctionId, edge: module_ir.Edge) ValidationError!void {
|
||||
const target = module.blocks.get(edge.target) orelse return error.InvalidBlock;
|
||||
|
||||
if (target.parent_function != function_id)
|
||||
return error.CrossFunctionReference;
|
||||
|
||||
if (edge.arguments.len != target.parameters.items.len)
|
||||
return error.WrongBranchArgumentCount;
|
||||
|
||||
for (edge.arguments, target.parameters.items) |argument, parameter| {
|
||||
if (try operandType(module, function_id, argument) != module.typeOf(parameter).?)
|
||||
return error.WrongBranchArgumentType;
|
||||
}
|
||||
}
|
||||
|
||||
fn validateTarget(module: *const module_ir.Module, function_id: ids.FunctionId, target_id: ids.BlockId) ValidationError!void {
|
||||
const target = module.blocks.get(target_id) orelse return error.InvalidStructuredControl;
|
||||
if (target.parent_function != function_id)
|
||||
return error.InvalidStructuredControl;
|
||||
}
|
||||
|
||||
fn operandType(module: *const module_ir.Module, function_id: ids.FunctionId, value_id: ids.ValueId) ValidationError!ids.TypeId {
|
||||
const value = module.values.get(value_id) orelse return error.InvalidValue;
|
||||
const owner = valueFunction(module, value_id) catch return error.InvalidValue;
|
||||
|
||||
if (owner) |actual| {
|
||||
if (actual != function_id)
|
||||
return error.CrossFunctionReference;
|
||||
}
|
||||
return value.type;
|
||||
}
|
||||
|
||||
fn valueFunction(module: *const module_ir.Module, value_id: ids.ValueId) ValidationError!?ids.FunctionId {
|
||||
const value = module.values.get(value_id) orelse return error.InvalidValue;
|
||||
|
||||
return switch (value.definition) {
|
||||
.constant, .undef => null,
|
||||
.function_parameter => |definition| definition.function,
|
||||
.block_parameter => |definition| (module.blocks.get(definition.block) orelse return error.InvalidBlock).parent_function,
|
||||
.instruction => |instruction_id| blk: {
|
||||
const instruction = module.instructions.get(instruction_id) orelse return error.InvalidInstruction;
|
||||
const block = module.blocks.get(instruction.parent_block) orelse return error.InvalidBlock;
|
||||
break :blk block.parent_function;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn indexedType(module: *const module_ir.Module, root: ids.TypeId, indices: []const u32) ValidationError!ids.TypeId {
|
||||
if (indices.len == 0)
|
||||
return error.WrongOperandType;
|
||||
|
||||
var current = root;
|
||||
|
||||
for (indices) |index| {
|
||||
const ty = module.types.get(current) orelse return error.InvalidType;
|
||||
current = switch (ty.*) {
|
||||
.vector => |vector| if (index < vector.length)
|
||||
vector.element_type
|
||||
else
|
||||
return error.WrongOperandType,
|
||||
.array => |array| if (index < array.length)
|
||||
array.element_type
|
||||
else
|
||||
return error.WrongOperandType,
|
||||
.structure => |structure| if (index < structure.members.len)
|
||||
structure.members[index]
|
||||
else
|
||||
return error.WrongOperandType,
|
||||
|
||||
else => return error.WrongOperandType,
|
||||
};
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
fn isBoolean(module: *const module_ir.Module, type_id: ids.TypeId) bool {
|
||||
const ty = module.types.get(type_id) orelse return false;
|
||||
return ty.* == .boolean;
|
||||
}
|
||||
|
||||
fn targetsBlock(terminator: module_ir.Terminator, target: ids.BlockId) bool {
|
||||
return switch (terminator) {
|
||||
.branch => |edge| edge.target == target,
|
||||
.conditional_branch => |branch| branch.true_edge.target == target or branch.false_edge.target == target,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const ids = @import("id.zig");
|
||||
|
||||
pub const TypeId = ids.TypeId;
|
||||
pub const ConstantId = ids.ConstantId;
|
||||
pub const InstructionId = ids.InstructionId;
|
||||
pub const FunctionId = ids.FunctionId;
|
||||
pub const BlockId = ids.BlockId;
|
||||
|
||||
pub const FunctionParameterDefinition = struct {
|
||||
function: FunctionId,
|
||||
index: u32,
|
||||
};
|
||||
|
||||
pub const BlockParameterDefinition = struct {
|
||||
block: BlockId,
|
||||
index: u32,
|
||||
};
|
||||
|
||||
pub const Definition = union(enum) {
|
||||
constant: ConstantId,
|
||||
function_parameter: FunctionParameterDefinition,
|
||||
block_parameter: BlockParameterDefinition,
|
||||
instruction: InstructionId,
|
||||
undef,
|
||||
};
|
||||
|
||||
pub const Value = struct {
|
||||
type: TypeId,
|
||||
definition: Definition,
|
||||
name: ?[]const u8 = null,
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
const ids = @import("id.zig");
|
||||
const instruction_ir = @import("instruction.zig");
|
||||
const module_ir = @import("module.zig");
|
||||
|
||||
pub const Visitor = struct {
|
||||
context: ?*anyopaque = null,
|
||||
visitInterfaceVariable: ?*const fn (?*anyopaque, ids.InterfaceVariableId, *const module_ir.InterfaceVariable) anyerror!void = null,
|
||||
visitResource: ?*const fn (?*anyopaque, ids.ResourceId, *const module_ir.Resource) anyerror!void = null,
|
||||
visitFunction: ?*const fn (?*anyopaque, ids.FunctionId, *const module_ir.Function) anyerror!void = null,
|
||||
visitBlock: ?*const fn (?*anyopaque, ids.BlockId, *const module_ir.Block) anyerror!void = null,
|
||||
visitInstruction: ?*const fn (?*anyopaque, ids.InstructionId, *const instruction_ir.Instruction) anyerror!void = null,
|
||||
visitTerminator: ?*const fn (?*anyopaque, ids.BlockId, module_ir.Terminator) anyerror!void = null,
|
||||
visitValueUse: ?*const fn (?*anyopaque, ids.BlockId, ?ids.InstructionId, ids.ValueId) anyerror!void = null,
|
||||
};
|
||||
|
||||
const UseContext = struct {
|
||||
visitor_context: ?*anyopaque,
|
||||
callback: *const fn (?*anyopaque, ids.BlockId, ?ids.InstructionId, ids.ValueId) anyerror!void,
|
||||
block: ids.BlockId,
|
||||
instruction: ?ids.InstructionId,
|
||||
failure: ?anyerror = null,
|
||||
};
|
||||
|
||||
pub fn walk(module: *const module_ir.Module, visitor: Visitor) !void {
|
||||
for (module.interface_variables.entries.items, 0..) |entry, index| {
|
||||
const variable = entry orelse continue;
|
||||
if (visitor.visitInterfaceVariable) |callback|
|
||||
try callback(visitor.context, ids.InterfaceVariableId.fromIndex(index), &variable);
|
||||
}
|
||||
|
||||
for (module.resources.entries.items, 0..) |entry, index| {
|
||||
const resource = entry orelse continue;
|
||||
if (visitor.visitResource) |callback|
|
||||
try callback(visitor.context, ids.ResourceId.fromIndex(index), &resource);
|
||||
}
|
||||
|
||||
for (module.functions.entries.items, 0..) |entry, function_index| {
|
||||
const function = entry orelse continue;
|
||||
const function_id = ids.FunctionId.fromIndex(function_index);
|
||||
|
||||
if (visitor.visitFunction) |callback|
|
||||
try callback(visitor.context, function_id, &function);
|
||||
|
||||
for (function.blocks.items) |block_id| {
|
||||
const block = module.blocks.get(block_id) orelse continue;
|
||||
|
||||
if (visitor.visitBlock) |callback|
|
||||
try callback(visitor.context, block_id, block);
|
||||
|
||||
for (block.instructions.items) |instruction_id| {
|
||||
const instruction = module.instructions.get(instruction_id) orelse continue;
|
||||
|
||||
if (visitor.visitInstruction) |callback|
|
||||
try callback(visitor.context, instruction_id, instruction);
|
||||
|
||||
if (visitor.visitValueUse) |callback| {
|
||||
var context: UseContext = .{
|
||||
.visitor_context = visitor.context,
|
||||
.callback = callback,
|
||||
.block = block_id,
|
||||
.instruction = instruction_id,
|
||||
};
|
||||
instruction.operation.visitValueUses(&context, visitUse);
|
||||
|
||||
if (context.failure) |failure|
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
|
||||
if (block.terminator) |terminator| {
|
||||
if (visitor.visitTerminator) |callback|
|
||||
try callback(visitor.context, block_id, terminator);
|
||||
|
||||
if (visitor.visitValueUse) |callback| {
|
||||
var context: UseContext = .{
|
||||
.visitor_context = visitor.context,
|
||||
.callback = callback,
|
||||
.block = block_id,
|
||||
.instruction = null,
|
||||
};
|
||||
module_ir.visitTerminatorValueUses(terminator, &context, visitUse);
|
||||
|
||||
if (context.failure) |failure|
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visitUse(context: *UseContext, value: ids.ValueId) void {
|
||||
if (context.failure != null)
|
||||
return;
|
||||
|
||||
context.callback(context.visitor_context, context.block, context.instruction, value) catch |err| {
|
||||
context.failure = err;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,826 @@
|
||||
//! ## Shader compiler infrastructure.
|
||||
//!
|
||||
//! This module exposes the project-specific intermediate representation in
|
||||
//! `ir` and the SPIR-V frontend in `spirv`.
|
||||
//!
|
||||
//! Together they form
|
||||
//! the first stage of the compiler pipeline: SPIR-V binary modules are decoded,
|
||||
//! translated into a smaller and easier-to-transform IR, validated, and then made
|
||||
//! available to later optimization or code-generation passes.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const ir = @import("ir/ir.zig");
|
||||
pub const spirv = @import("spirv/root.zig");
|
||||
|
||||
const VisitorStatistics = struct {
|
||||
functions: usize = 0,
|
||||
blocks: usize = 0,
|
||||
};
|
||||
|
||||
test "IR builder generation" {
|
||||
// shader vertex @main
|
||||
// {
|
||||
// @color: vec4[f32] = input[location(0), component(0), index(0)]
|
||||
// @out_color: vec4[f32] = output[location(0), component(0), index(0)]
|
||||
// %0: constant bool = true
|
||||
// %1: constant f32 = bits(0x3f800000)
|
||||
//
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// %3: vec4[f32] = load_interface @color
|
||||
// conditional_branch %0, .pass(), .merge(%3)
|
||||
//
|
||||
// .pass():
|
||||
// %4: vec4[f32] = composite_construct %1, %1, %1, %1
|
||||
// branch .merge(%4)
|
||||
//
|
||||
// .merge(%2: vec4[f32]):
|
||||
// store_interface @out_color, %2
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .vertex);
|
||||
defer module.deinit();
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
const bool_type = try builder.internType(.boolean);
|
||||
const f32_type = try builder.internType(.{ .floating = .{ .bits = 32 } });
|
||||
const duplicate_f32 = try builder.internType(.{ .floating = .{ .bits = 32 } });
|
||||
try std.testing.expectEqual(f32_type, duplicate_f32);
|
||||
const vec4_type = try builder.internType(.{ .vector = .{ .element_type = f32_type, .length = 4 } });
|
||||
|
||||
const true_value = try builder.internConstant(bool_type, .{ .boolean = true });
|
||||
const one = try builder.internConstant(f32_type, .{ .float_bits = @as(u32, @bitCast(@as(f32, 1.0))) });
|
||||
|
||||
const input = try builder.addInterfaceVariable(vec4_type, .input, .{ .location = .{ .location = 0 } }, "color");
|
||||
const output = try builder.addInterfaceVariable(vec4_type, .output, .{ .location = .{ .location = 0 } }, "out_color");
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
const pass = try builder.addBlock(main, "pass");
|
||||
const merge = try builder.addBlock(main, "merge");
|
||||
const merged = try builder.addBlockParameter(merge, vec4_type, "merged");
|
||||
|
||||
const loaded = (try builder.appendInstruction(entry, vec4_type, .{
|
||||
.load_interface = .{ .variable = input },
|
||||
}, "loaded")).?;
|
||||
try builder.setTerminator(entry, .{ .conditional_branch = .{
|
||||
.condition = true_value,
|
||||
.true_edge = try builder.edge(pass, &.{}),
|
||||
.false_edge = try builder.edge(merge, &.{loaded}),
|
||||
} });
|
||||
|
||||
const splat = (try builder.appendInstruction(pass, vec4_type, .{
|
||||
.composite_construct = .{ .elements = &.{ one, one, one, one } },
|
||||
}, "white")).?;
|
||||
try builder.setTerminator(pass, .{ .branch = try builder.edge(merge, &.{splat}) });
|
||||
_ = try builder.appendInstruction(merge, null, .{
|
||||
.store_interface = .{ .variable = output, .value = merged },
|
||||
}, null);
|
||||
try builder.setTerminator(merge, .return_void);
|
||||
|
||||
try ir.validator.validate(&module);
|
||||
|
||||
var control_flow = try ir.cfg.init(std.testing.allocator, &module, main);
|
||||
defer control_flow.deinit();
|
||||
try std.testing.expectEqual(@as(usize, 2), control_flow.predecessors(merge).?.len);
|
||||
try std.testing.expect(control_flow.dominates(entry, merge));
|
||||
try std.testing.expect(!control_flow.dominates(pass, merge));
|
||||
|
||||
const text = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
|
||||
defer std.testing.allocator.free(text);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "shader vertex @main") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "@color: vec4[f32] = input[location(0), component(0), index(0)]") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "@out_color: vec4[f32] = output[location(0), component(0), index(0)]") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "conditional_branch %0, .pass(), .merge(%loaded)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, ".merge(%merged: vec4[f32])") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "store_interface @out_color, %merged") != null);
|
||||
|
||||
var parsed = try ir.parser.parseString(std.testing.allocator, text);
|
||||
defer parsed.deinit();
|
||||
const round_trip = try ir.printer.allocPrint(std.testing.allocator, &parsed);
|
||||
defer std.testing.allocator.free(round_trip);
|
||||
try std.testing.expectEqualStrings(text, round_trip);
|
||||
|
||||
const io = std.Options.debug_io;
|
||||
const path = ".zig-cache/ir-parser-round-trip.ir";
|
||||
const file = try std.Io.Dir.cwd().createFile(io, path, .{ .truncate = true });
|
||||
{
|
||||
defer file.close(io);
|
||||
var file_buffer: [4096]u8 = @splat(0);
|
||||
var file_writer = file.writer(io, &file_buffer);
|
||||
try file_writer.interface.writeAll(text);
|
||||
try file_writer.interface.flush();
|
||||
}
|
||||
defer std.Io.Dir.cwd().deleteFile(io, path) catch @panic("Caught an error while handling an error");
|
||||
|
||||
var parsed_file = try ir.parser.parseFile(std.testing.allocator, io, path);
|
||||
defer parsed_file.deinit();
|
||||
const file_round_trip = try ir.printer.allocPrint(std.testing.allocator, &parsed_file);
|
||||
defer std.testing.allocator.free(file_round_trip);
|
||||
try std.testing.expectEqualStrings(text, file_round_trip);
|
||||
}
|
||||
|
||||
test "IR parse interface" {
|
||||
const source =
|
||||
\\ shader vertex @main
|
||||
\\ {
|
||||
\\ @in_color: vec4[f32] = input[location(0), component(0), index(0)]
|
||||
\\ @out_color: vec4[f32] = output[location(0), component(0), index(0)]
|
||||
\\ @position: vec4[f32] = output[builtin(position)]
|
||||
\\
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ return
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
|
||||
var module = try ir.parser.parseString(std.testing.allocator, source);
|
||||
defer module.deinit();
|
||||
const printed = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(printed);
|
||||
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "@in_color: vec4[f32] = input[location(0), component(0), index(0)]") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "@out_color: vec4[f32] = output[location(0), component(0), index(0)]") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "@position: vec4[f32] = output[builtin(position)]") != null);
|
||||
}
|
||||
|
||||
test "IR parse types, operations, calls, terminators" {
|
||||
const source =
|
||||
\\ shader fragment @main
|
||||
\\ {
|
||||
\\ %0: constant bool = true
|
||||
\\ %1: constant u32 = bits(0x1)
|
||||
\\ %2: constant u32 = bits(0x2)
|
||||
\\ %3: constant f32 = bits(0x3f800000)
|
||||
\\ %4: constant array[u32, 2] = [#1, #2]
|
||||
\\ %5: constant struct[u32, u32] = [#1, #2]
|
||||
\\ %6: constant ptr[private, u32] = null
|
||||
\\ %7: constant resourceHandle[sampler] = null
|
||||
\\
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ %9: u32 = bitwise_not %1
|
||||
\\ %10: u32 = integer_add %9, %2
|
||||
\\ %11: bool = cmp_equal %1, %2
|
||||
\\ %12: u32 = select %11, %1, %2
|
||||
\\ %13: u32 = bitcast %12
|
||||
\\ %14: vec2[u32] = composite_construct %1, %2
|
||||
\\ %15: u32 = composite_extract %14[0]
|
||||
\\ %16: f32 = negate %3
|
||||
\\ %17: f32 = float_add %3, %16
|
||||
\\ %18: u32 = call @helper(%15)
|
||||
\\ return
|
||||
\\ }
|
||||
\\
|
||||
\\ fn @helper(%8: u32) -> u32
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ return %8
|
||||
\\ }
|
||||
\\
|
||||
\\ fn @discarder() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ discard
|
||||
\\ }
|
||||
\\
|
||||
\\ fn @dead() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ unreachable
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
|
||||
var module = try ir.parser.parseString(std.testing.allocator, source);
|
||||
defer module.deinit();
|
||||
const printed = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(printed);
|
||||
|
||||
var reparsed = try ir.parser.parseString(std.testing.allocator, printed);
|
||||
defer reparsed.deinit();
|
||||
const printed_again = try ir.printer.allocPrint(std.testing.allocator, &reparsed);
|
||||
defer std.testing.allocator.free(printed_again);
|
||||
try std.testing.expectEqualStrings(printed, printed_again);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "cmp_equal %1, %2") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "cmp.") == null);
|
||||
}
|
||||
|
||||
test "IR parse named value IDs" {
|
||||
const source =
|
||||
\\ shader compute @main
|
||||
\\ {
|
||||
\\ %one_value: constant u32 = bits(0x1)
|
||||
\\
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ %sum_value: u32 = integer_add %one_value, %one_value
|
||||
\\ branch .merge(%sum_value)
|
||||
\\
|
||||
\\ .merge(%merged_value: u32):
|
||||
\\ %product_value: u32 = integer_multiply %merged_value, %one_value
|
||||
\\ return
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
|
||||
var module = try ir.parser.parseString(std.testing.allocator, source);
|
||||
defer module.deinit();
|
||||
const printed = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(printed);
|
||||
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%one_value: constant u32 = bits(0x1)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%sum_value: u32 = integer_add %one_value, %one_value") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "branch .merge(%sum_value)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, ".merge(%merged_value: u32)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%product_value: u32 = integer_multiply %merged_value, %one_value") != null);
|
||||
|
||||
var reparsed = try ir.parser.parseString(std.testing.allocator, printed);
|
||||
defer reparsed.deinit();
|
||||
const printed_again = try ir.printer.allocPrint(std.testing.allocator, &reparsed);
|
||||
defer std.testing.allocator.free(printed_again);
|
||||
try std.testing.expectEqualStrings(printed, printed_again);
|
||||
}
|
||||
|
||||
test "IR parse numeric constants" {
|
||||
const source =
|
||||
\\ shader compute @main
|
||||
\\ {
|
||||
\\ %0: constant u8 = 255
|
||||
\\ %1: constant i8 = -1
|
||||
\\ %2: constant f16 = 1.5
|
||||
\\ %3: constant f32 = -0.0
|
||||
\\ %4: constant f64 = 2.5e0
|
||||
\\
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ return
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
|
||||
var module = try ir.parser.parseString(std.testing.allocator, source);
|
||||
defer module.deinit();
|
||||
const printed = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(printed);
|
||||
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%0: constant u8 = bits(0xff)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%1: constant i8 = bits(0xff)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%2: constant f16 = bits(0x3e00)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%3: constant f32 = bits(0x80000000)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, printed, "%4: constant f64 = bits(0x4004000000000000)") != null);
|
||||
|
||||
const out_of_range =
|
||||
\\ shader compute @main
|
||||
\\ {
|
||||
\\ %0: constant u8 = 256
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ return
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
try std.testing.expectError(error.InvalidNumber, ir.parser.parseString(std.testing.allocator, out_of_range));
|
||||
}
|
||||
|
||||
test "IR parser error: unknown value" {
|
||||
const source =
|
||||
\\ shader compute @main
|
||||
\\ {
|
||||
\\ fn @main() -> void
|
||||
\\ {
|
||||
\\ .entry():
|
||||
\\ return %99
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
try std.testing.expectError(error.UnknownValue, ir.parser.parseString(std.testing.allocator, source));
|
||||
}
|
||||
|
||||
test "Validator error: wrong block argument count" {
|
||||
// shader compute @main
|
||||
// {
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// branch .merge()
|
||||
//
|
||||
// .merge(%0: u32):
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .compute);
|
||||
defer module.deinit();
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
const u32_type = try builder.internType(.{ .integer = .{ .bits = 32, .signedness = .unsigned } });
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
const merge = try builder.addBlock(main, "merge");
|
||||
_ = try builder.addBlockParameter(merge, u32_type, null);
|
||||
try builder.setTerminator(entry, .{ .branch = try builder.edge(merge, &.{}) });
|
||||
try builder.setTerminator(merge, .return_void);
|
||||
|
||||
try std.testing.expectError(error.WrongBranchArgumentCount, ir.validator.validate(&module));
|
||||
}
|
||||
|
||||
test "Central store IDs disposal" {
|
||||
var module = ir.module.Module.init(std.testing.allocator, .fragment);
|
||||
defer module.deinit();
|
||||
const first = try module.internType(.boolean);
|
||||
try std.testing.expect(module.types.remove(first));
|
||||
const second = try module.internType(.boolean);
|
||||
try std.testing.expect(first.index() != second.index());
|
||||
try std.testing.expect(module.types.get(first) == null);
|
||||
}
|
||||
|
||||
test "Validator error: SSA definition does not dominate its use" {
|
||||
// shader compute @main
|
||||
// {
|
||||
// %0: constant bool = true
|
||||
// %1: constant u32 = bits(0x1)
|
||||
//
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// conditional_branch %0, .left(), .right()
|
||||
//
|
||||
// .left():
|
||||
// %2: u32 = integer_add %1, %1
|
||||
// branch .merge()
|
||||
//
|
||||
// .right():
|
||||
// branch .merge()
|
||||
//
|
||||
// .merge():
|
||||
// %3: u32 = integer_multiply %2, %1
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .compute);
|
||||
defer module.deinit();
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
const bool_type = try builder.internType(.boolean);
|
||||
const u32_type = try builder.internType(.{ .integer = .{ .bits = 32, .signedness = .unsigned } });
|
||||
const condition = try builder.internConstant(bool_type, .{ .boolean = true });
|
||||
const one = try builder.internConstant(u32_type, .{ .integer_bits = 1 });
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
const left = try builder.addBlock(main, "left");
|
||||
const right = try builder.addBlock(main, "right");
|
||||
const merge = try builder.addBlock(main, "merge");
|
||||
|
||||
try builder.setTerminator(
|
||||
entry,
|
||||
.{
|
||||
.conditional_branch = .{
|
||||
.condition = condition,
|
||||
.true_edge = try builder.edge(left, &.{}),
|
||||
.false_edge = try builder.edge(right, &.{}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const left_value = (try builder.appendInstruction(left, u32_type, .{
|
||||
.binary = .{
|
||||
.opcode = .integer_add,
|
||||
.lhs = one,
|
||||
.rhs = one,
|
||||
},
|
||||
}, null)).?;
|
||||
|
||||
try builder.setTerminator(left, .{ .branch = try builder.edge(merge, &.{}) });
|
||||
try builder.setTerminator(right, .{ .branch = try builder.edge(merge, &.{}) });
|
||||
|
||||
_ = try builder.appendInstruction(merge, u32_type, .{
|
||||
.binary = .{
|
||||
.opcode = .integer_multiply,
|
||||
.lhs = left_value,
|
||||
.rhs = one,
|
||||
},
|
||||
}, null);
|
||||
|
||||
try builder.setTerminator(merge, .return_void);
|
||||
|
||||
try std.testing.expectError(error.DefinitionDoesNotDominateUse, ir.validator.validate(&module));
|
||||
}
|
||||
|
||||
test "Rewriter replace all ID uses, safely erase dead instruction" {
|
||||
// shader compute @main
|
||||
// {
|
||||
// %0: constant u32 = bits(0x1)
|
||||
// %1: constant u32 = bits(0x2)
|
||||
//
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// %2: u32 = integer_add %0, %1
|
||||
// %3: u32 = integer_multiply %2, %1
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .compute);
|
||||
defer module.deinit();
|
||||
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
const u32_type = try builder.internType(.{ .integer = .{ .bits = 32, .signedness = .unsigned } });
|
||||
|
||||
const one = try builder.internConstant(u32_type, .{ .integer_bits = 1 });
|
||||
const two = try builder.internConstant(u32_type, .{ .integer_bits = 2 });
|
||||
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
const sum = (try builder.appendInstruction(entry, u32_type, .{
|
||||
.binary = .{
|
||||
.opcode = .integer_add,
|
||||
.lhs = one,
|
||||
.rhs = two,
|
||||
},
|
||||
}, null)).?;
|
||||
_ = try builder.appendInstruction(entry, u32_type, .{
|
||||
.binary = .{
|
||||
.opcode = .integer_multiply,
|
||||
.lhs = sum,
|
||||
.rhs = two,
|
||||
},
|
||||
}, null);
|
||||
try builder.setTerminator(entry, .return_void);
|
||||
|
||||
try ir.validator.validate(&module);
|
||||
|
||||
const sum_instruction = module.values.get(sum).?.definition.instruction;
|
||||
var rewriter = ir.Rewriter.init(&module);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), try rewriter.replaceAllUses(sum, one));
|
||||
try rewriter.eraseInstruction(sum_instruction);
|
||||
|
||||
try std.testing.expect(module.values.get(sum) == null);
|
||||
try std.testing.expect(module.instructions.get(sum_instruction) == null);
|
||||
|
||||
try ir.validator.validate(&module);
|
||||
}
|
||||
|
||||
test "Rewriter add block parameter and sync branch calls" {
|
||||
// shader compute @main
|
||||
// {
|
||||
// %0: constant u32 = bits(0x1)
|
||||
//
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// branch .merge()
|
||||
//
|
||||
// .merge():
|
||||
// return
|
||||
//
|
||||
// .alternate():
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .compute);
|
||||
defer module.deinit();
|
||||
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
const u32_type = try builder.internType(.{ .integer = .{ .bits = 32, .signedness = .unsigned } });
|
||||
|
||||
const one = try builder.internConstant(u32_type, .{ .integer_bits = 1 });
|
||||
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
const merge = try builder.addBlock(main, "merge");
|
||||
const alternate = try builder.addBlock(main, "alternate");
|
||||
|
||||
try builder.setTerminator(entry, .{ .branch = try builder.edge(merge, &.{}) });
|
||||
try builder.setTerminator(merge, .return_void);
|
||||
try builder.setTerminator(alternate, .return_void);
|
||||
|
||||
var rewriter = ir.Rewriter.init(&module);
|
||||
|
||||
const parameter = try rewriter.addBlockParameter(merge, u32_type, "incoming", &.{
|
||||
.{
|
||||
.predecessor = entry,
|
||||
.value = one,
|
||||
},
|
||||
});
|
||||
const merge_edge = module.blocks.get(entry).?.terminator.?.branch;
|
||||
try std.testing.expectEqualSlices(ir.id.ValueId, &.{one}, merge_edge.arguments);
|
||||
|
||||
_ = try builder.appendInstruction(merge, u32_type, .{
|
||||
.binary = .{
|
||||
.opcode = .integer_add,
|
||||
.lhs = parameter,
|
||||
.rhs = one,
|
||||
},
|
||||
}, null);
|
||||
try ir.validator.validate(&module);
|
||||
|
||||
try rewriter.removeBlockParameter(merge, 0, one);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), module.blocks.get(merge).?.parameters.items.len);
|
||||
try std.testing.expectEqual(@as(usize, 0), module.blocks.get(entry).?.terminator.?.branch.arguments.len);
|
||||
|
||||
try ir.validator.validate(&module);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), try rewriter.redirectEdges(entry, merge, alternate, &.{}));
|
||||
try std.testing.expectEqual(alternate, module.blocks.get(entry).?.terminator.?.branch.target);
|
||||
|
||||
try ir.validator.validate(&module);
|
||||
}
|
||||
|
||||
fn establishNoCalls(_: *ir.module.Module, _: *ir.pass_manager.Context) !bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn countVisitedFunction(context: ?*anyopaque, _: ir.id.FunctionId, _: *const ir.module.Function) !void {
|
||||
const statistics: *VisitorStatistics = @ptrCast(@alignCast(context.?));
|
||||
statistics.functions += 1;
|
||||
}
|
||||
|
||||
fn countVisitedBlock(context: ?*anyopaque, _: ir.id.BlockId, _: *const ir.module.Block) !void {
|
||||
const statistics: *VisitorStatistics = @ptrCast(@alignCast(context.?));
|
||||
statistics.blocks += 1;
|
||||
}
|
||||
|
||||
test "Pass manager track independent IR properties" {
|
||||
// shader compute @main
|
||||
// {
|
||||
// fn @main() -> void
|
||||
// {
|
||||
// .entry():
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
var module = ir.module.Module.init(std.testing.allocator, .compute);
|
||||
defer module.deinit();
|
||||
|
||||
var builder = ir.Builder.init(&module);
|
||||
|
||||
const void_type = try builder.internType(.void);
|
||||
|
||||
const main = try builder.addFunction(void_type, "main");
|
||||
builder.setEntryPoint(main);
|
||||
|
||||
const entry = try builder.addBlock(main, "entry");
|
||||
try builder.setTerminator(entry, .return_void);
|
||||
|
||||
module.properties.valid_cfg = true;
|
||||
|
||||
var manager = ir.pass_manager.Manager.init(std.testing.allocator);
|
||||
defer manager.deinit();
|
||||
|
||||
try manager.add(.{
|
||||
.name = "establish-no-calls",
|
||||
.required = .{ .valid_cfg = true },
|
||||
.produced = .{ .no_function_calls = true },
|
||||
.run = establishNoCalls,
|
||||
});
|
||||
|
||||
var context: ir.pass_manager.Context = .{ .allocator = std.testing.allocator };
|
||||
|
||||
try std.testing.expect(!try manager.run(&module, &context));
|
||||
try std.testing.expect(module.properties.no_function_calls);
|
||||
|
||||
var statistics: VisitorStatistics = .{};
|
||||
|
||||
try ir.visitor.walk(&module, .{
|
||||
.context = &statistics,
|
||||
.visitFunction = countVisitedFunction,
|
||||
.visitBlock = countVisitedBlock,
|
||||
});
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), statistics.functions);
|
||||
try std.testing.expectEqual(@as(usize, 1), statistics.blocks);
|
||||
}
|
||||
|
||||
test "SPIR-V parser error: zero-word instruction" {
|
||||
const words = [_]u32{
|
||||
spirv.spec.magic_number,
|
||||
0x0001_0000,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
instructionWord(.nop, 0),
|
||||
};
|
||||
try std.testing.expectError(error.ZeroWordInstruction, spirv.Parser.init(&words));
|
||||
|
||||
const truncated = [_]u32{
|
||||
spirv.spec.magic_number,
|
||||
0x0001_0000,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
instructionWord(.i_add, 5),
|
||||
1,
|
||||
};
|
||||
try std.testing.expectError(error.TruncatedInstruction, spirv.Parser.init(&truncated));
|
||||
}
|
||||
|
||||
test "SPIR-V structured branches and OpPhi to block parameters" {
|
||||
const assembly =
|
||||
\\ OpCapability Shader
|
||||
\\ OpMemoryModel Logical GLSL450
|
||||
\\ OpEntryPoint GLCompute %main "main"
|
||||
\\ OpExecutionMode %main LocalSize 1 1 1
|
||||
\\ OpName %main "main"
|
||||
\\ OpName %entry "entry"
|
||||
\\ OpName %true "true"
|
||||
\\ OpName %one "one"
|
||||
\\ OpName %then "then"
|
||||
\\ OpName %then_value "then_value"
|
||||
\\ OpName %else "else"
|
||||
\\ OpName %else_value "else_value"
|
||||
\\ OpName %merge "merge"
|
||||
\\ OpName %merged "merged"
|
||||
\\ OpName %product "product"
|
||||
\\
|
||||
\\ %void = OpTypeVoid
|
||||
\\ %bool = OpTypeBool
|
||||
\\ %uint = OpTypeInt 32 0
|
||||
\\ %fn_void = OpTypeFunction %void
|
||||
\\ %true = OpConstantTrue %bool
|
||||
\\ %one = OpConstant %uint 1
|
||||
\\
|
||||
\\ %main = OpFunction %void None %fn_void
|
||||
\\ %entry = OpLabel
|
||||
\\ OpSelectionMerge %merge None
|
||||
\\ OpBranchConditional %true %then %else
|
||||
\\ %then = OpLabel
|
||||
\\ %then_value = OpIAdd %uint %one %one
|
||||
\\ OpBranch %merge
|
||||
\\ %else = OpLabel
|
||||
\\ %else_value = OpISub %uint %one %one
|
||||
\\ OpBranch %merge
|
||||
\\ %merge = OpLabel
|
||||
\\ %merged = OpPhi %uint %then_value %then %else_value %else
|
||||
\\ %product = OpIMul %uint %merged %one
|
||||
\\ OpReturn
|
||||
\\ OpFunctionEnd
|
||||
;
|
||||
const words = try assembleSpirv(std.testing.allocator, assembly);
|
||||
defer std.testing.allocator.free(words);
|
||||
|
||||
var module = try spirv.translator.translate(std.testing.allocator, words, .{ .entry_point = "main" });
|
||||
defer module.deinit();
|
||||
|
||||
try std.testing.expectEqual(ir.module.Stage.compute, module.stage);
|
||||
try std.testing.expectEqual([3]u32{ 1, 1, 1 }, module.execution_modes.workgroup_size.?);
|
||||
try std.testing.expect(module.properties.valid_cfg);
|
||||
try std.testing.expect(module.properties.valid_ssa);
|
||||
|
||||
const function = module.functions.get(module.entry_point.?).?;
|
||||
try std.testing.expectEqual(@as(usize, 4), function.blocks.items.len);
|
||||
const entry = module.blocks.get(function.blocks.items[0]).?;
|
||||
try std.testing.expect(entry.structured_control == .selection);
|
||||
const merge = module.blocks.get(function.blocks.items[3]).?;
|
||||
try std.testing.expectEqual(@as(usize, 1), merge.parameters.items.len);
|
||||
try std.testing.expectEqual(@as(usize, 1), merge.instructions.items.len);
|
||||
const multiply = module.instructions.get(merge.instructions.items[0]).?;
|
||||
try std.testing.expectEqual(ir.instruction.BinaryOpcode.integer_multiply, multiply.operation.binary.opcode);
|
||||
|
||||
const text = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(text);
|
||||
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "%one: constant u32 = bits(0x1)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "%true: constant bool = true") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "conditional_branch %true, .then(), .else()") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "%then_value: u32 = integer_add %one, %one") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "branch .merge(%then_value)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "%else_value: u32 = integer_subtract %one, %one") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, ".merge(%merged: u32)") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "%product: u32 = integer_multiply %merged, %one") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "integerMultiply") == null);
|
||||
|
||||
var parsed = try ir.parser.parseString(std.testing.allocator, text);
|
||||
defer parsed.deinit();
|
||||
const round_trip = try ir.printer.allocPrint(std.testing.allocator, &parsed);
|
||||
defer std.testing.allocator.free(round_trip);
|
||||
try std.testing.expectEqualStrings(text, round_trip);
|
||||
}
|
||||
|
||||
test "SPIR-V decorated vertex interfaces and load-store operations" {
|
||||
const assembly =
|
||||
\\ OpCapability Shader
|
||||
\\ OpMemoryModel Logical GLSL450
|
||||
\\ OpEntryPoint Vertex %main "main" %in_color %out_color
|
||||
\\ OpName %in_color "in_color"
|
||||
\\ OpName %out_color "out_color"
|
||||
\\ OpDecorate %in_color Location 0
|
||||
\\ OpDecorate %out_color Location 0
|
||||
\\
|
||||
\\ %void = OpTypeVoid
|
||||
\\ %float = OpTypeFloat 32
|
||||
\\ %vec4 = OpTypeVector %float 4
|
||||
\\ %input_vec4 = OpTypePointer Input %vec4
|
||||
\\ %output_vec4 = OpTypePointer Output %vec4
|
||||
\\ %fn_void = OpTypeFunction %void
|
||||
\\ %in_color = OpVariable %input_vec4 Input
|
||||
\\ %out_color = OpVariable %output_vec4 Output
|
||||
\\
|
||||
\\ %main = OpFunction %void None %fn_void
|
||||
\\ %entry = OpLabel
|
||||
\\ %color = OpLoad %vec4 %in_color
|
||||
\\ OpStore %out_color %color
|
||||
\\ OpReturn
|
||||
\\ OpFunctionEnd
|
||||
;
|
||||
const words = try assembleSpirv(std.testing.allocator, assembly);
|
||||
defer std.testing.allocator.free(words);
|
||||
|
||||
var module = try spirv.translator.translate(std.testing.allocator, words, .{ .entry_point = "main" });
|
||||
defer module.deinit();
|
||||
try std.testing.expectEqual(ir.module.Stage.vertex, module.stage);
|
||||
try std.testing.expectEqual(@as(usize, 2), module.interface_variables.entries.items.len);
|
||||
|
||||
const text = try ir.printer.allocPrint(std.testing.allocator, &module);
|
||||
defer std.testing.allocator.free(text);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "load_interface @in_color") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "store_interface @out_color") != null);
|
||||
}
|
||||
|
||||
fn instructionWord(opcode: spirv.spec.Opcode, word_count: u16) u32 {
|
||||
return (@as(u32, word_count) << 16) | @intFromEnum(opcode);
|
||||
}
|
||||
|
||||
fn assembleSpirv(allocator: std.mem.Allocator, assembly: []const u8) ![]u32 {
|
||||
var io_backend: std.Io.Threaded = .init(allocator, .{});
|
||||
defer io_backend.deinit();
|
||||
const io = io_backend.io();
|
||||
|
||||
var child = try std.process.spawn(io, .{
|
||||
.argv = &.{ "spirv-as", "--target-env", "spv1.0", "-o", "-", "-" },
|
||||
.stdin = .pipe,
|
||||
.stdout = .pipe,
|
||||
.stderr = .pipe,
|
||||
});
|
||||
defer child.kill(io);
|
||||
|
||||
{
|
||||
const stdin = child.stdin.?;
|
||||
var stdin_writer = stdin.writer(io, &.{});
|
||||
try stdin_writer.interface.writeAll(assembly);
|
||||
try stdin_writer.interface.flush();
|
||||
stdin.close(io);
|
||||
child.stdin = null;
|
||||
}
|
||||
|
||||
var stdout_buffer: [4096]u8 = undefined;
|
||||
var stdout_reader = child.stdout.?.reader(io, &stdout_buffer);
|
||||
const binary = try stdout_reader.interface.allocRemaining(allocator, .limited(1024 * 1024));
|
||||
defer allocator.free(binary);
|
||||
|
||||
var stderr_buffer: [4096]u8 = undefined;
|
||||
var stderr_reader = child.stderr.?.reader(io, &stderr_buffer);
|
||||
const stderr = try stderr_reader.interface.allocRemaining(allocator, .limited(64 * 1024));
|
||||
defer allocator.free(stderr);
|
||||
|
||||
const term = try child.wait(io);
|
||||
switch (term) {
|
||||
.exited => |code| if (code != 0) {
|
||||
std.log.err("spirv-as failed:\n{s}", .{stderr});
|
||||
return error.SpirvAssemblyFailed;
|
||||
},
|
||||
else => {
|
||||
std.log.err("spirv-as terminated unexpectedly:\n{s}", .{stderr});
|
||||
return error.SpirvAssemblyFailed;
|
||||
},
|
||||
}
|
||||
|
||||
if (binary.len % @sizeOf(u32) != 0) return error.InvalidSpirvBinaryLength;
|
||||
const words = try allocator.alloc(u32, binary.len / @sizeOf(u32));
|
||||
errdefer allocator.free(words);
|
||||
for (words, 0..) |*word, index| {
|
||||
const offset = index * @sizeOf(u32);
|
||||
word.* = std.mem.readInt(u32, binary[offset..][0..4], .little);
|
||||
}
|
||||
return words;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
const spirv = @import("spirv.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const Error = error{
|
||||
HeaderTooShort,
|
||||
InvalidMagic,
|
||||
ByteSwappedModule,
|
||||
InvalidVersion,
|
||||
InvalidIdBound,
|
||||
InvalidSchema,
|
||||
ZeroWordInstruction,
|
||||
TruncatedInstruction,
|
||||
UnterminatedString,
|
||||
};
|
||||
|
||||
pub const Header = struct {
|
||||
version: u32,
|
||||
generator: u32,
|
||||
bound: u32,
|
||||
schema: u32,
|
||||
|
||||
pub inline fn major(self: Header) u8 {
|
||||
return @truncate(self.version >> 16);
|
||||
}
|
||||
|
||||
pub inline fn minor(self: Header) u8 {
|
||||
return @truncate(self.version >> 8);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Instruction = struct {
|
||||
opcode: spirv.Opcode,
|
||||
operands: []const u32,
|
||||
word_offset: usize,
|
||||
|
||||
pub fn operand(self: Instruction, index: usize) ?u32 {
|
||||
return if (index < self.operands.len) self.operands[index] else null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
words: []const u32,
|
||||
cursor: usize = spirv.header_word_count,
|
||||
|
||||
pub fn next(self: *Iterator) Error!?Instruction {
|
||||
if (self.cursor == self.words.len)
|
||||
return null;
|
||||
|
||||
const first_word = self.words[self.cursor];
|
||||
const word_count: usize = first_word >> 16;
|
||||
|
||||
if (word_count == 0)
|
||||
return error.ZeroWordInstruction;
|
||||
if (word_count > self.words.len - self.cursor)
|
||||
return error.TruncatedInstruction;
|
||||
|
||||
const instruction: Instruction = .{
|
||||
.opcode = @enumFromInt(@as(u16, @truncate(first_word))),
|
||||
.operands = self.words[self.cursor + 1 .. self.cursor + word_count],
|
||||
.word_offset = self.cursor,
|
||||
};
|
||||
self.cursor += word_count;
|
||||
return instruction;
|
||||
}
|
||||
};
|
||||
|
||||
words: []const u32,
|
||||
header: Header,
|
||||
|
||||
pub fn init(words: []const u32) Error!Self {
|
||||
if (words.len < spirv.header_word_count)
|
||||
return error.HeaderTooShort;
|
||||
if (words[0] == spirv.byte_swapped_magic_number)
|
||||
return error.ByteSwappedModule;
|
||||
if (words[0] != spirv.magic_number)
|
||||
return error.InvalidMagic;
|
||||
|
||||
const header: Header = .{
|
||||
.version = words[1],
|
||||
.generator = words[2],
|
||||
.bound = words[3],
|
||||
.schema = words[4],
|
||||
};
|
||||
|
||||
if (header.major() != 1 or header.minor() > 6 or (header.version & 0xff00_00ff) != 0)
|
||||
return error.InvalidVersion;
|
||||
if (header.bound == 0)
|
||||
return error.InvalidIdBound;
|
||||
if (header.schema != 0)
|
||||
return error.InvalidSchema;
|
||||
|
||||
var self: Self = .{ .words = words, .header = header };
|
||||
var instruction_iterator = self.iterator();
|
||||
while (try instruction_iterator.next()) |_| {}
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn iterator(self: Self) Iterator {
|
||||
return .{ .words = self.words };
|
||||
}
|
||||
|
||||
pub fn literalStringWordCount(words: []const u32) Error!usize {
|
||||
for (words, 0..) |word, word_index| {
|
||||
inline for (0..4) |byte_index| {
|
||||
if (@as(u8, @truncate(word >> (byte_index * 8))) == 0)
|
||||
return word_index + 1;
|
||||
}
|
||||
}
|
||||
return error.UnterminatedString;
|
||||
}
|
||||
|
||||
pub fn literalStringEquals(words: []const u32, expected: []const u8) Error!bool {
|
||||
var byte_cursor: usize = 0;
|
||||
for (words) |word| {
|
||||
inline for (0..4) |byte_index| {
|
||||
const byte: u8 = @truncate(word >> (byte_index * 8));
|
||||
|
||||
if (byte == 0)
|
||||
return byte_cursor == expected.len;
|
||||
if (byte_cursor >= expected.len or byte != expected[byte_cursor])
|
||||
return false;
|
||||
|
||||
byte_cursor += 1;
|
||||
}
|
||||
}
|
||||
return error.UnterminatedString;
|
||||
}
|
||||
|
||||
pub fn copyLiteralString(allocator: anytype, words: []const u32) ![]u8 {
|
||||
const word_count = try literalStringWordCount(words);
|
||||
var byte_count: usize = 0;
|
||||
outer: for (words[0..word_count]) |word| {
|
||||
inline for (0..4) |byte_index| {
|
||||
if (@as(u8, @truncate(word >> (byte_index * 8))) == 0)
|
||||
break :outer;
|
||||
|
||||
byte_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const result = try allocator.alloc(u8, byte_count);
|
||||
var cursor: usize = 0;
|
||||
outer: for (words[0..word_count]) |word| {
|
||||
inline for (0..4) |byte_index| {
|
||||
const byte: u8 = @truncate(word >> (byte_index * 8));
|
||||
if (byte == 0)
|
||||
break :outer;
|
||||
|
||||
result[cursor] = byte;
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//! ## SPIR-V frontend
|
||||
//!
|
||||
//! This namespace contains the SPIR-V parser and translator used to import shader
|
||||
//! modules into the compiler IR.
|
||||
//!
|
||||
//! `Parser` validates the SPIR-V header and iterates over binary instructions.
|
||||
//! `spec` exposes a minimalistic SPIR-V header translation.
|
||||
//!
|
||||
//! The main entry point is `translator.translate`, which finds the requested entry
|
||||
//! point, maps its execution model to an IR shader stage, lowers supported types,
|
||||
//! constants, interfaces, instructions, and structured control flow, then validates
|
||||
//! the generated IR module.
|
||||
|
||||
pub const Parser = @import("Parser.zig");
|
||||
pub const translator = @import("translator.zig");
|
||||
pub const spec = @import("spirv.zig");
|
||||
@@ -0,0 +1,169 @@
|
||||
pub const magic_number: u32 = 0x07230203;
|
||||
pub const byte_swapped_magic_number: u32 = 0x03022307;
|
||||
pub const header_word_count: usize = 5;
|
||||
|
||||
pub const Opcode = enum(u32) {
|
||||
nop = 0,
|
||||
undef = 1,
|
||||
name = 5,
|
||||
member_name = 6,
|
||||
string = 7,
|
||||
line = 8,
|
||||
extension = 10,
|
||||
ext_inst_import = 11,
|
||||
ext_inst = 12,
|
||||
memory_model = 14,
|
||||
entry_point = 15,
|
||||
execution_mode = 16,
|
||||
capability = 17,
|
||||
|
||||
type_void = 19,
|
||||
type_bool = 20,
|
||||
type_int = 21,
|
||||
type_float = 22,
|
||||
type_vector = 23,
|
||||
type_matrix = 24,
|
||||
type_image = 25,
|
||||
type_sampler = 26,
|
||||
type_sampled_image = 27,
|
||||
type_array = 28,
|
||||
type_runtime_array = 29,
|
||||
type_struct = 30,
|
||||
type_opaque = 31,
|
||||
type_pointer = 32,
|
||||
type_function = 33,
|
||||
|
||||
constant_true = 41,
|
||||
constant_false = 42,
|
||||
constant = 43,
|
||||
constant_composite = 44,
|
||||
constant_null = 46,
|
||||
spec_constant_true = 48,
|
||||
spec_constant_false = 49,
|
||||
spec_constant = 50,
|
||||
spec_constant_composite = 51,
|
||||
spec_constant_op = 52,
|
||||
|
||||
function = 54,
|
||||
function_parameter = 55,
|
||||
function_end = 56,
|
||||
function_call = 57,
|
||||
variable = 59,
|
||||
load = 61,
|
||||
store = 62,
|
||||
access_chain = 65,
|
||||
|
||||
decorate = 71,
|
||||
member_decorate = 72,
|
||||
vector_shuffle = 79,
|
||||
composite_construct = 80,
|
||||
composite_extract = 81,
|
||||
composite_insert = 82,
|
||||
copy_object = 83,
|
||||
|
||||
convert_f_to_u = 109,
|
||||
convert_f_to_s = 110,
|
||||
convert_s_to_f = 111,
|
||||
convert_u_to_f = 112,
|
||||
u_convert = 113,
|
||||
s_convert = 114,
|
||||
f_convert = 115,
|
||||
bitcast = 124,
|
||||
|
||||
s_negate = 126,
|
||||
f_negate = 127,
|
||||
i_add = 128,
|
||||
f_add = 129,
|
||||
i_sub = 130,
|
||||
f_sub = 131,
|
||||
i_mul = 132,
|
||||
f_mul = 133,
|
||||
u_div = 134,
|
||||
s_div = 135,
|
||||
f_div = 136,
|
||||
u_mod = 137,
|
||||
s_rem = 138,
|
||||
s_mod = 139,
|
||||
f_rem = 140,
|
||||
f_mod = 141,
|
||||
shift_right_logical = 194,
|
||||
shift_right_arithmetic = 195,
|
||||
shift_left_logical = 196,
|
||||
bitwise_or = 197,
|
||||
bitwise_xor = 198,
|
||||
bitwise_and = 199,
|
||||
logical_equal = 164,
|
||||
logical_not_equal = 165,
|
||||
logical_or = 166,
|
||||
logical_and = 167,
|
||||
logical_not = 168,
|
||||
select = 169,
|
||||
i_equal = 170,
|
||||
i_not_equal = 171,
|
||||
u_less_than = 176,
|
||||
s_less_than = 177,
|
||||
f_ord_equal = 180,
|
||||
f_unord_equal = 181,
|
||||
f_ord_not_equal = 182,
|
||||
f_unord_not_equal = 183,
|
||||
f_ord_less_than = 184,
|
||||
f_unord_less_than = 185,
|
||||
|
||||
phi = 245,
|
||||
loop_merge = 246,
|
||||
selection_merge = 247,
|
||||
label = 248,
|
||||
branch = 249,
|
||||
branch_conditional = 250,
|
||||
@"switch" = 251,
|
||||
kill = 252,
|
||||
return_ = 253,
|
||||
return_value = 254,
|
||||
@"unreachable" = 255,
|
||||
no_line = 317,
|
||||
|
||||
_,
|
||||
};
|
||||
|
||||
pub const ExecutionModel = enum(u32) {
|
||||
vertex = 0,
|
||||
tessellation_control = 1,
|
||||
tessellation_evaluation = 2,
|
||||
geometry = 3,
|
||||
fragment = 4,
|
||||
gl_compute = 5,
|
||||
kernel = 6,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const StorageClass = enum(u32) {
|
||||
uniform_constant = 0,
|
||||
input = 1,
|
||||
uniform = 2,
|
||||
output = 3,
|
||||
workgroup = 4,
|
||||
cross_workgroup = 5,
|
||||
private = 6,
|
||||
function = 7,
|
||||
generic = 8,
|
||||
push_constant = 9,
|
||||
atomic_counter = 10,
|
||||
image = 11,
|
||||
storage_buffer = 12,
|
||||
physical_storage_buffer = 5349,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const ExecutionMode = enum(u32) {
|
||||
early_fragment_tests = 9,
|
||||
local_size = 17,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Decoration = enum(u32) {
|
||||
built_in = 11,
|
||||
location = 30,
|
||||
component = 31,
|
||||
index = 32,
|
||||
_,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,70 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
|
||||
const drm_syncobj_create = 0xbf;
|
||||
const drm_syncobj_destroy = 0xc0;
|
||||
const drm_syncobj_wait = 0xc3;
|
||||
const drm_syncobj_reset = 0xc4;
|
||||
const drm_syncobj_signal = 0xc5;
|
||||
|
||||
const syncobj_wait_all: u32 = 1 << 0;
|
||||
const syncobj_wait_for_submit: u32 = 1 << 1;
|
||||
|
||||
const SyncObjCreate = extern struct {
|
||||
handle: u32,
|
||||
flags: u32,
|
||||
};
|
||||
|
||||
const SyncObjDestroy = extern struct {
|
||||
handle: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
const SyncObjWait = extern struct {
|
||||
handles: u64,
|
||||
timeout_nsec: i64,
|
||||
count_handles: u32,
|
||||
flags: u32,
|
||||
first_signaled: u32,
|
||||
pad: u32,
|
||||
deadline_nsec: u64,
|
||||
};
|
||||
|
||||
const SyncObjArray = extern struct {
|
||||
handles: u64,
|
||||
count_handles: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BinarySemaphore;
|
||||
|
||||
interface: Interface,
|
||||
handle: u32,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
const flint_device: *FlintDevice = @alignCast(@fieldParentPtr("interface", device));
|
||||
var create_info = SyncObjCreate{
|
||||
.handle = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
base.utils.ioctl(
|
||||
try flint_device.kmd.file(),
|
||||
device.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_create, SyncObjCreate),
|
||||
&create_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
errdefer destroyHandle(flint_device, device.io(), create_info.handle);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
@@ -24,21 +74,89 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.handle = create_info.handle,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
destroyHandle(device, interface.owner.io(), self.handle);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var signal_info = SyncObjArray{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.count_handles = handles.len,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_signal, SyncObjArray),
|
||||
&signal_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var wait_info = SyncObjWait{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.timeout_nsec = std.math.maxInt(i64),
|
||||
.count_handles = handles.len,
|
||||
.flags = syncobj_wait_all | syncobj_wait_for_submit,
|
||||
.first_signaled = 0,
|
||||
.pad = 0,
|
||||
.deadline_nsec = 0,
|
||||
};
|
||||
|
||||
const errno = base.utils.ioctlErrno(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_wait, SyncObjWait),
|
||||
&wait_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
|
||||
switch (errno) {
|
||||
.SUCCESS => {},
|
||||
else => return VkError.DeviceLost,
|
||||
}
|
||||
try reset(interface);
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var reset_info = SyncObjArray{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.count_handles = handles.len,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_reset, SyncObjArray),
|
||||
&reset_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
fn destroyHandle(device: *FlintDevice, io: std.Io, handle: u32) void {
|
||||
var destroy_info = SyncObjDestroy{
|
||||
.handle = handle,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
device.kmd.file() catch return,
|
||||
io,
|
||||
kmd.drmIoctlIowr(drm_syncobj_destroy, SyncObjDestroy),
|
||||
&destroy_info,
|
||||
) catch @panic("Caught an error while handling an error");
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Buffer;
|
||||
@@ -17,6 +15,8 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.allowed_memory_types = std.bit_set.IntegerBitSet(32).initEmpty();
|
||||
interface.allowed_memory_types.set(0);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
@@ -35,6 +35,14 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequirements) void {
|
||||
_ = interface;
|
||||
_ = requirements;
|
||||
requirements.alignment = 16;
|
||||
if (interface.usage.uniform_texel_buffer_bit or interface.usage.storage_texel_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, interface.owner.physical_device.props.limits.min_texel_buffer_offset_alignment);
|
||||
}
|
||||
if (interface.usage.storage_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, interface.owner.physical_device.props.limits.min_storage_buffer_offset_alignment);
|
||||
}
|
||||
if (interface.usage.uniform_buffer_bit) {
|
||||
requirements.alignment = @max(requirements.alignment, interface.owner.physical_device.props.limits.min_uniform_buffer_offset_alignment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BufferView;
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
const FlintDescriptorSet = @import("FlintDescriptorSet.zig");
|
||||
const FlintPipeline = @import("FlintPipeline.zig");
|
||||
|
||||
const MemoryRange = @import("MemoryRange.zig");
|
||||
|
||||
const copy = @import("copy_commands.zig");
|
||||
const blitter = @import("blitter.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.CommandBuffer;
|
||||
|
||||
interface: Interface,
|
||||
batch: std.ArrayList(u32),
|
||||
relocations: std.ArrayList(kmd.Relocation),
|
||||
bound_compute_pipeline: ?*FlintPipeline,
|
||||
bound_compute_descriptor_sets: [base.vulkan_max_descriptor_sets]?*FlintDescriptorSet,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.CommandBufferAllocateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
@@ -53,7 +66,10 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
.resolveImage = resolveImage,
|
||||
.setEvent = setEvent,
|
||||
.setBlendConstants = setBlendConstants,
|
||||
.setDepthBias = setDepthBias,
|
||||
.setDepthBounds = setDepthBounds,
|
||||
.setDeviceMask = setDeviceMask,
|
||||
.setLineWidth = setLineWidth,
|
||||
.setScissor = setScissor,
|
||||
.setStencilCompareMask = setStencilCompareMask,
|
||||
.setStencilReference = setStencilReference,
|
||||
@@ -61,20 +77,35 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
.setViewport = setViewport,
|
||||
.updateBuffer = updateBuffer,
|
||||
.waitEvent = waitEvent,
|
||||
.writeTimestamp = writeTimestamp,
|
||||
};
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.batch = .empty,
|
||||
.relocations = .empty,
|
||||
.bound_compute_pipeline = null,
|
||||
.bound_compute_descriptor_sets = @splat(null),
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const command_allocator = self.interface.host_allocator.allocator();
|
||||
self.batch.deinit(command_allocator);
|
||||
self.relocations.deinit(command_allocator);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn execute(self: *Self) VkError!void {
|
||||
pub fn submitGpuBatch(self: *Self, syncs: []const kmd.SyncDependency) VkError!void {
|
||||
try self.interface.submit();
|
||||
self.interface.finish() catch {};
|
||||
defer self.interface.finish() catch @panic("Caught an error while handling an error");
|
||||
|
||||
// Empty command buffers still need a no-op submission to carry queue synchronization.
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", self.interface.owner));
|
||||
const allocator = self.interface.host_allocator.allocator();
|
||||
try device.kmd.submitBatch(self.interface.owner.io(), allocator, self.batch.items, self.relocations.items, syncs);
|
||||
}
|
||||
|
||||
pub fn begin(interface: *Interface, info: *const vk.CommandBufferBeginInfo) VkError!void {
|
||||
@@ -87,8 +118,34 @@ pub fn end(interface: *Interface) VkError!void {
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, flags: vk.CommandBufferResetFlags) VkError!void {
|
||||
_ = interface;
|
||||
_ = flags;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (flags.release_resources_bit) {
|
||||
const command_allocator = self.interface.host_allocator.allocator();
|
||||
self.batch.clearAndFree(command_allocator);
|
||||
self.relocations.clearAndFree(command_allocator);
|
||||
} else {
|
||||
self.batch.clearRetainingCapacity();
|
||||
self.relocations.clearRetainingCapacity();
|
||||
}
|
||||
self.bound_compute_pipeline = null;
|
||||
self.bound_compute_descriptor_sets = @splat(null);
|
||||
}
|
||||
|
||||
pub fn emit(self: *Self, dword: u32) VkError!void {
|
||||
self.batch.append(self.interface.host_allocator.allocator(), dword) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
pub fn emitRelocatedAddress(self: *Self, range: MemoryRange, read: bool, write: bool) VkError!void {
|
||||
const address_offset = self.batch.items.len * @sizeOf(u32);
|
||||
try self.emit(@intCast(range.offset));
|
||||
try self.emit(0);
|
||||
self.relocations.append(self.interface.host_allocator.allocator(), .{
|
||||
.target_handle = try range.memory.allocation.handle(),
|
||||
.offset = @intCast(address_offset),
|
||||
.delta = @intCast(range.offset),
|
||||
.read = read,
|
||||
.write = write,
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
pub fn beginQuery(interface: *Interface, pool: *base.QueryPool, query: u32, flags: vk.QueryControlFlags) VkError!void {
|
||||
@@ -115,18 +172,27 @@ pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, fra
|
||||
_ = clear_values;
|
||||
}
|
||||
|
||||
pub fn bindDescriptorSets(interface: *Interface, bind_point: vk.PipelineBindPoint, first_set: u32, sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*base.DescriptorSet, dynamic_offsets: []const u32) VkError!void {
|
||||
_ = interface;
|
||||
_ = bind_point;
|
||||
_ = first_set;
|
||||
_ = sets;
|
||||
pub fn bindDescriptorSets(interface: *Interface, bind_point: vk.PipelineBindPoint, first_set: u32, sets: [base.vulkan_max_descriptor_sets]?*base.DescriptorSet, dynamic_offsets: []const u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (bind_point != .compute) return;
|
||||
if (first_set >= base.vulkan_max_descriptor_sets) return VkError.ValidationFailed;
|
||||
|
||||
for (sets, 0..) |set, index| {
|
||||
const base_set = set orelse break;
|
||||
const destination = first_set + index;
|
||||
if (destination >= base.vulkan_max_descriptor_sets) return VkError.ValidationFailed;
|
||||
self.bound_compute_descriptor_sets[destination] = @alignCast(@fieldParentPtr("interface", base_set));
|
||||
}
|
||||
_ = dynamic_offsets;
|
||||
}
|
||||
|
||||
pub fn bindPipeline(interface: *Interface, bind_point: vk.PipelineBindPoint, pipeline: *base.Pipeline) VkError!void {
|
||||
_ = interface;
|
||||
_ = bind_point;
|
||||
_ = pipeline;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (bind_point != .compute)
|
||||
return;
|
||||
|
||||
const flint_pipeline: *FlintPipeline = @alignCast(@fieldParentPtr("interface", pipeline));
|
||||
self.bound_compute_pipeline = flint_pipeline;
|
||||
}
|
||||
|
||||
pub fn bindIndexBuffer(interface: *Interface, buffer: *base.Buffer, offset: usize, index_type: vk.IndexType) VkError!void {
|
||||
@@ -144,13 +210,13 @@ pub fn bindVertexBuffer(interface: *Interface, index: usize, buffer: *base.Buffe
|
||||
}
|
||||
|
||||
pub fn blitImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageBlit, filter: vk.Filter) VkError!void {
|
||||
_ = interface;
|
||||
_ = src;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
_ = filter;
|
||||
if (filter != .nearest) return VkError.FeatureNotPresent;
|
||||
|
||||
for (regions) |region|
|
||||
try blitter.blitImageRegion(self, src, dst, region);
|
||||
}
|
||||
|
||||
pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, rect: vk.ClearRect) VkError!void {
|
||||
@@ -176,35 +242,35 @@ pub fn clearDepthStencilImage(interface: *Interface, image: *base.Image, layout:
|
||||
}
|
||||
|
||||
pub fn copyBuffer(interface: *Interface, src: *base.Buffer, dst: *base.Buffer, regions: []const vk.BufferCopy) VkError!void {
|
||||
_ = interface;
|
||||
_ = src;
|
||||
_ = dst;
|
||||
_ = regions;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
|
||||
for (regions) |region| {
|
||||
const src_range = try copy.copyRangeFromRegion(src, region.src_offset, region.size);
|
||||
const dst_range = try copy.copyRangeFromRegion(dst, region.dst_offset, region.size);
|
||||
try copy.emitLinearCopy(self, src_range, dst_range);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyBufferToImage(interface: *Interface, src: *base.Buffer, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.BufferImageCopy) VkError!void {
|
||||
_ = interface;
|
||||
_ = src;
|
||||
_ = dst;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
for (regions) |region|
|
||||
try copy.copyBufferImage(self, src, dst, region, true);
|
||||
}
|
||||
|
||||
pub fn copyImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageCopy) VkError!void {
|
||||
_ = interface;
|
||||
_ = src;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
for (regions) |region|
|
||||
try copy.copyImage(self, src, dst, region);
|
||||
}
|
||||
|
||||
pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Buffer, regions: []const vk.BufferImageCopy) VkError!void {
|
||||
_ = interface;
|
||||
_ = src;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = regions;
|
||||
for (regions) |region|
|
||||
try copy.copyBufferImage(self, dst, src, region, false);
|
||||
}
|
||||
|
||||
pub fn copyQueryPoolResults(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32, dst: *base.Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void {
|
||||
@@ -219,10 +285,7 @@ pub fn copyQueryPoolResults(interface: *Interface, pool: *base.QueryPool, first:
|
||||
}
|
||||
|
||||
pub fn dispatch(interface: *Interface, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
_ = interface;
|
||||
_ = group_count_x;
|
||||
_ = group_count_y;
|
||||
_ = group_count_z;
|
||||
try dispatchBase(interface, 0, 0, 0, group_count_x, group_count_y, group_count_z);
|
||||
}
|
||||
|
||||
pub fn dispatchBase(interface: *Interface, base_group_x: u32, base_group_y: u32, base_group_z: u32, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
@@ -284,16 +347,37 @@ pub fn endRenderPass(interface: *Interface) VkError!void {
|
||||
}
|
||||
|
||||
pub fn executeCommands(interface: *Interface, commands: *Interface) VkError!void {
|
||||
_ = interface;
|
||||
_ = commands;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const secondary: *Self = @alignCast(@fieldParentPtr("interface", commands));
|
||||
const allocator = self.interface.host_allocator.allocator();
|
||||
const relocation_offset = self.batch.items.len * @sizeOf(u32);
|
||||
|
||||
self.batch.appendSlice(allocator, secondary.batch.items) catch return VkError.OutOfHostMemory;
|
||||
for (secondary.relocations.items) |relocation| {
|
||||
self.relocations.append(allocator, .{
|
||||
.target_handle = relocation.target_handle,
|
||||
.offset = relocation.offset + relocation_offset,
|
||||
.delta = relocation.delta,
|
||||
.read = relocation.read,
|
||||
.write = relocation.write,
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void {
|
||||
_ = interface;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
_ = data;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const dst_range = try copy.fillRange(buffer, offset, size);
|
||||
|
||||
var filled: vk.DeviceSize = 0;
|
||||
while (filled < dst_range.size) {
|
||||
const dst_chunk: MemoryRange = .{ .memory = dst_range.memory, .offset = dst_range.offset + filled, .size = @sizeOf(u32) };
|
||||
|
||||
try self.emit(kmd.mi_store_data_imm_dword);
|
||||
try self.emitRelocatedAddress(dst_chunk, false, true);
|
||||
try self.emit(data);
|
||||
|
||||
filled += @sizeOf(u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, data: []const u8) VkError!void {
|
||||
@@ -363,6 +447,24 @@ pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void
|
||||
_ = constants;
|
||||
}
|
||||
|
||||
pub fn setDepthBias(interface: *Interface, constant_factor: f32, clamp: f32, slope_factor: f32) VkError!void {
|
||||
_ = interface;
|
||||
_ = constant_factor;
|
||||
_ = clamp;
|
||||
_ = slope_factor;
|
||||
}
|
||||
|
||||
pub fn setDepthBounds(interface: *Interface, min: f32, max: f32) VkError!void {
|
||||
_ = interface;
|
||||
_ = min;
|
||||
_ = max;
|
||||
}
|
||||
|
||||
pub fn setLineWidth(interface: *Interface, width: f32) VkError!void {
|
||||
_ = interface;
|
||||
_ = width;
|
||||
}
|
||||
|
||||
pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void {
|
||||
_ = interface;
|
||||
_ = face_mask;
|
||||
@@ -390,3 +492,9 @@ pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.Pipeli
|
||||
_ = buffer_barriers;
|
||||
_ = image_barriers;
|
||||
}
|
||||
|
||||
pub fn writeTimestamp(interface: *Interface, stage: vk.PipelineStageFlags, pool: *base.QueryPool, query: u32) VkError!void {
|
||||
_ = interface;
|
||||
_ = stage;
|
||||
try pool.writeTimestamp(query, 0);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const NonDispatchable = base.NonDispatchable;
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const FlintCommandBuffer = @import("FlintCommandBuffer.zig");
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ const base = @import("base");
|
||||
const VkError = base.VkError;
|
||||
const VulkanAllocator = base.VulkanAllocator;
|
||||
|
||||
const Device = base.Device;
|
||||
const FlintDescriptorSet = @import("FlintDescriptorSet.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorPool;
|
||||
|
||||
interface: Interface,
|
||||
sets: std.ArrayList(*FlintDescriptorSet),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
@@ -27,29 +28,42 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.sets = std.ArrayList(*FlintDescriptorSet).initCapacity(allocator, info.max_sets) catch return VkError.OutOfHostMemory,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetLayout) VkError!*base.DescriptorSet {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = layout;
|
||||
return VkError.Unknown;
|
||||
if (self.sets.items.len == self.sets.capacity) return VkError.OutOfPoolMemory;
|
||||
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
const set = try FlintDescriptorSet.create(interface.owner, allocator, layout);
|
||||
self.sets.appendAssumeCapacity(set);
|
||||
return &set.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const set_allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
for (self.sets.items) |set| set.interface.destroy(set_allocator);
|
||||
self.sets.deinit(allocator);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = set;
|
||||
const flint_set: *FlintDescriptorSet = @alignCast(@fieldParentPtr("interface", set));
|
||||
const index = std.mem.indexOfScalar(*FlintDescriptorSet, self.sets.items, flint_set) orelse return VkError.ValidationFailed;
|
||||
_ = self.sets.orderedRemove(index);
|
||||
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
set.destroy(allocator);
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
const allocator = VulkanAllocator.init(null, .object).allocator();
|
||||
for (self.sets.items) |set| set.interface.destroy(allocator);
|
||||
self.sets.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
@@ -3,11 +3,25 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const NonDispatchable = base.NonDispatchable;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSet;
|
||||
|
||||
pub const DescriptorBuffer = struct {
|
||||
buffer: ?*base.Buffer,
|
||||
offset: vk.DeviceSize,
|
||||
size: vk.DeviceSize,
|
||||
};
|
||||
|
||||
const Descriptor = union(enum) {
|
||||
buffer: []DescriptorBuffer,
|
||||
unsupported,
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
heap: []u8,
|
||||
descriptors: []Descriptor,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.DescriptorSetLayout) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
@@ -21,26 +35,139 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.
|
||||
.write = write,
|
||||
};
|
||||
|
||||
var heap_size = layout.bindings.len * @sizeOf(Descriptor);
|
||||
for (layout.bindings) |binding| {
|
||||
heap_size += switch (binding.descriptor_type) {
|
||||
.uniform_buffer,
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer,
|
||||
.storage_buffer_dynamic,
|
||||
=> binding.array_size * @sizeOf(DescriptorBuffer),
|
||||
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
const heap = allocator.alloc(u8, heap_size) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.free(heap);
|
||||
|
||||
var fixed = std.heap.FixedBufferAllocator.init(heap);
|
||||
const descriptors = fixed.allocator().alloc(Descriptor, layout.bindings.len) catch return VkError.OutOfHostMemory;
|
||||
for (descriptors, layout.bindings) |*descriptor, binding| {
|
||||
descriptor.* = switch (binding.descriptor_type) {
|
||||
.uniform_buffer, .uniform_buffer_dynamic, .storage_buffer, .storage_buffer_dynamic => blk: {
|
||||
const buffers = fixed.allocator().alloc(DescriptorBuffer, binding.array_size) catch return VkError.OutOfHostMemory;
|
||||
|
||||
for (buffers) |*buffer| {
|
||||
buffer.* = .{
|
||||
.buffer = null,
|
||||
.offset = 0,
|
||||
.size = 0,
|
||||
};
|
||||
}
|
||||
|
||||
break :blk .{ .buffer = buffers };
|
||||
},
|
||||
else => .unsupported,
|
||||
};
|
||||
}
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.heap = heap,
|
||||
.descriptors = descriptors,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.free(self.heap);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = src_interface;
|
||||
_ = data;
|
||||
const src: *const Self = @alignCast(@fieldParentPtr("interface", src_interface));
|
||||
|
||||
if (data.dst_binding >= self.descriptors.len or data.src_binding >= src.descriptors.len)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const dst = switch (self.descriptors[data.dst_binding]) {
|
||||
.buffer => |buffers| buffers,
|
||||
.unsupported => return VkError.FeatureNotPresent,
|
||||
};
|
||||
|
||||
const source = switch (src.descriptors[data.src_binding]) {
|
||||
.buffer => |buffers| buffers,
|
||||
.unsupported => return VkError.FeatureNotPresent,
|
||||
};
|
||||
|
||||
const dst_start: usize = @intCast(data.dst_array_element);
|
||||
const src_start: usize = @intCast(data.src_array_element);
|
||||
const count: usize = @intCast(data.descriptor_count);
|
||||
|
||||
if (dst_start > dst.len or count > dst.len - dst_start)
|
||||
return VkError.ValidationFailed;
|
||||
if (src_start > source.len or count > source.len - src_start)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
@memcpy(dst[dst_start .. dst_start + count], source[src_start .. src_start + count]);
|
||||
}
|
||||
|
||||
pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = write_data;
|
||||
|
||||
switch (write_data.descriptor_type) {
|
||||
.uniform_buffer,
|
||||
.uniform_buffer_dynamic,
|
||||
.storage_buffer,
|
||||
.storage_buffer_dynamic,
|
||||
=> {
|
||||
if (write_data.dst_binding >= self.descriptors.len)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const descriptors = switch (self.descriptors[write_data.dst_binding]) {
|
||||
.buffer => |buffers| buffers,
|
||||
.unsupported => return VkError.FeatureNotPresent,
|
||||
};
|
||||
|
||||
const start: usize = @intCast(write_data.dst_array_element);
|
||||
const count: usize = @intCast(write_data.descriptor_count);
|
||||
|
||||
if (start > descriptors.len or count > descriptors.len - start)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
for (write_data.p_buffer_info, 0..write_data.descriptor_count) |buffer_info, index| {
|
||||
const descriptor = &descriptors[start + index];
|
||||
descriptor.* = .{ .buffer = null, .offset = buffer_info.offset, .size = buffer_info.range };
|
||||
|
||||
if (buffer_info.buffer == .null_handle)
|
||||
continue;
|
||||
|
||||
const buffer = try NonDispatchable(base.Buffer).fromHandleObject(buffer_info.buffer);
|
||||
|
||||
if (descriptor.offset > buffer.size)
|
||||
return VkError.ValidationFailed;
|
||||
if (descriptor.size == vk.WHOLE_SIZE)
|
||||
descriptor.size = buffer.size - descriptor.offset;
|
||||
if (descriptor.size > buffer.size - descriptor.offset)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
descriptor.buffer = buffer;
|
||||
}
|
||||
},
|
||||
|
||||
else => return VkError.FeatureNotPresent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getBuffer(self: *const Self, binding: u32, array_element: u32) VkError!DescriptorBuffer {
|
||||
if (binding >= self.descriptors.len) return VkError.ValidationFailed;
|
||||
const buffers = switch (self.descriptors[binding]) {
|
||||
.buffer => |items| items,
|
||||
.unsupported => return VkError.FeatureNotPresent,
|
||||
};
|
||||
if (array_element >= buffers.len) return VkError.ValidationFailed;
|
||||
return buffers[array_element];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSetLayout;
|
||||
|
||||
@@ -3,6 +3,8 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const FlintQueue = @import("FlintQueue.zig");
|
||||
const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
|
||||
const Kmd = @import("kmd.zig");
|
||||
|
||||
pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
|
||||
pub const FlintBuffer = @import("FlintBuffer.zig");
|
||||
@@ -32,12 +34,16 @@ const Self = @This();
|
||||
pub const Interface = base.Device;
|
||||
|
||||
interface: Interface,
|
||||
kmd: Kmd.Device,
|
||||
|
||||
pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, instance, physical_device, info);
|
||||
const flint_physical_device: *const FlintPhysicalDevice = @alignCast(@fieldParentPtr("interface", physical_device));
|
||||
var kmd_device = try Kmd.Device.open(instance.io(), flint_physical_device);
|
||||
errdefer kmd_device.close(instance.io());
|
||||
|
||||
interface.vtable = &.{
|
||||
.createQueue = FlintQueue.create,
|
||||
@@ -73,6 +79,7 @@ pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, a
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.kmd = kmd_device,
|
||||
};
|
||||
|
||||
try self.interface.createQueues(allocator, info);
|
||||
@@ -81,6 +88,7 @@ pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, a
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.kmd.close(interface.io());
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
@@ -11,12 +11,19 @@ const Self = @This();
|
||||
pub const Interface = base.DeviceMemory;
|
||||
|
||||
interface: Interface,
|
||||
allocation: kmd.Memory,
|
||||
|
||||
pub fn create(device: *FlintDevice, allocator: std.mem.Allocator, size: vk.DeviceSize, memory_type_index: u32) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
if (memory_type_index >= device.interface.physical_device.mem_props.memory_type_count) {
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
var interface = try Interface.init(&device.interface, size, memory_type_index);
|
||||
var allocation = try device.kmd.allocateMemory(device.interface.io(), size);
|
||||
errdefer allocation.deinit(&device.kmd, device.interface.io());
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
@@ -28,33 +35,42 @@ pub fn create(device: *FlintDevice, allocator: std.mem.Allocator, size: vk.Devic
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.allocation = allocation,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
self.allocation.deinit(&device.kmd, interface.owner.io());
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
try self.allocation.flushRange(&device.kmd, interface.owner.io(), offset, size);
|
||||
}
|
||||
|
||||
pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
try self.allocation.invalidateRange(&device.kmd, interface.owner.io(), offset, size);
|
||||
}
|
||||
|
||||
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
return VkError.Unknown;
|
||||
if (interface.is_mapped) return VkError.MemoryMapFailed;
|
||||
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
const data = try self.allocation.map(&device.kmd, interface.owner.io(), offset, size);
|
||||
interface.is_mapped = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn unmap(_: *Interface) void {}
|
||||
pub fn unmap(interface: *Interface) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.allocation.unmap();
|
||||
interface.is_mapped = false;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Event;
|
||||
|
||||
+131
-6
@@ -1,20 +1,73 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
|
||||
const drm_syncobj_create = 0xbf;
|
||||
const drm_syncobj_destroy = 0xc0;
|
||||
const drm_syncobj_wait = 0xc3;
|
||||
const drm_syncobj_reset = 0xc4;
|
||||
const drm_syncobj_signal = 0xc5;
|
||||
|
||||
const syncobj_create_signaled: u32 = 1 << 0;
|
||||
const syncobj_wait_all: u32 = 1 << 0;
|
||||
const syncobj_wait_for_submit: u32 = 1 << 1;
|
||||
|
||||
const SyncObjCreate = extern struct {
|
||||
handle: u32,
|
||||
flags: u32,
|
||||
};
|
||||
|
||||
const SyncObjDestroy = extern struct {
|
||||
handle: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
const SyncObjWait = extern struct {
|
||||
handles: u64,
|
||||
timeout_nsec: i64,
|
||||
count_handles: u32,
|
||||
flags: u32,
|
||||
first_signaled: u32,
|
||||
pad: u32,
|
||||
deadline_nsec: u64,
|
||||
};
|
||||
|
||||
const SyncObjArray = extern struct {
|
||||
handles: u64,
|
||||
count_handles: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Fence;
|
||||
|
||||
interface: Interface,
|
||||
handle: u32,
|
||||
|
||||
pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
const flint_device: *FlintDevice = @alignCast(@fieldParentPtr("interface", device));
|
||||
var create_info = SyncObjCreate{
|
||||
.handle = 0,
|
||||
.flags = if (info.flags.signaled_bit) syncobj_create_signaled else 0,
|
||||
};
|
||||
|
||||
base.utils.ioctl(
|
||||
try flint_device.kmd.file(),
|
||||
device.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_create, SyncObjCreate),
|
||||
&create_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
|
||||
errdefer destroyHandle(flint_device, device.io(), create_info.handle);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
@@ -26,32 +79,104 @@ pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.Fen
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.handle = create_info.handle,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
destroyHandle(device, interface.owner.io(), self.handle);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getStatus(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
wait(interface, 0) catch |err| switch (err) {
|
||||
VkError.Timeout => return VkError.NotReady,
|
||||
else => return err,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var reset_info = SyncObjArray{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.count_handles = handles.len,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_reset, SyncObjArray),
|
||||
&reset_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var signal_info = SyncObjArray{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.count_handles = handles.len,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_signal, SyncObjArray),
|
||||
&signal_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface, timeout: u64) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = timeout;
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
var handles = [_]u32{self.handle};
|
||||
var wait_info = SyncObjWait{
|
||||
.handles = @intFromPtr(&handles),
|
||||
.timeout_nsec = absoluteTimeout(interface.owner.io(), timeout),
|
||||
.count_handles = handles.len,
|
||||
.flags = syncobj_wait_all | syncobj_wait_for_submit,
|
||||
.first_signaled = 0,
|
||||
.pad = 0,
|
||||
.deadline_nsec = 0,
|
||||
};
|
||||
|
||||
const errno = base.utils.ioctlErrno(
|
||||
try device.kmd.file(),
|
||||
interface.owner.io(),
|
||||
kmd.drmIoctlIowr(drm_syncobj_wait, SyncObjWait),
|
||||
&wait_info,
|
||||
) catch return VkError.DeviceLost;
|
||||
|
||||
return switch (errno) {
|
||||
.SUCCESS => {},
|
||||
.TIME => VkError.Timeout,
|
||||
else => VkError.DeviceLost,
|
||||
};
|
||||
}
|
||||
|
||||
fn destroyHandle(device: *FlintDevice, io: std.Io, handle: u32) void {
|
||||
var destroy_info = SyncObjDestroy{
|
||||
.handle = handle,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
device.kmd.file() catch return,
|
||||
io,
|
||||
kmd.drmIoctlIowr(drm_syncobj_destroy, SyncObjDestroy),
|
||||
&destroy_info,
|
||||
) catch @panic("ioctl failed");
|
||||
}
|
||||
|
||||
fn absoluteTimeout(io: std.Io, timeout: u64) i64 {
|
||||
if (timeout == std.math.maxInt(u64)) return std.math.maxInt(i64);
|
||||
|
||||
const now = std.Io.Clock.awake.now(io).nanoseconds;
|
||||
const deadline: i96 = now + @as(i96, timeout);
|
||||
return @intCast(@min(deadline, std.math.maxInt(i64)));
|
||||
}
|
||||
|
||||
+133
-25
@@ -1,16 +1,21 @@
|
||||
//! Flint images currently use a tightly packed, linear representation.
|
||||
//! Aspects are stored consecutively. Within each aspect, every array layer
|
||||
//! contains all mip levels, and every mip level contains its depth slices and
|
||||
//! samples. Keeping this layout description here gives command encoding a
|
||||
//! single source of truth for image addresses and pitches.
|
||||
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const FlintDeviceMemory = @import("FlintDeviceMemory.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Image;
|
||||
|
||||
pub const F32x4 = @Vector(4, f32);
|
||||
pub const U32x4 = @Vector(4, u32);
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*Self {
|
||||
@@ -18,6 +23,9 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.allowed_memory_types = std.bit_set.IntegerBitSet(32).initEmpty();
|
||||
interface.allowed_memory_types.set(0);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getMemoryRequirements = getMemoryRequirements,
|
||||
@@ -28,9 +36,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
.copyToMemory = copyToMemory,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -40,39 +46,141 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(_: *Interface, requirements: *vk.MemoryRequirements) VkError!void {
|
||||
_ = requirements;
|
||||
requirements.alignment = lib.image_memory_alignment;
|
||||
requirements.size = std.mem.alignForward(vk.DeviceSize, requirements.size, lib.image_memory_alignment);
|
||||
}
|
||||
|
||||
pub fn copyToMemory(interface: *const Interface, memory: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
|
||||
_ = interface;
|
||||
_ = subresource;
|
||||
@memset(memory, 0);
|
||||
pub fn copyToMemory(interface: *const Interface, dst: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const memory_interface = interface.memory orelse return VkError.InvalidDeviceMemoryDrv;
|
||||
const memory: *FlintDeviceMemory = @alignCast(@fieldParentPtr("interface", memory_interface));
|
||||
|
||||
try validateSingleAspect(interface.format, subresource.aspect_mask);
|
||||
if (subresource.mip_level >= interface.mip_levels or
|
||||
subresource.base_array_layer >= interface.array_layers or
|
||||
subresource.layer_count == 0)
|
||||
return VkError.ValidationFailed;
|
||||
const layer_count = if (subresource.layer_count == vk.REMAINING_ARRAY_LAYERS)
|
||||
interface.array_layers - subresource.base_array_layer
|
||||
else
|
||||
subresource.layer_count;
|
||||
if (layer_count > interface.array_layers - subresource.base_array_layer)
|
||||
return VkError.ValidationFailed;
|
||||
const level_size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level);
|
||||
const required_size, const size_overflow = @mulWithOverflow(level_size, @as(usize, layer_count));
|
||||
if (size_overflow != 0 or dst.len < required_size) return VkError.ValidationFailed;
|
||||
|
||||
const first_offset = try self.getSubresourceOffset(
|
||||
subresource.aspect_mask,
|
||||
subresource.mip_level,
|
||||
subresource.base_array_layer,
|
||||
);
|
||||
const absolute_offset, const offset_overflow = @addWithOverflow(interface.memory_offset, first_offset);
|
||||
if (offset_overflow != 0) return VkError.ValidationFailed;
|
||||
|
||||
const device: *@import("FlintDevice.zig") = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
const mapped = try memory.allocation.map(&device.kmd, interface.owner.io(), absolute_offset, vk.WHOLE_SIZE);
|
||||
|
||||
const layer_pitch = self.getLayerSize(subresource.aspect_mask);
|
||||
var dst_offset: usize = 0;
|
||||
var src_offset: usize = 0;
|
||||
for (0..layer_count) |_| {
|
||||
if (src_offset > mapped.len or level_size > mapped.len - src_offset)
|
||||
return VkError.InvalidDeviceMemoryDrv;
|
||||
@memcpy(dst[dst_offset..][0..level_size], mapped[src_offset..][0..level_size]);
|
||||
dst_offset += level_size;
|
||||
src_offset += layer_pitch;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getSubresourceOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, layer: u32) VkError!usize {
|
||||
if (mip_level >= self.interface.mip_levels or layer >= self.interface.array_layers)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
var offset = try self.getAspectOffset(aspect_mask);
|
||||
offset += layer * self.getLayerSize(aspect_mask);
|
||||
for (0..mip_level) |mip|
|
||||
offset += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip));
|
||||
return offset;
|
||||
}
|
||||
|
||||
fn getAspectOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags) VkError!usize {
|
||||
try validateSingleAspect(self.interface.format, aspect_mask);
|
||||
return switch (self.interface.format) {
|
||||
.d16_unorm_s8_uint,
|
||||
.d24_unorm_s8_uint,
|
||||
.d32_sfloat_s8_uint,
|
||||
=> if (aspect_mask.stencil_bit)
|
||||
self.interface.getTotalSizeForAspect(.{ .depth_bit = true })
|
||||
else
|
||||
0,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getTotalSizeForAspect(interface: *const Interface, aspect_mask: vk.ImageAspectFlags) VkError!usize {
|
||||
_ = aspect_mask;
|
||||
return interface.extent.width * interface.extent.height * interface.extent.depth * base.format.texelSize(interface.format);
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const valid_aspects = base.format.toAspect(interface.format);
|
||||
if (aspect_mask.toInt() == 0 or aspect_mask.subtract(valid_aspects).toInt() != 0)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
var size: usize = 0;
|
||||
if (aspect_mask.color_bit) size += self.getLayerSize(.{ .color_bit = true });
|
||||
if (aspect_mask.depth_bit) size += self.getLayerSize(.{ .depth_bit = true });
|
||||
if (aspect_mask.stencil_bit) size += self.getLayerSize(.{ .stencil_bit = true });
|
||||
return size * interface.array_layers;
|
||||
}
|
||||
|
||||
pub fn getSubresourceLayout(interface: *const Interface, subresource: vk.ImageSubresource) VkError!vk.SubresourceLayout {
|
||||
_ = subresource;
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
try validateSingleAspect(interface.format, subresource.aspect_mask);
|
||||
|
||||
return .{
|
||||
.offset = 0,
|
||||
.size = try getTotalSizeForAspect(interface, base.format.toAspect(interface.format)),
|
||||
.row_pitch = getRowPitchMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
.array_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
.depth_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
.offset = try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer),
|
||||
.size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level),
|
||||
.row_pitch = getRowPitchMemSizeForMipLevel(interface, subresource.aspect_mask, subresource.mip_level),
|
||||
.array_pitch = self.getLayerSize(subresource.aspect_mask),
|
||||
.depth_pitch = getSliceMemSizeForMipLevel(interface, subresource.aspect_mask, subresource.mip_level),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getLayerSize(self: *const Self, aspect_mask: vk.ImageAspectFlags) usize {
|
||||
var size: usize = 0;
|
||||
for (0..self.interface.mip_levels) |mip_level|
|
||||
size += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip_level));
|
||||
return size;
|
||||
}
|
||||
|
||||
pub inline fn getMultiSampledLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
return self.getMipLevelSize(aspect_mask, mip_level) * self.interface.samples.toInt();
|
||||
}
|
||||
|
||||
pub inline fn getMipLevelSize(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
return getSliceMemSizeForMipLevel(&self.interface, aspect_mask, mip_level) * self.getMipLevelExtent(mip_level).depth;
|
||||
}
|
||||
|
||||
pub fn getMipLevelExtent(self: *const Self, mip_level: u32) vk.Extent3D {
|
||||
return .{
|
||||
.width = @max(1, self.interface.extent.width >> @intCast(mip_level)),
|
||||
.height = @max(1, self.interface.extent.height >> @intCast(mip_level)),
|
||||
.depth = @max(1, self.interface.extent.depth >> @intCast(mip_level)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
_ = aspect_mask;
|
||||
_ = mip_level;
|
||||
return interface.extent.width * interface.extent.height * base.format.texelSize(interface.format);
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const extent = self.getMipLevelExtent(mip_level);
|
||||
return base.format.sliceMemSize(base.format.fromAspect(interface.format, aspect_mask), extent.width, extent.height);
|
||||
}
|
||||
|
||||
pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
_ = aspect_mask;
|
||||
_ = mip_level;
|
||||
return interface.extent.width * base.format.texelSize(interface.format);
|
||||
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const extent = self.getMipLevelExtent(mip_level);
|
||||
return base.format.pitchMemSize(base.format.fromAspect(interface.format, aspect_mask), extent.width);
|
||||
}
|
||||
|
||||
fn validateSingleAspect(format: vk.Format, aspect_mask: vk.ImageAspectFlags) VkError!void {
|
||||
const valid_aspects = base.format.toAspect(format);
|
||||
if (aspect_mask.toInt() == 0 or @popCount(aspect_mask.toInt()) != 1 or aspect_mask.subtract(valid_aspects).toInt() != 0)
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.ImageView;
|
||||
|
||||
@@ -26,7 +26,7 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const EXTENSIONS = [_]vk.ExtensionProperties{
|
||||
pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group_creation),
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
@@ -73,11 +73,24 @@ fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator, d
|
||||
|
||||
if (drm_device.node_type != .render or
|
||||
std.meta.activeTag(drm_device.device_info) != .pci or
|
||||
drm_device.device_info.pci.vendor_id != lib.INTEL_PCI_VENDOR_ID)
|
||||
drm_device.device_info.pci.vendor_id != lib.intel_pci_vendor_id)
|
||||
continue;
|
||||
|
||||
const physical_device = try FlintPhysicalDevice.create(allocator, interface, &drm_device);
|
||||
errdefer physical_device.interface.release(allocator) catch {};
|
||||
const version = device.getVersion(io_var, allocator) catch continue;
|
||||
defer version.deinit(allocator);
|
||||
|
||||
const kmd_type: lib.KmdType = if (std.mem.eql(u8, version.name, "i915"))
|
||||
.i915
|
||||
else if (std.mem.eql(u8, version.name, "xe"))
|
||||
.xe
|
||||
else
|
||||
.invalid;
|
||||
|
||||
if (kmd_type == .invalid)
|
||||
continue;
|
||||
|
||||
const physical_device = try FlintPhysicalDevice.create(allocator, interface, &drm_device, kmd_type);
|
||||
errdefer physical_device.interface.release(allocator) catch @panic("Caught an error while handling an error");
|
||||
|
||||
const dispatchable = try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface);
|
||||
errdefer dispatchable.destroy(allocator);
|
||||
|
||||
@@ -9,7 +9,6 @@ const pci_ids = @import("pci_ids.zig").map;
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const VulkanAllocator = base.VulkanAllocator;
|
||||
const SurfaceKHR = base.SurfaceKHR;
|
||||
|
||||
const Self = @This();
|
||||
@@ -24,14 +23,15 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const EXTENSIONS = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group),
|
||||
pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_swapchain),
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
kmd_type: lib.KmdType,
|
||||
node_path: [base.drm.max_node_name:0]u8,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device: *const base.drm.Device) VkError!*Self {
|
||||
pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device: *const base.drm.Device, kmd_type: lib.KmdType) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
@@ -53,9 +53,9 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
|
||||
.getSurfaceSupportKHR = getSurfaceSupportKHR,
|
||||
};
|
||||
|
||||
interface.props.api_version = @bitCast(lib.VULKAN_VERSION);
|
||||
interface.props.vendor_id = lib.INTEL_PCI_VENDOR_ID;
|
||||
interface.props.driver_version = @bitCast(base.DRIVER_VERSION);
|
||||
interface.props.api_version = @bitCast(lib.vulkan_version);
|
||||
interface.props.vendor_id = lib.intel_pci_vendor_id;
|
||||
interface.props.driver_version = @bitCast(base.driver_version);
|
||||
interface.props.device_id = drm_device.device_info.pci.device_id;
|
||||
interface.props.device_type = .integrated_gpu;
|
||||
|
||||
@@ -78,7 +78,7 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
|
||||
break;
|
||||
}
|
||||
|
||||
interface.props.pipeline_cache_uuid = undefined;
|
||||
interface.props.pipeline_cache_uuid = @splat(0);
|
||||
interface.props.limits = .{
|
||||
.max_image_dimension_1d = 4096,
|
||||
.max_image_dimension_2d = 4096,
|
||||
@@ -223,11 +223,19 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.kmd_type = kmd_type,
|
||||
.node_path = @splat(0),
|
||||
};
|
||||
const node_path = drm_device.nodePath();
|
||||
@memcpy(self.node_path[0..node_path.len], node_path);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn getNodePath(self: *const Self) [:0]const u8 {
|
||||
return std.mem.sliceTo(&self.node_path, 0);
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
@@ -248,10 +256,10 @@ pub fn enumerateExtensionProperties(_: *const Interface, layer_name: ?[]const u8
|
||||
return VkError.LayerNotPresent;
|
||||
}
|
||||
|
||||
const available = EXTENSIONS.len;
|
||||
const available = extensions.len;
|
||||
if (p_properties) |properties| {
|
||||
const write_count = @min(count.*, available);
|
||||
for (EXTENSIONS[0..write_count], properties[0..write_count]) |ext, *prop| {
|
||||
for (extensions[0..write_count], properties[0..write_count]) |ext, *prop| {
|
||||
prop.* = ext;
|
||||
}
|
||||
count.* = @intCast(write_count);
|
||||
@@ -368,7 +376,6 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
},
|
||||
|
||||
// Formats which can be sampled, but don't support filtering
|
||||
@@ -408,7 +415,6 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.
|
||||
.g10x6_b10x6r10x6_2plane_420_unorm_3pack16,
|
||||
=> {
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_ycbcr_conversion_linear_filter_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
@@ -678,6 +684,11 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.
|
||||
properties.linear_tiling_features.transfer_dst_bit = true;
|
||||
}
|
||||
|
||||
if (properties.optimal_tiling_features.blit_src_bit or properties.optimal_tiling_features.blit_dst_bit) {
|
||||
properties.optimal_tiling_features.blit_src_bit = true;
|
||||
properties.optimal_tiling_features.blit_dst_bit = true;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -687,20 +698,48 @@ pub fn getImageFormatProperties(
|
||||
image_type: vk.ImageType,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
_: vk.ImageCreateFlags,
|
||||
flags: vk.ImageCreateFlags,
|
||||
) VkError!vk.ImageFormatProperties {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (!try self.isFormatSupported(format, image_type, tiling, usage))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const properties: vk.ImageFormatProperties = .{
|
||||
.max_extent = .{ .width = 0, .height = 0, .depth = 1 },
|
||||
var properties: vk.ImageFormatProperties = .{
|
||||
.max_extent = .{ .width = 1, .height = 1, .depth = 1 },
|
||||
.max_mip_levels = 1,
|
||||
.max_array_layers = 1,
|
||||
.max_array_layers = interface.props.limits.max_image_array_layers,
|
||||
.sample_counts = .{ .@"1_bit" = true },
|
||||
.max_resource_size = std.math.maxInt(u32),
|
||||
};
|
||||
|
||||
switch (image_type) {
|
||||
.@"1d" => {
|
||||
properties.max_extent.width = interface.props.limits.max_image_dimension_1d;
|
||||
properties.max_mip_levels = std.math.log2_int(u32, properties.max_extent.width) + 1;
|
||||
},
|
||||
.@"2d" => {
|
||||
const dimension = if (flags.cube_compatible_bit)
|
||||
interface.props.limits.max_image_dimension_cube
|
||||
else
|
||||
interface.props.limits.max_image_dimension_2d;
|
||||
properties.max_extent.width = dimension;
|
||||
properties.max_extent.height = dimension;
|
||||
properties.max_mip_levels = std.math.log2_int(u32, dimension) + 1;
|
||||
},
|
||||
.@"3d" => {
|
||||
const dimension = interface.props.limits.max_image_dimension_3d;
|
||||
properties.max_extent = .{ .width = dimension, .height = dimension, .depth = dimension };
|
||||
properties.max_mip_levels = std.math.log2_int(u32, dimension) + 1;
|
||||
properties.max_array_layers = 1;
|
||||
},
|
||||
else => return VkError.FormatNotSupported,
|
||||
}
|
||||
|
||||
if (tiling == .linear) {
|
||||
properties.max_mip_levels = 1;
|
||||
properties.max_array_layers = 1;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -827,10 +866,10 @@ fn cpuid(leaf_id: u32, subleaf_id: u32) CpuidRegs {
|
||||
}
|
||||
}
|
||||
|
||||
var eax: u32 = undefined;
|
||||
var ebx: u32 = undefined;
|
||||
var ecx: u32 = undefined;
|
||||
var edx: u32 = undefined;
|
||||
var eax: u32 = 0;
|
||||
var ebx: u32 = 0;
|
||||
var ecx: u32 = 0;
|
||||
var edx: u32 = 0;
|
||||
|
||||
asm volatile ("cpuid"
|
||||
: [_] "={eax}" (eax),
|
||||
|
||||
@@ -8,6 +8,7 @@ const Self = @This();
|
||||
pub const Interface = base.Pipeline;
|
||||
|
||||
interface: Interface,
|
||||
host_allocator: base.VulkanAllocator,
|
||||
|
||||
pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
@@ -15,8 +16,12 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
|
||||
|
||||
var interface = try Interface.initCompute(device, allocator, cache, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.host_allocator = base.VulkanAllocator.from(allocator).clone(),
|
||||
};
|
||||
errdefer self.interface.layout.unref(allocator);
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -27,7 +32,12 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
|
||||
var interface = try Interface.initGraphics(device, allocator, cache, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.host_allocator = base.VulkanAllocator.from(allocator).clone(),
|
||||
};
|
||||
errdefer self.interface.layout.unref(allocator);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PipelineLayout;
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.QueryPool;
|
||||
|
||||
+90
-12
@@ -2,7 +2,11 @@ const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
|
||||
const FlintCommandBuffer = @import("FlintCommandBuffer.zig");
|
||||
const FlintDevice = @import("FlintDevice.zig");
|
||||
const FlintFence = @import("FlintFence.zig");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
@@ -10,24 +14,35 @@ const Self = @This();
|
||||
pub const Interface = base.Queue;
|
||||
|
||||
interface: Interface,
|
||||
completion: *FlintFence,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, device, index, family_index, flags);
|
||||
const completion = try FlintFence.create(device, allocator, &.{
|
||||
.s_type = .fence_create_info,
|
||||
.p_next = null,
|
||||
.flags = .{ .signaled_bit = true },
|
||||
});
|
||||
errdefer completion.interface.destroy(allocator);
|
||||
interface.dispatch_table = &.{
|
||||
.bindSparse = bindSparse,
|
||||
.submit = submit,
|
||||
.waitIdle = waitIdle,
|
||||
};
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.completion = completion,
|
||||
};
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.completion.interface.destroy(allocator);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
@@ -39,26 +54,89 @@ pub fn bindSparse(interface: *Interface, info: []const vk.BindSparseInfo, fence:
|
||||
}
|
||||
|
||||
pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, fence: ?*base.Fence) VkError!void {
|
||||
_ = interface;
|
||||
for (infos) |info| {
|
||||
for (info.wait_semaphores.items) |semaphore| {
|
||||
try semaphore.wait();
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
const allocator = interface.host_allocator.allocator();
|
||||
|
||||
try self.completion.interface.reset();
|
||||
|
||||
for (infos, 0..) |info, info_index| {
|
||||
const last_info = info_index + 1 == infos.len;
|
||||
const request_count = @max(info.command_buffers.items.len, 1);
|
||||
|
||||
for (0..request_count) |request_index| {
|
||||
const first_request = request_index == 0;
|
||||
const last_request = request_index + 1 == request_count;
|
||||
|
||||
var syncs = std.ArrayList(kmd.SyncDependency).empty;
|
||||
defer syncs.deinit(allocator);
|
||||
|
||||
if (first_request) {
|
||||
for (info.wait_semaphores.items) |base_semaphore| {
|
||||
const semaphore: *FlintBinarySemaphore = @alignCast(@fieldParentPtr("interface", base_semaphore));
|
||||
syncs.append(allocator, .{ .handle = semaphore.handle, .wait = true }) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
}
|
||||
|
||||
for (info.command_buffers.items) |command_buffer| {
|
||||
if (last_request) {
|
||||
for (info.signal_semaphores.items) |base_semaphore| {
|
||||
const semaphore: *FlintBinarySemaphore = @alignCast(@fieldParentPtr("interface", base_semaphore));
|
||||
syncs.append(allocator, .{ .handle = semaphore.handle, .signal = true }) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
if (last_info) {
|
||||
syncs.append(allocator, .{ .handle = self.completion.handle, .signal = true }) catch return VkError.OutOfHostMemory;
|
||||
if (fence) |base_fence| {
|
||||
const flint_fence: *FlintFence = @alignCast(@fieldParentPtr("interface", base_fence));
|
||||
syncs.append(allocator, .{ .handle = flint_fence.handle, .signal = true }) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info.command_buffers.items.len == 0) {
|
||||
try device.kmd.submitBatch(
|
||||
interface.owner.io(),
|
||||
allocator,
|
||||
&.{},
|
||||
&.{},
|
||||
syncs.items,
|
||||
);
|
||||
} else {
|
||||
const command_buffer = info.command_buffers.items[request_index];
|
||||
const intel_command_buffer: *FlintCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
|
||||
_ = intel_command_buffer;
|
||||
try intel_command_buffer.submitGpuBatch(syncs.items);
|
||||
}
|
||||
|
||||
for (info.signal_semaphores.items) |semaphore| {
|
||||
try semaphore.signal();
|
||||
if (first_request) {
|
||||
for (info.wait_semaphores.items) |base_semaphore| {
|
||||
const semaphore: *FlintBinarySemaphore = @alignCast(@fieldParentPtr("interface", base_semaphore));
|
||||
try FlintBinarySemaphore.reset(&semaphore.interface);
|
||||
}
|
||||
}
|
||||
if (fence) |value| {
|
||||
try value.signal();
|
||||
}
|
||||
}
|
||||
|
||||
if (infos.len == 0) {
|
||||
var syncs: [2]kmd.SyncDependency = undefined;
|
||||
var sync_count: usize = 1;
|
||||
syncs[0] = .{ .handle = self.completion.handle, .signal = true };
|
||||
if (fence) |base_fence| {
|
||||
const flint_fence: *FlintFence = @alignCast(@fieldParentPtr("interface", base_fence));
|
||||
syncs[sync_count] = .{ .handle = flint_fence.handle, .signal = true };
|
||||
sync_count += 1;
|
||||
}
|
||||
|
||||
try device.kmd.submitBatch(
|
||||
interface.owner.io(),
|
||||
allocator,
|
||||
&.{},
|
||||
&.{},
|
||||
syncs[0..sync_count],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn waitIdle(interface: *Interface) VkError!void {
|
||||
_ = interface;
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
try self.completion.interface.wait(std.math.maxInt(u64));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.RenderPass;
|
||||
|
||||
@@ -8,6 +8,7 @@ const Self = @This();
|
||||
pub const Interface = base.ShaderModule;
|
||||
|
||||
interface: Interface,
|
||||
code: []u32,
|
||||
ref_count: std.atomic.Value(usize),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*Self {
|
||||
@@ -16,9 +17,13 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
if (info.code_size % @sizeOf(u32) != 0) return VkError.ValidationFailed;
|
||||
const code = allocator.dupe(u32, info.p_code[0 .. info.code_size / @sizeOf(u32)]) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.free(code);
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.code = code,
|
||||
.ref_count = std.atomic.Value(usize).init(1),
|
||||
};
|
||||
return self;
|
||||
@@ -30,6 +35,7 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
}
|
||||
|
||||
pub fn drop(self: *Self, allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.code);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const FlintDeviceMemory = @import("FlintDeviceMemory.zig");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
memory: *FlintDeviceMemory,
|
||||
offset: vk.DeviceSize,
|
||||
size: vk.DeviceSize,
|
||||
|
||||
pub fn fromBuffer(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self {
|
||||
const base_memory = buffer.memory orelse return VkError.InvalidDeviceMemoryDrv;
|
||||
const bound, const bound_overflow = @addWithOverflow(offset, size);
|
||||
if (bound_overflow != 0 or bound > buffer.size) return VkError.ValidationFailed;
|
||||
|
||||
const memory_offset, const memory_offset_overflow = @addWithOverflow(buffer.offset, offset);
|
||||
if (memory_offset_overflow != 0) return VkError.ValidationFailed;
|
||||
|
||||
return fromMemory(base_memory, memory_offset, size);
|
||||
}
|
||||
|
||||
pub fn fromImage(image: *base.Image, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self {
|
||||
const base_memory = image.memory orelse return VkError.InvalidDeviceMemoryDrv;
|
||||
const memory_offset, const memory_offset_overflow = @addWithOverflow(image.memory_offset, offset);
|
||||
if (memory_offset_overflow != 0) return VkError.ValidationFailed;
|
||||
|
||||
return fromMemory(base_memory, memory_offset, size);
|
||||
}
|
||||
|
||||
pub fn fromMemory(base_memory: *base.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!Self {
|
||||
const memory: *FlintDeviceMemory = @alignCast(@fieldParentPtr("interface", base_memory));
|
||||
if (offset > base_memory.size or size > base_memory.size - offset)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
return .{
|
||||
.memory = memory,
|
||||
.offset = offset,
|
||||
.size = size,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const FlintImage = @import("FlintImage.zig");
|
||||
const FlintCommandBuffer = @import("FlintCommandBuffer.zig");
|
||||
const MemoryRange = @import("MemoryRange.zig");
|
||||
|
||||
const copy = @import("copy_commands.zig");
|
||||
|
||||
pub fn blitImageRegion(cmd: *FlintCommandBuffer, src: *base.Image, dst: *base.Image, region: vk.ImageBlit) VkError!void {
|
||||
if (region.src_subresource.mip_level >= src.mip_levels or
|
||||
region.dst_subresource.mip_level >= dst.mip_levels)
|
||||
return VkError.ValidationFailed;
|
||||
if (src.samples.toInt() != 1 or dst.samples.toInt() != 1)
|
||||
return VkError.ValidationFailed;
|
||||
if (src.image_type != dst.image_type)
|
||||
return VkError.FeatureNotPresent;
|
||||
try validateAspect(src, region.src_subresource.aspect_mask);
|
||||
try validateAspect(dst, region.dst_subresource.aspect_mask);
|
||||
if (region.src_subresource.aspect_mask != region.dst_subresource.aspect_mask)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const src_image: *FlintImage = @alignCast(@fieldParentPtr("interface", src));
|
||||
const dst_image: *FlintImage = @alignCast(@fieldParentPtr("interface", dst));
|
||||
const src_format = src.formatFromAspect(region.src_subresource.aspect_mask);
|
||||
const dst_format = dst.formatFromAspect(region.dst_subresource.aspect_mask);
|
||||
const src_texel_size = base.format.texelSize(src_format);
|
||||
const dst_texel_size = base.format.texelSize(dst_format);
|
||||
|
||||
if (base.format.isCompressed(src_format) or base.format.isCompressed(dst_format))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
// xy_src_copy_blt does not perform component conversion. Different Vulkan
|
||||
// names are safe only when they describe the same bytes and values.
|
||||
if (!bitwiseCompatibleFormats(src_format, dst_format) or src_texel_size != dst_texel_size)
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
if ((base.format.isDepth(src.format) or base.format.isStencil(src.format) or
|
||||
base.format.isDepth(dst.format) or base.format.isStencil(dst.format)) and
|
||||
src.format != dst.format)
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const src_extent = src_image.getMipLevelExtent(region.src_subresource.mip_level);
|
||||
const dst_extent = dst_image.getMipLevelExtent(region.dst_subresource.mip_level);
|
||||
try validateOffsets(region.src_offsets, src_extent);
|
||||
try validateOffsets(region.dst_offsets, dst_extent);
|
||||
|
||||
const src_layer_count = try resolveLayerCount(src, region.src_subresource);
|
||||
const dst_layer_count = try resolveLayerCount(dst, region.dst_subresource);
|
||||
if (src_layer_count != dst_layer_count)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
var src_0 = region.src_offsets[0];
|
||||
var src_1 = region.src_offsets[1];
|
||||
var dst_0 = region.dst_offsets[0];
|
||||
var dst_1 = region.dst_offsets[1];
|
||||
inline for (.{ "x", "y", "z" }) |field| {
|
||||
if (@field(dst_0, field) > @field(dst_1, field)) {
|
||||
std.mem.swap(i32, &@field(src_0, field), &@field(src_1, field));
|
||||
std.mem.swap(i32, &@field(dst_0, field), &@field(dst_1, field));
|
||||
}
|
||||
}
|
||||
|
||||
if (dst_0.x == dst_1.x or dst_0.y == dst_1.y or dst_0.z == dst_1.z or
|
||||
src_0.x == src_1.x or src_0.y == src_1.y or src_0.z == src_1.z)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const copy_whole_rows = src_1.x - src_0.x == dst_1.x - dst_0.x and src_1.x > src_0.x;
|
||||
for (0..src_layer_count) |layer| {
|
||||
const src_layer = region.src_subresource.base_array_layer + @as(u32, @intCast(layer));
|
||||
const dst_layer = region.dst_subresource.base_array_layer + @as(u32, @intCast(layer));
|
||||
|
||||
var dst_z = dst_0.z;
|
||||
while (dst_z < dst_1.z) : (dst_z += 1) {
|
||||
const src_z = nearestBlitCoordinate(src_0.z, src_1.z, dst_0.z, dst_1.z, dst_z, src_extent.depth);
|
||||
var dst_y = dst_0.y;
|
||||
while (dst_y < dst_1.y) : (dst_y += 1) {
|
||||
const src_y = nearestBlitCoordinate(src_0.y, src_1.y, dst_0.y, dst_1.y, dst_y, src_extent.height);
|
||||
|
||||
if (copy_whole_rows) {
|
||||
const src_offset = try imageTexelOffset(src_image, region.src_subresource.aspect_mask, region.src_subresource.mip_level, src_layer, @intCast(src_0.x), src_y, src_z);
|
||||
const dst_offset = try imageTexelOffset(dst_image, region.dst_subresource.aspect_mask, region.dst_subresource.mip_level, dst_layer, @intCast(dst_0.x), @intCast(dst_y), @intCast(dst_z));
|
||||
const size: usize = @as(usize, @intCast(dst_1.x - dst_0.x)) * dst_texel_size;
|
||||
try copy.emitLinearCopy(
|
||||
cmd,
|
||||
try MemoryRange.fromImage(src, src_offset, size),
|
||||
try MemoryRange.fromImage(dst, dst_offset, size),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
var dst_x = dst_0.x;
|
||||
while (dst_x < dst_1.x) : (dst_x += 1) {
|
||||
const src_x = nearestBlitCoordinate(src_0.x, src_1.x, dst_0.x, dst_1.x, dst_x, src_extent.width);
|
||||
const src_offset = try imageTexelOffset(src_image, region.src_subresource.aspect_mask, region.src_subresource.mip_level, src_layer, src_x, src_y, src_z);
|
||||
const dst_offset = try imageTexelOffset(dst_image, region.dst_subresource.aspect_mask, region.dst_subresource.mip_level, dst_layer, @intCast(dst_x), @intCast(dst_y), @intCast(dst_z));
|
||||
try copy.emitLinearCopy(
|
||||
cmd,
|
||||
try MemoryRange.fromImage(src, src_offset, src_texel_size),
|
||||
try MemoryRange.fromImage(dst, dst_offset, dst_texel_size),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validateAspect(image: *const base.Image, aspect: vk.ImageAspectFlags) VkError!void {
|
||||
const valid_aspects = base.format.toAspect(image.format);
|
||||
if (aspect.toInt() == 0 or @popCount(aspect.toInt()) != 1 or
|
||||
aspect.subtract(valid_aspects).toInt() != 0)
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
fn validateOffsets(offsets: [2]vk.Offset3D, extent: vk.Extent3D) VkError!void {
|
||||
for (offsets) |offset| {
|
||||
if (offset.x < 0 or offset.y < 0 or offset.z < 0 or
|
||||
offset.x > extent.width or offset.y > extent.height or offset.z > extent.depth)
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveLayerCount(image: *const base.Image, subresource: vk.ImageSubresourceLayers) VkError!u32 {
|
||||
if (subresource.base_array_layer >= image.array_layers)
|
||||
return VkError.ValidationFailed;
|
||||
const available = image.array_layers - subresource.base_array_layer;
|
||||
const count = if (subresource.layer_count == vk.REMAINING_ARRAY_LAYERS)
|
||||
available
|
||||
else
|
||||
subresource.layer_count;
|
||||
if (count == 0 or count > available)
|
||||
return VkError.ValidationFailed;
|
||||
return count;
|
||||
}
|
||||
|
||||
fn bitwiseCompatibleFormats(src: vk.Format, dst: vk.Format) bool {
|
||||
if (src == dst) return true;
|
||||
|
||||
// On little-endian the packed A8B8G8R8 formats have the same byte
|
||||
// representation and component interpretation as their R8G8B8A8 peers.
|
||||
return switch (src) {
|
||||
.r8g8b8a8_unorm => dst == .a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_unorm_pack32 => dst == .r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm => dst == .a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32 => dst == .r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uint => dst == .a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_uint_pack32 => dst == .r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint => dst == .a8b8g8r8_sint_pack32,
|
||||
.a8b8g8r8_sint_pack32 => dst == .r8g8b8a8_sint,
|
||||
.r8g8b8a8_srgb => dst == .a8b8g8r8_srgb_pack32,
|
||||
.a8b8g8r8_srgb_pack32 => dst == .r8g8b8a8_srgb,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn nearestBlitCoordinate(src_0: i32, src_1: i32, dst_0: i32, dst_1: i32, dst: i32, extent: u32) usize {
|
||||
const numerator = @as(i64, 2 * (dst - dst_0) + 1) * @as(i64, src_1 - src_0);
|
||||
const denominator: i64 = 2 * (dst_1 - dst_0);
|
||||
const coordinate = @as(i64, src_0) + @divFloor(numerator, denominator);
|
||||
return @intCast(std.math.clamp(coordinate, 0, @as(i64, extent) - 1));
|
||||
}
|
||||
|
||||
fn imageTexelOffset(image: *const FlintImage, aspect: vk.ImageAspectFlags, mip_level: u32, layer: u32, x: usize, y: usize, z: usize) VkError!usize {
|
||||
const format = image.interface.formatFromAspect(aspect);
|
||||
return try image.getSubresourceOffset(aspect, mip_level, layer) +
|
||||
z * image.interface.getSliceMemSizeForMipLevel(aspect, mip_level) +
|
||||
y * image.interface.getRowPitchMemSizeForMipLevel(aspect, mip_level) +
|
||||
x * base.format.texelSize(format);
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const kmd = @import("kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const FlintImage = @import("FlintImage.zig");
|
||||
const FlintCommandBuffer = @import("FlintCommandBuffer.zig");
|
||||
const MemoryRange = @import("MemoryRange.zig");
|
||||
|
||||
pub fn emitLinearCopy(cmd: *FlintCommandBuffer, src: MemoryRange, dst: MemoryRange) VkError!void {
|
||||
if (src.size != dst.size) return VkError.ValidationFailed;
|
||||
|
||||
var copied: vk.DeviceSize = 0;
|
||||
while (copied < src.size) {
|
||||
const chunk = @min(src.size - copied, kmd.max_blt_span);
|
||||
const src_chunk: MemoryRange = .{ .memory = src.memory, .offset = src.offset + copied, .size = chunk };
|
||||
const dst_chunk: MemoryRange = .{ .memory = dst.memory, .offset = dst.offset + copied, .size = chunk };
|
||||
const width: u32 = @intCast(chunk);
|
||||
|
||||
try cmd.emit(kmd.xy_src_copy_blt | kmd.xy_blt_write_alpha | kmd.xy_blt_write_rgb);
|
||||
try cmd.emit(kmd.blt_depth_8 | kmd.rop_source_copy | width);
|
||||
try cmd.emit(0);
|
||||
try cmd.emit((1 << 16) | width);
|
||||
try cmd.emitRelocatedAddress(dst_chunk, false, true);
|
||||
try cmd.emit(0);
|
||||
try cmd.emit(width);
|
||||
try cmd.emitRelocatedAddress(src_chunk, true, false);
|
||||
|
||||
copied += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyBufferImage(cmd: *FlintCommandBuffer, buffer: *base.Buffer, image_interface: *base.Image, region: vk.BufferImageCopy, image_is_dst: bool) VkError!void {
|
||||
if (region.image_extent.width == 0 or region.image_extent.height == 0 or region.image_extent.depth == 0)
|
||||
return;
|
||||
if (region.image_offset.x < 0 or region.image_offset.y < 0 or region.image_offset.z < 0)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const image: *FlintImage = @alignCast(@fieldParentPtr("interface", image_interface));
|
||||
const format = image_interface.formatFromAspect(region.image_subresource.aspect_mask);
|
||||
const bytes_per_block = base.format.texelSize(format);
|
||||
const block_width = base.format.blockWidth(format);
|
||||
const block_height = base.format.blockHeight(format);
|
||||
|
||||
const buffer_width = if (region.buffer_row_length == 0) region.image_extent.width else region.buffer_row_length;
|
||||
const buffer_height = if (region.buffer_image_height == 0) region.image_extent.height else region.buffer_image_height;
|
||||
const buffer_row_pitch = base.format.pitchMemSize(format, buffer_width);
|
||||
const buffer_slice_pitch = base.format.sliceMemSize(format, buffer_width, buffer_height);
|
||||
const copy_row_size = base.format.pitchMemSize(format, region.image_extent.width);
|
||||
const copy_rows = base.format.blockCountY(format, region.image_extent.height);
|
||||
|
||||
const layer_count = if (region.image_subresource.layer_count == vk.REMAINING_ARRAY_LAYERS)
|
||||
image_interface.array_layers - region.image_subresource.base_array_layer
|
||||
else
|
||||
region.image_subresource.layer_count;
|
||||
if (layer_count == 0 or layer_count > image_interface.array_layers - region.image_subresource.base_array_layer)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const image_row_pitch = image_interface.getRowPitchMemSizeForMipLevel(region.image_subresource.aspect_mask, region.image_subresource.mip_level);
|
||||
const image_slice_pitch = image_interface.getSliceMemSizeForMipLevel(region.image_subresource.aspect_mask, region.image_subresource.mip_level);
|
||||
const image_x_offset = @divFloor(@as(usize, @intCast(region.image_offset.x)), block_width) * bytes_per_block;
|
||||
const image_y_offset = @divFloor(@as(usize, @intCast(region.image_offset.y)), block_height);
|
||||
|
||||
for (0..layer_count) |layer| {
|
||||
const image_subresource_offset = try image.getSubresourceOffset(
|
||||
region.image_subresource.aspect_mask,
|
||||
region.image_subresource.mip_level,
|
||||
region.image_subresource.base_array_layer + @as(u32, @intCast(layer)),
|
||||
);
|
||||
|
||||
for (0..region.image_extent.depth) |z| {
|
||||
for (0..copy_rows) |row| {
|
||||
const buffer_offset = region.buffer_offset +
|
||||
(layer * region.image_extent.depth + z) * buffer_slice_pitch +
|
||||
row * buffer_row_pitch;
|
||||
const image_offset = image_subresource_offset +
|
||||
(@as(usize, @intCast(region.image_offset.z)) + z) * image_slice_pitch +
|
||||
(image_y_offset + row) * image_row_pitch +
|
||||
image_x_offset;
|
||||
|
||||
const buffer_range = try MemoryRange.fromBuffer(buffer, buffer_offset, copy_row_size);
|
||||
const image_range = try MemoryRange.fromImage(image_interface, image_offset, copy_row_size);
|
||||
if (image_is_dst)
|
||||
try emitLinearCopy(cmd, buffer_range, image_range)
|
||||
else
|
||||
try emitLinearCopy(cmd, image_range, buffer_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyImage(cmd: *FlintCommandBuffer, src_interface: *base.Image, dst_interface: *base.Image, region: vk.ImageCopy) VkError!void {
|
||||
const depth_stencil: vk.ImageAspectFlags = .{ .depth_bit = true, .stencil_bit = true };
|
||||
if (region.src_subresource.aspect_mask == depth_stencil and region.dst_subresource.aspect_mask == depth_stencil) {
|
||||
var single_aspect_region = region;
|
||||
single_aspect_region.src_subresource.aspect_mask = .{ .depth_bit = true };
|
||||
single_aspect_region.dst_subresource.aspect_mask = .{ .depth_bit = true };
|
||||
try copyImageSingleAspect(cmd, src_interface, dst_interface, single_aspect_region);
|
||||
|
||||
single_aspect_region.src_subresource.aspect_mask = .{ .stencil_bit = true };
|
||||
single_aspect_region.dst_subresource.aspect_mask = .{ .stencil_bit = true };
|
||||
try copyImageSingleAspect(cmd, src_interface, dst_interface, single_aspect_region);
|
||||
return;
|
||||
}
|
||||
|
||||
try copyImageSingleAspect(cmd, src_interface, dst_interface, region);
|
||||
}
|
||||
|
||||
fn copyImageSingleAspect(cmd: *FlintCommandBuffer, src_interface: *base.Image, dst_interface: *base.Image, region: vk.ImageCopy) VkError!void {
|
||||
if (region.extent.width == 0 or region.extent.height == 0 or region.extent.depth == 0)
|
||||
return;
|
||||
if (region.src_offset.x < 0 or region.src_offset.y < 0 or region.src_offset.z < 0 or
|
||||
region.dst_offset.x < 0 or region.dst_offset.y < 0 or region.dst_offset.z < 0)
|
||||
return VkError.ValidationFailed;
|
||||
if (@popCount(region.src_subresource.aspect_mask.toInt()) != 1 or
|
||||
@popCount(region.dst_subresource.aspect_mask.toInt()) != 1)
|
||||
return VkError.ValidationFailed;
|
||||
if (region.src_subresource.aspect_mask.subtract(base.format.toAspect(src_interface.format)).toInt() != 0 or
|
||||
region.dst_subresource.aspect_mask.subtract(base.format.toAspect(dst_interface.format)).toInt() != 0)
|
||||
return VkError.ValidationFailed;
|
||||
if (src_interface.samples.toInt() != dst_interface.samples.toInt())
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const src: *FlintImage = @alignCast(@fieldParentPtr("interface", src_interface));
|
||||
const dst: *FlintImage = @alignCast(@fieldParentPtr("interface", dst_interface));
|
||||
const src_format = src_interface.formatFromAspect(region.src_subresource.aspect_mask);
|
||||
const dst_format = dst_interface.formatFromAspect(region.dst_subresource.aspect_mask);
|
||||
const bytes_per_block = base.format.texelSize(src_format);
|
||||
|
||||
if (bytes_per_block != base.format.texelSize(dst_format))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const src_block_width = base.format.blockWidth(src_format);
|
||||
const src_block_height = base.format.blockHeight(src_format);
|
||||
const dst_block_width = base.format.blockWidth(dst_format);
|
||||
const dst_block_height = base.format.blockHeight(dst_format);
|
||||
if (base.format.isCompressed(src_format) and base.format.isCompressed(dst_format) and
|
||||
(src_block_width != dst_block_width or src_block_height != dst_block_height))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const src_x: usize = @intCast(region.src_offset.x);
|
||||
const src_y: usize = @intCast(region.src_offset.y);
|
||||
const src_z: usize = @intCast(region.src_offset.z);
|
||||
const dst_x: usize = @intCast(region.dst_offset.x);
|
||||
const dst_y: usize = @intCast(region.dst_offset.y);
|
||||
const dst_z: usize = @intCast(region.dst_offset.z);
|
||||
if (@mod(src_x, src_block_width) != 0 or @mod(src_y, src_block_height) != 0 or
|
||||
@mod(dst_x, dst_block_width) != 0 or @mod(dst_y, dst_block_height) != 0)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
if (region.src_subresource.mip_level >= src_interface.mip_levels or
|
||||
region.dst_subresource.mip_level >= dst_interface.mip_levels)
|
||||
return VkError.ValidationFailed;
|
||||
const src_extent = src.getMipLevelExtent(region.src_subresource.mip_level);
|
||||
const dst_extent = dst.getMipLevelExtent(region.dst_subresource.mip_level);
|
||||
const copy_blocks_x = base.format.blockCountX(src_format, region.extent.width);
|
||||
const copy_blocks_y = base.format.blockCountY(src_format, region.extent.height);
|
||||
const src_block_x = src_x / src_block_width;
|
||||
const src_block_y = src_y / src_block_height;
|
||||
const dst_block_x = dst_x / dst_block_width;
|
||||
const dst_block_y = dst_y / dst_block_height;
|
||||
if (src_block_x + copy_blocks_x > base.format.blockCountX(src_format, src_extent.width) or
|
||||
src_block_y + copy_blocks_y > base.format.blockCountY(src_format, src_extent.height) or
|
||||
dst_block_x + copy_blocks_x > base.format.blockCountX(dst_format, dst_extent.width) or
|
||||
dst_block_y + copy_blocks_y > base.format.blockCountY(dst_format, dst_extent.height) or
|
||||
src_z + region.extent.depth > src_extent.depth or
|
||||
dst_z + region.extent.depth > dst_extent.depth)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const src_layer_count = try resolveLayerCount(src_interface, region.src_subresource);
|
||||
const dst_layer_count = try resolveLayerCount(dst_interface, region.dst_subresource);
|
||||
if (src_layer_count != dst_layer_count)
|
||||
return VkError.ValidationFailed;
|
||||
|
||||
const src_row_pitch = src_interface.getRowPitchMemSizeForMipLevel(region.src_subresource.aspect_mask, region.src_subresource.mip_level);
|
||||
const src_slice_pitch = src_interface.getSliceMemSizeForMipLevel(region.src_subresource.aspect_mask, region.src_subresource.mip_level);
|
||||
const src_sample_pitch = src.getMipLevelSize(region.src_subresource.aspect_mask, region.src_subresource.mip_level);
|
||||
const dst_row_pitch = dst_interface.getRowPitchMemSizeForMipLevel(region.dst_subresource.aspect_mask, region.dst_subresource.mip_level);
|
||||
const dst_slice_pitch = dst_interface.getSliceMemSizeForMipLevel(region.dst_subresource.aspect_mask, region.dst_subresource.mip_level);
|
||||
const dst_sample_pitch = dst.getMipLevelSize(region.dst_subresource.aspect_mask, region.dst_subresource.mip_level);
|
||||
const copy_row_size = copy_blocks_x * bytes_per_block;
|
||||
|
||||
for (0..src_layer_count) |layer| {
|
||||
const src_subresource_offset = try src.getSubresourceOffset(
|
||||
region.src_subresource.aspect_mask,
|
||||
region.src_subresource.mip_level,
|
||||
region.src_subresource.base_array_layer + @as(u32, @intCast(layer)),
|
||||
);
|
||||
const dst_subresource_offset = try dst.getSubresourceOffset(
|
||||
region.dst_subresource.aspect_mask,
|
||||
region.dst_subresource.mip_level,
|
||||
region.dst_subresource.base_array_layer + @as(u32, @intCast(layer)),
|
||||
);
|
||||
|
||||
for (0..src_interface.samples.toInt()) |sample| {
|
||||
for (0..region.extent.depth) |z| {
|
||||
for (0..copy_blocks_y) |row| {
|
||||
const src_offset = src_subresource_offset +
|
||||
sample * src_sample_pitch +
|
||||
(src_z + z) * src_slice_pitch +
|
||||
(src_block_y + row) * src_row_pitch +
|
||||
src_block_x * bytes_per_block;
|
||||
const dst_offset = dst_subresource_offset +
|
||||
sample * dst_sample_pitch +
|
||||
(dst_z + z) * dst_slice_pitch +
|
||||
(dst_block_y + row) * dst_row_pitch +
|
||||
dst_block_x * bytes_per_block;
|
||||
try emitLinearCopy(
|
||||
cmd,
|
||||
try MemoryRange.fromImage(src_interface, src_offset, copy_row_size),
|
||||
try MemoryRange.fromImage(dst_interface, dst_offset, copy_row_size),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveLayerCount(image: *const base.Image, subresource: vk.ImageSubresourceLayers) VkError!u32 {
|
||||
if (subresource.base_array_layer >= image.array_layers)
|
||||
return VkError.ValidationFailed;
|
||||
const layer_count = if (subresource.layer_count == vk.REMAINING_ARRAY_LAYERS)
|
||||
image.array_layers - subresource.base_array_layer
|
||||
else
|
||||
subresource.layer_count;
|
||||
if (layer_count == 0 or layer_count > image.array_layers - subresource.base_array_layer)
|
||||
return VkError.ValidationFailed;
|
||||
return layer_count;
|
||||
}
|
||||
|
||||
pub fn copyRangeFromRegion(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!MemoryRange {
|
||||
return MemoryRange.fromBuffer(buffer, offset, size);
|
||||
}
|
||||
|
||||
pub fn fillRange(buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!MemoryRange {
|
||||
const resolved_size = if (size == vk.WHOLE_SIZE) blk: {
|
||||
if (offset > buffer.size) return VkError.ValidationFailed;
|
||||
break :blk std.mem.alignBackward(vk.DeviceSize, buffer.size - offset, @sizeOf(u32));
|
||||
} else blk: {
|
||||
if (size % @sizeOf(u32) != 0) return VkError.ValidationFailed;
|
||||
break :blk size;
|
||||
};
|
||||
|
||||
return MemoryRange.fromBuffer(buffer, offset, resolved_size);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
pub const command_base = 0x40;
|
||||
pub const gem_create = 0x1b;
|
||||
pub const gem_mmap_gtt = 0x24;
|
||||
pub const gem_set_domain = 0x1f;
|
||||
pub const gem_execbuffer2 = 0x29;
|
||||
pub const gem_close = 0x09;
|
||||
|
||||
pub const mmap_offset_wb = 2;
|
||||
pub const gem_domain_cpu = 0x00000001;
|
||||
pub const gem_domain_gtt = 0x00000040;
|
||||
pub const exec_blt = 3 << 0;
|
||||
pub const exec_fence_array: u64 = 1 << 19;
|
||||
pub const exec_fence_wait: u32 = 1 << 0;
|
||||
pub const exec_fence_signal: u32 = 1 << 1;
|
||||
pub const exec_object_write = 1 << 2;
|
||||
pub const mi_flush_dw: u32 = (0x26 << 23) | 3;
|
||||
|
||||
pub const GemCreate = extern struct {
|
||||
size: u64,
|
||||
handle: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
pub const GemMmapOffset = extern struct {
|
||||
handle: u32,
|
||||
pad: u32,
|
||||
offset: u64,
|
||||
flags: u64,
|
||||
extensions: u64,
|
||||
};
|
||||
|
||||
pub const GemClose = extern struct {
|
||||
handle: u32,
|
||||
pad: u32,
|
||||
};
|
||||
|
||||
pub const GemSetDomain = extern struct {
|
||||
handle: u32,
|
||||
read_domains: u32,
|
||||
write_domain: u32,
|
||||
};
|
||||
|
||||
pub const RelocationEntry = extern struct {
|
||||
target_handle: u32,
|
||||
delta: u32,
|
||||
offset: u64,
|
||||
presumed_offset: u64,
|
||||
read_domains: u32,
|
||||
write_domain: u32,
|
||||
};
|
||||
|
||||
pub const ExecObject2 = extern struct {
|
||||
handle: u32,
|
||||
relocation_count: u32,
|
||||
relocs_ptr: u64,
|
||||
alignment: u64,
|
||||
offset: u64,
|
||||
flags: u64,
|
||||
rsvd1: u64,
|
||||
rsvd2: u64,
|
||||
};
|
||||
|
||||
pub const ExecBuffer2 = extern struct {
|
||||
buffers_ptr: u64,
|
||||
buffer_count: u32,
|
||||
batch_start_offset: u32,
|
||||
batch_len: u32,
|
||||
DR1: u32,
|
||||
DR4: u32,
|
||||
num_cliprects: u32,
|
||||
cliprects_ptr: u64,
|
||||
flags: u64,
|
||||
rsvd1: u64,
|
||||
rsvd2: u64,
|
||||
};
|
||||
|
||||
pub const ExecFence = extern struct {
|
||||
handle: u32,
|
||||
flags: u32,
|
||||
};
|
||||
@@ -0,0 +1,247 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const _i915 = @import("i915.zig");
|
||||
const common_kmd = @import("../kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Mapping = struct {
|
||||
bytes: []align(std.heap.page_size_min) u8,
|
||||
|
||||
inline fn slice(self: Mapping, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
const start: usize = @intCast(offset);
|
||||
const len: usize = @intCast(size);
|
||||
return self.bytes[start .. start + len];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Device = struct {
|
||||
card: base.drm.Card,
|
||||
|
||||
pub fn open(io: std.Io, node_path: []const u8) VkError!Device {
|
||||
return .{
|
||||
.card = base.drm.Card.open(io, node_path) catch return VkError.InitializationFailed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Device, io: std.Io) void {
|
||||
self.card.close(io);
|
||||
}
|
||||
|
||||
pub fn allocateMemory(self: *Device, io: std.Io, size: vk.DeviceSize) VkError!Memory {
|
||||
var create = _i915.GemCreate{
|
||||
.size = size,
|
||||
.handle = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
self.card.handle,
|
||||
io,
|
||||
common_kmd.drmIoctlIowr(_i915.command_base + _i915.gem_create, _i915.GemCreate),
|
||||
&create,
|
||||
) catch return VkError.OutOfDeviceMemory;
|
||||
|
||||
var memory = Memory{
|
||||
.handle = create.handle,
|
||||
.size = create.size,
|
||||
.mapping = null,
|
||||
};
|
||||
errdefer memory.deinit(self, io);
|
||||
|
||||
try memory.setDomain(self, io, _i915.gem_domain_cpu, 0);
|
||||
return memory;
|
||||
}
|
||||
|
||||
pub fn submitBatch(self: *Device, io: std.Io, allocator: std.mem.Allocator, commands: []const u32, relocations: []const common_kmd.Relocation, syncs: []const common_kmd.SyncDependency) VkError!void {
|
||||
const trailer_words = 6;
|
||||
const batch_size = (commands.len + trailer_words) * @sizeOf(u32);
|
||||
var batch = try self.allocateMemory(io, batch_size);
|
||||
defer batch.deinit(self, io);
|
||||
|
||||
{
|
||||
const batch_map = try batch.map(self, io, 0, batch_size);
|
||||
const batch_words = std.mem.bytesAsSlice(u32, batch_map);
|
||||
@memcpy(batch_words[0..commands.len], commands);
|
||||
batch_words[commands.len + 0] = _i915.mi_flush_dw;
|
||||
batch_words[commands.len + 1] = 0;
|
||||
batch_words[commands.len + 2] = 0;
|
||||
batch_words[commands.len + 3] = 0;
|
||||
batch_words[commands.len + 4] = 0;
|
||||
batch_words[commands.len + 5] = 0x05000000;
|
||||
batch.unmap();
|
||||
}
|
||||
try batch.flushRange(self, io, 0, batch_size);
|
||||
|
||||
var objects = std.ArrayList(_i915.ExecObject2).empty;
|
||||
defer objects.deinit(allocator);
|
||||
|
||||
var object_handles = std.ArrayList(u32).empty;
|
||||
defer object_handles.deinit(allocator);
|
||||
|
||||
for (relocations) |relocation| {
|
||||
if (std.mem.indexOfScalar(u32, object_handles.items, relocation.target_handle) == null) {
|
||||
object_handles.append(allocator, relocation.target_handle) catch return VkError.OutOfHostMemory;
|
||||
objects.append(allocator, .{
|
||||
.handle = relocation.target_handle,
|
||||
.relocation_count = 0,
|
||||
.relocs_ptr = 0,
|
||||
.alignment = 0,
|
||||
.offset = 0,
|
||||
.flags = if (relocation.write) _i915.exec_object_write else 0,
|
||||
.rsvd1 = 0,
|
||||
.rsvd2 = 0,
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
} else if (relocation.write) {
|
||||
const index = std.mem.indexOfScalar(u32, object_handles.items, relocation.target_handle).?;
|
||||
objects.items[index].flags |= _i915.exec_object_write;
|
||||
}
|
||||
}
|
||||
|
||||
var i915_relocations = std.ArrayList(_i915.RelocationEntry).empty;
|
||||
defer i915_relocations.deinit(allocator);
|
||||
|
||||
for (relocations) |relocation| {
|
||||
i915_relocations.append(allocator, .{
|
||||
.target_handle = relocation.target_handle,
|
||||
.delta = relocation.delta,
|
||||
.offset = relocation.offset,
|
||||
.presumed_offset = 0,
|
||||
.read_domains = 0,
|
||||
.write_domain = 0,
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
objects.append(allocator, .{
|
||||
.handle = batch.handle,
|
||||
.relocation_count = @intCast(i915_relocations.items.len),
|
||||
.relocs_ptr = @intFromPtr(i915_relocations.items.ptr),
|
||||
.alignment = 0,
|
||||
.offset = 0,
|
||||
.flags = 0,
|
||||
.rsvd1 = 0,
|
||||
.rsvd2 = 0,
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
|
||||
var exec_fences = std.ArrayList(_i915.ExecFence).empty;
|
||||
defer exec_fences.deinit(allocator);
|
||||
for (syncs) |sync| {
|
||||
exec_fences.append(allocator, .{
|
||||
.handle = sync.handle,
|
||||
.flags = (if (sync.wait) _i915.exec_fence_wait else 0) | (if (sync.signal) _i915.exec_fence_signal else 0),
|
||||
}) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
|
||||
var execbuffer = _i915.ExecBuffer2{
|
||||
.buffers_ptr = @intFromPtr(objects.items.ptr),
|
||||
.buffer_count = @intCast(objects.items.len),
|
||||
.batch_start_offset = 0,
|
||||
.batch_len = @intCast(batch_size),
|
||||
.DR1 = 0,
|
||||
.DR4 = 0,
|
||||
.num_cliprects = @intCast(exec_fences.items.len),
|
||||
.cliprects_ptr = if (exec_fences.items.len == 0) 0 else @intFromPtr(exec_fences.items.ptr),
|
||||
.flags = _i915.exec_blt | (if (exec_fences.items.len == 0) 0 else _i915.exec_fence_array),
|
||||
.rsvd1 = 0,
|
||||
.rsvd2 = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
self.card.handle,
|
||||
io,
|
||||
common_kmd.drmIoctlIowr(_i915.command_base + _i915.gem_execbuffer2, _i915.ExecBuffer2),
|
||||
&execbuffer,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Memory = struct {
|
||||
handle: u32,
|
||||
size: vk.DeviceSize,
|
||||
mapping: ?Mapping,
|
||||
|
||||
pub fn deinit(self: *Memory, device: *Device, io: std.Io) void {
|
||||
self.unmap();
|
||||
|
||||
var close = _i915.GemClose{
|
||||
.handle = self.handle,
|
||||
.pad = 0,
|
||||
};
|
||||
base.utils.ioctl(device.card.handle, io, common_kmd.drmIoctlIow(_i915.gem_close, _i915.GemClose), &close) catch @panic("Caught an error while handling an error");
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn map(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
if (offset > self.size) return VkError.MemoryMapFailed;
|
||||
const available = self.size - offset;
|
||||
const map_size = if (size == vk.WHOLE_SIZE) available else size;
|
||||
if (map_size > available) return VkError.MemoryMapFailed;
|
||||
if (map_size > std.math.maxInt(usize)) return VkError.MemoryMapFailed;
|
||||
|
||||
if (self.mapping) |mapping| {
|
||||
return mapping.slice(offset, map_size);
|
||||
}
|
||||
|
||||
var mmap_offset = _i915.GemMmapOffset{
|
||||
.handle = self.handle,
|
||||
.pad = 0,
|
||||
.offset = 0,
|
||||
.flags = _i915.mmap_offset_wb,
|
||||
.extensions = 0,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
device.card.handle,
|
||||
io,
|
||||
common_kmd.drmIoctlIowr(_i915.command_base + _i915.gem_mmap_gtt, _i915.GemMmapOffset),
|
||||
&mmap_offset,
|
||||
) catch return VkError.MemoryMapFailed;
|
||||
|
||||
if (self.size > std.math.maxInt(usize)) return VkError.MemoryMapFailed;
|
||||
const full_size: usize = @intCast(self.size);
|
||||
const bytes = std.posix.mmap(
|
||||
null,
|
||||
full_size,
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.{ .TYPE = .SHARED },
|
||||
device.card.handle.handle,
|
||||
@intCast(mmap_offset.offset),
|
||||
) catch return VkError.MemoryMapFailed;
|
||||
|
||||
self.mapping = .{ .bytes = bytes };
|
||||
return self.mapping.?.slice(offset, map_size);
|
||||
}
|
||||
|
||||
pub fn unmap(self: *Memory) void {
|
||||
if (self.mapping) |mapping| {
|
||||
std.posix.munmap(mapping.bytes);
|
||||
self.mapping = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flushRange(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = offset;
|
||||
_ = size;
|
||||
try self.setDomain(device, io, _i915.gem_domain_cpu, 0);
|
||||
}
|
||||
|
||||
pub fn invalidateRange(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = offset;
|
||||
_ = size;
|
||||
try self.setDomain(device, io, _i915.gem_domain_cpu, 0);
|
||||
}
|
||||
|
||||
fn setDomain(self: *Memory, device: *Device, io: std.Io, read_domains: u32, write_domain: u32) VkError!void {
|
||||
var domain = _i915.GemSetDomain{
|
||||
.handle = self.handle,
|
||||
.read_domains = read_domains,
|
||||
.write_domain = write_domain,
|
||||
};
|
||||
base.utils.ioctl(
|
||||
device.card.handle,
|
||||
io,
|
||||
common_kmd.drmIoctlIow(_i915.command_base + _i915.gem_set_domain, _i915.GemSetDomain),
|
||||
&domain,
|
||||
) catch return VkError.DeviceLost;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const KmdType = @import("lib.zig").KmdType;
|
||||
|
||||
const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
|
||||
const i915_kmd = @import("i915/kmd.zig");
|
||||
const xe = @import("xe/kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const ioctl = std.os.linux.IOCTL;
|
||||
|
||||
pub const xy_src_copy_blt: u32 = (2 << 29) | (0x53 << 22) | 8;
|
||||
pub const xy_blt_write_alpha: u32 = 1 << 21;
|
||||
pub const xy_blt_write_rgb: u32 = 1 << 20;
|
||||
pub const mi_store_data_imm_dword: u32 = (0x20 << 23) | 2;
|
||||
pub const blt_depth_8: u32 = 0 << 24;
|
||||
pub const rop_source_copy: u32 = 0xcc << 16;
|
||||
pub const max_blt_span: vk.DeviceSize = 32 * 1024 - 1;
|
||||
|
||||
pub const Relocation = struct {
|
||||
target_handle: u32,
|
||||
offset: u64,
|
||||
delta: u32,
|
||||
read: bool = false,
|
||||
write: bool = false,
|
||||
};
|
||||
|
||||
pub const SyncDependency = struct {
|
||||
handle: u32,
|
||||
wait: bool = false,
|
||||
signal: bool = false,
|
||||
};
|
||||
|
||||
pub const Device = union(KmdType) {
|
||||
invalid: void,
|
||||
i915: i915_kmd.Device,
|
||||
xe: xe.Device,
|
||||
|
||||
pub fn open(io: std.Io, physical_device: *const FlintPhysicalDevice) VkError!Device {
|
||||
return switch (physical_device.kmd_type) {
|
||||
.i915 => .{ .i915 = try i915_kmd.Device.open(io, physical_device.getNodePath()) },
|
||||
.xe => .{ .xe = try xe.Device.open(io, physical_device.getNodePath()) },
|
||||
.invalid => VkError.InitializationFailed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Device, io: std.Io) void {
|
||||
switch (self.*) {
|
||||
.i915 => |*device| device.close(io),
|
||||
.xe => |*device| device.close(io),
|
||||
.invalid => {},
|
||||
}
|
||||
self.* = .{ .invalid = {} };
|
||||
}
|
||||
|
||||
pub fn allocateMemory(self: *Device, io: std.Io, size: vk.DeviceSize) VkError!Memory {
|
||||
return switch (self.*) {
|
||||
.i915 => |*device| .{ .i915 = try device.allocateMemory(io, size) },
|
||||
.xe => |*device| .{ .xe = try device.allocateMemory(io, size) },
|
||||
.invalid => VkError.OutOfDeviceMemory,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn submitBatch(self: *Device, io: std.Io, allocator: std.mem.Allocator, commands: []const u32, relocations: []const Relocation, syncs: []const SyncDependency) VkError!void {
|
||||
return switch (self.*) {
|
||||
.i915 => |*device| device.submitBatch(io, allocator, commands, relocations, syncs),
|
||||
.xe => |*device| device.submitBatch(io, allocator, commands, relocations, syncs),
|
||||
.invalid => VkError.DeviceLost,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn file(self: *Device) VkError!std.Io.File {
|
||||
return switch (self.*) {
|
||||
.i915 => |*device| device.card.handle,
|
||||
.xe => |*device| device.card.handle,
|
||||
.invalid => VkError.DeviceLost,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Memory = union(KmdType) {
|
||||
invalid: void,
|
||||
i915: i915_kmd.Memory,
|
||||
xe: xe.Memory,
|
||||
|
||||
pub fn deinit(self: *Memory, device: *Device, io: std.Io) void {
|
||||
switch (self.*) {
|
||||
.i915 => |*memory| switch (device.*) {
|
||||
.i915 => |*adapter| memory.deinit(adapter, io),
|
||||
else => {},
|
||||
},
|
||||
.xe => |*memory| switch (device.*) {
|
||||
.xe => |*adapter| memory.deinit(adapter, io),
|
||||
else => {},
|
||||
},
|
||||
.invalid => {},
|
||||
}
|
||||
self.* = .{ .invalid = {} };
|
||||
}
|
||||
|
||||
pub fn map(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
return switch (self.*) {
|
||||
.i915 => |*memory| switch (device.*) {
|
||||
.i915 => |*adapter| memory.map(adapter, io, offset, size),
|
||||
else => VkError.MemoryMapFailed,
|
||||
},
|
||||
.xe => |*memory| switch (device.*) {
|
||||
.xe => |*adapter| memory.map(adapter, io, offset, size),
|
||||
else => VkError.MemoryMapFailed,
|
||||
},
|
||||
.invalid => VkError.MemoryMapFailed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unmap(self: *Memory) void {
|
||||
switch (self.*) {
|
||||
.i915 => |*memory| memory.unmap(),
|
||||
.xe => |*memory| memory.unmap(),
|
||||
.invalid => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flushRange(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
return switch (self.*) {
|
||||
.i915 => |*memory| switch (device.*) {
|
||||
.i915 => |*adapter| memory.flushRange(adapter, io, offset, size),
|
||||
else => VkError.InvalidDeviceMemoryDrv,
|
||||
},
|
||||
.xe => |*memory| switch (device.*) {
|
||||
.xe => |*adapter| memory.flushRange(adapter, io, offset, size),
|
||||
else => VkError.InvalidDeviceMemoryDrv,
|
||||
},
|
||||
.invalid => VkError.InvalidDeviceMemoryDrv,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn invalidateRange(self: *Memory, device: *Device, io: std.Io, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
return switch (self.*) {
|
||||
.i915 => |*memory| switch (device.*) {
|
||||
.i915 => |*adapter| memory.invalidateRange(adapter, io, offset, size),
|
||||
else => VkError.InvalidDeviceMemoryDrv,
|
||||
},
|
||||
.xe => |*memory| switch (device.*) {
|
||||
.xe => |*adapter| memory.invalidateRange(adapter, io, offset, size),
|
||||
else => VkError.InvalidDeviceMemoryDrv,
|
||||
},
|
||||
.invalid => VkError.InvalidDeviceMemoryDrv,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn handle(self: *const Memory) VkError!u32 {
|
||||
return switch (self.*) {
|
||||
.i915 => |*memory| memory.handle,
|
||||
.xe => VkError.FeatureNotPresent,
|
||||
.invalid => VkError.InvalidDeviceMemoryDrv,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub inline fn drmIoctlIow(nr: u8, comptime T: type) u32 {
|
||||
return ioctl.IOW('d', nr, T);
|
||||
}
|
||||
|
||||
pub inline fn drmIoctlIowr(nr: u8, comptime T: type) u32 {
|
||||
return ioctl.IOWR('d', nr, T);
|
||||
}
|
||||
+12
-7
@@ -9,6 +9,7 @@ pub const FlintInstance = @import("FlintInstance.zig");
|
||||
pub const FlintDevice = @import("FlintDevice.zig");
|
||||
pub const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
|
||||
pub const FlintQueue = @import("FlintQueue.zig");
|
||||
pub const kmd = @import("kmd.zig");
|
||||
|
||||
pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
|
||||
pub const FlintBuffer = @import("FlintBuffer.zig");
|
||||
@@ -34,23 +35,26 @@ pub const FlintShaderModule = @import("FlintShaderModule.zig");
|
||||
|
||||
pub const Instance = FlintInstance;
|
||||
|
||||
pub const DRIVER_NAME = "Flint";
|
||||
pub const driver_name = "Flint";
|
||||
|
||||
pub const PHYSICAL_DEVICE_DEFAULT_NAME = "Unkown Intel device";
|
||||
pub const physical_device_default_name = "Unkown Intel device";
|
||||
|
||||
pub const INTEL_PCI_VENDOR_ID = 0x8086;
|
||||
pub const intel_pci_vendor_id = 0x8086;
|
||||
|
||||
pub const VULKAN_VERSION = vk.makeApiVersion(
|
||||
pub const vulkan_version = vk.makeApiVersion(
|
||||
0,
|
||||
config.flint_vulkan_version.major,
|
||||
config.flint_vulkan_version.minor,
|
||||
config.flint_vulkan_version.patch,
|
||||
);
|
||||
|
||||
/// GEM buffer objects are page based
|
||||
pub const image_memory_alignment = std.heap.page_size_max;
|
||||
|
||||
pub const KmdType = enum {
|
||||
Invalid,
|
||||
I915,
|
||||
Xe,
|
||||
invalid,
|
||||
i915,
|
||||
xe,
|
||||
};
|
||||
|
||||
pub const std_options = base.std_options;
|
||||
@@ -85,5 +89,6 @@ test {
|
||||
std.testing.refAllDecls(FlintRenderPass);
|
||||
std.testing.refAllDecls(FlintSampler);
|
||||
std.testing.refAllDecls(FlintShaderModule);
|
||||
std.testing.refAllDecls(kmd);
|
||||
std.testing.refAllDecls(base);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
const std = @import("std");
|
||||
|
||||
const PciInfo = struct {
|
||||
id: u16,
|
||||
name: []const u8,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const common_kmd = @import("../kmd.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
pub const Device = struct {
|
||||
card: base.drm.Card,
|
||||
|
||||
pub fn open(io: std.Io, node_path: []const u8) VkError!Device {
|
||||
return .{
|
||||
.card = base.drm.Card.open(io, node_path) catch return VkError.InitializationFailed,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Device, io: std.Io) void {
|
||||
self.card.close(io);
|
||||
}
|
||||
|
||||
pub fn allocateMemory(_: *Device, _: std.Io, _: vk.DeviceSize) VkError!Memory {
|
||||
return VkError.OutOfDeviceMemory;
|
||||
}
|
||||
|
||||
pub fn submitBatch(_: *Device, _: std.Io, _: std.mem.Allocator, _: []const u32, _: []const common_kmd.Relocation, _: []const common_kmd.SyncDependency) VkError!void {
|
||||
return VkError.FeatureNotPresent;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Memory = struct {
|
||||
pub fn deinit(_: *Memory, _: *Device, _: std.Io) void {}
|
||||
|
||||
pub fn map(_: *Memory, _: *Device, _: std.Io, _: vk.DeviceSize, _: vk.DeviceSize) VkError![]u8 {
|
||||
return VkError.MemoryMapFailed;
|
||||
}
|
||||
|
||||
pub fn unmap(_: *Memory) void {}
|
||||
|
||||
pub fn flushRange(_: *Memory, _: *Device, _: std.Io, _: vk.DeviceSize, _: vk.DeviceSize) VkError!void {
|
||||
return VkError.FeatureNotPresent;
|
||||
}
|
||||
|
||||
pub fn invalidateRange(_: *Memory, _: *Device, _: std.Io, _: vk.DeviceSize, _: vk.DeviceSize) VkError!void {
|
||||
return VkError.FeatureNotPresent;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BinarySemaphore;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Buffer;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.BufferCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getMemoryRequirements = getMemoryRequirements,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequirements) void {
|
||||
_ = interface;
|
||||
_ = requirements;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.BufferView;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const proto = lib.proto;
|
||||
|
||||
const VkError = base.VkError;
|
||||
const PhiDeviceMemory = @import("PhiDeviceMemory.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.CommandBuffer;
|
||||
|
||||
interface: Interface,
|
||||
cmd_count: usize,
|
||||
serialized_cmd_count: usize,
|
||||
commands: std.ArrayList(u8),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.CommandBufferAllocateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
interface.dispatch_table = &.{
|
||||
.begin = begin,
|
||||
.beginQuery = beginQuery,
|
||||
.beginRenderPass = beginRenderPass,
|
||||
.bindDescriptorSets = bindDescriptorSets,
|
||||
.bindPipeline = bindPipeline,
|
||||
.bindIndexBuffer = bindIndexBuffer,
|
||||
.bindVertexBuffer = bindVertexBuffer,
|
||||
.blitImage = blitImage,
|
||||
.clearAttachment = clearAttachment,
|
||||
.clearColorImage = clearColorImage,
|
||||
.clearDepthStencilImage = clearDepthStencilImage,
|
||||
.copyBuffer = copyBuffer,
|
||||
.copyBufferToImage = copyBufferToImage,
|
||||
.copyImage = copyImage,
|
||||
.copyImageToBuffer = copyImageToBuffer,
|
||||
.copyQueryPoolResults = copyQueryPoolResults,
|
||||
.dispatch = dispatch,
|
||||
.dispatchBase = dispatchBase,
|
||||
.dispatchIndirect = dispatchIndirect,
|
||||
.draw = draw,
|
||||
.drawIndexed = drawIndexed,
|
||||
.drawIndexedIndirect = drawIndexedIndirect,
|
||||
.drawIndirect = drawIndirect,
|
||||
.end = end,
|
||||
.endQuery = endQuery,
|
||||
.endRenderPass = endRenderPass,
|
||||
.executeCommands = executeCommands,
|
||||
.fillBuffer = fillBuffer,
|
||||
.nextSubpass = nextSubpass,
|
||||
.pipelineBarrier = pipelineBarrier,
|
||||
.pushConstants = pushConstants,
|
||||
.reset = reset,
|
||||
.resetQueryPool = resetQueryPool,
|
||||
.resetEvent = resetEvent,
|
||||
.resolveImage = resolveImage,
|
||||
.setEvent = setEvent,
|
||||
.setBlendConstants = setBlendConstants,
|
||||
.setDepthBias = setDepthBias,
|
||||
.setDepthBounds = setDepthBounds,
|
||||
.setDeviceMask = setDeviceMask,
|
||||
.setLineWidth = setLineWidth,
|
||||
.setScissor = setScissor,
|
||||
.setStencilCompareMask = setStencilCompareMask,
|
||||
.setStencilReference = setStencilReference,
|
||||
.setStencilWriteMask = setStencilWriteMask,
|
||||
.setViewport = setViewport,
|
||||
.updateBuffer = updateBuffer,
|
||||
.waitEvent = waitEvent,
|
||||
.writeTimestamp = writeTimestamp,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.cmd_count = 0,
|
||||
.serialized_cmd_count = 0,
|
||||
.commands = .empty,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.commands.deinit(allocator);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn begin(interface: *Interface, info: *const vk.CommandBufferBeginInfo) VkError!void {
|
||||
_ = interface;
|
||||
_ = info;
|
||||
}
|
||||
|
||||
pub fn end(interface: *Interface) VkError!void {
|
||||
_ = interface;
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, flags: vk.CommandBufferResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count = 0;
|
||||
self.serialized_cmd_count = 0;
|
||||
self.commands.clearRetainingCapacity();
|
||||
_ = flags;
|
||||
}
|
||||
|
||||
fn appendCommand(self: *Self, comptime T: type, command_type: c_int, payload: T) VkError!void {
|
||||
const allocator = self.interface.host_allocator.allocator();
|
||||
const header: proto.PhiCmdHeader = .{
|
||||
.magic = proto.PHI_COMMAND_MAGIC,
|
||||
.type = @intCast(command_type),
|
||||
};
|
||||
self.commands.appendSlice(allocator, std.mem.asBytes(&header)) catch return VkError.OutOfHostMemory;
|
||||
self.commands.appendSlice(allocator, std.mem.asBytes(&payload)) catch return VkError.OutOfHostMemory;
|
||||
self.cmd_count += 1;
|
||||
self.serialized_cmd_count += 1;
|
||||
}
|
||||
|
||||
fn remoteMemory(buffer: *base.Buffer) VkError!*PhiDeviceMemory {
|
||||
const memory = buffer.memory orelse return VkError.ValidationFailed;
|
||||
const phi_memory: *PhiDeviceMemory = @alignCast(@fieldParentPtr("interface", memory));
|
||||
if (phi_memory.remote_handle == 0) {
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
return phi_memory;
|
||||
}
|
||||
|
||||
pub fn beginQuery(interface: *Interface, pool: *base.QueryPool, query: u32, flags: vk.QueryControlFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = flags;
|
||||
try pool.begin(query);
|
||||
}
|
||||
|
||||
pub fn endQuery(interface: *Interface, pool: *base.QueryPool, query: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
try pool.end(query);
|
||||
}
|
||||
|
||||
pub fn resetQueryPool(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
try pool.reset(first, count);
|
||||
}
|
||||
|
||||
pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, framebuffer: *base.Framebuffer, render_area: vk.Rect2D, clear_values: ?[]const vk.ClearValue) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = render_pass;
|
||||
_ = framebuffer;
|
||||
_ = render_area;
|
||||
_ = clear_values;
|
||||
}
|
||||
|
||||
pub fn bindDescriptorSets(interface: *Interface, bind_point: vk.PipelineBindPoint, first_set: u32, sets: [base.vulkan_max_descriptor_sets]?*base.DescriptorSet, dynamic_offsets: []const u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = bind_point;
|
||||
_ = first_set;
|
||||
_ = sets;
|
||||
_ = dynamic_offsets;
|
||||
}
|
||||
|
||||
pub fn bindPipeline(interface: *Interface, bind_point: vk.PipelineBindPoint, pipeline: *base.Pipeline) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = bind_point;
|
||||
_ = pipeline;
|
||||
}
|
||||
|
||||
pub fn bindIndexBuffer(interface: *Interface, buffer: *base.Buffer, offset: usize, index_type: vk.IndexType) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
_ = index_type;
|
||||
}
|
||||
|
||||
pub fn bindVertexBuffer(interface: *Interface, index: usize, buffer: *base.Buffer, offset: usize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = index;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
}
|
||||
|
||||
pub fn blitImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageBlit, filter: vk.Filter) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src;
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
_ = filter;
|
||||
}
|
||||
|
||||
pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, rect: vk.ClearRect) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = attachment;
|
||||
_ = rect;
|
||||
}
|
||||
|
||||
pub fn clearColorImage(interface: *Interface, image: *base.Image, layout: vk.ImageLayout, color: *const vk.ClearColorValue, range: vk.ImageSubresourceRange) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = image;
|
||||
_ = layout;
|
||||
_ = color;
|
||||
_ = range;
|
||||
}
|
||||
|
||||
pub fn clearDepthStencilImage(interface: *Interface, image: *base.Image, layout: vk.ImageLayout, value: *const vk.ClearDepthStencilValue, range: vk.ImageSubresourceRange) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = image;
|
||||
_ = layout;
|
||||
_ = value;
|
||||
_ = range;
|
||||
}
|
||||
|
||||
pub fn copyBuffer(interface: *Interface, src: *base.Buffer, dst: *base.Buffer, regions: []const vk.BufferCopy) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const src_memory = try remoteMemory(src);
|
||||
const dst_memory = try remoteMemory(dst);
|
||||
|
||||
for (regions) |region| {
|
||||
const src_offset, const src_overflow = @addWithOverflow(src.offset, region.src_offset);
|
||||
const dst_offset, const dst_overflow = @addWithOverflow(dst.offset, region.dst_offset);
|
||||
if (src_overflow != 0 or dst_overflow != 0) {
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
try self.appendCommand(proto.PhiCmdCopyBuffer, proto.PHI_CMD_COPY_BUFFER, .{
|
||||
.size = region.size,
|
||||
.src_memory = @intCast(src_memory.remote_handle),
|
||||
.dst_memory = @intCast(dst_memory.remote_handle),
|
||||
.src_offset = src_offset,
|
||||
.dst_offset = dst_offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copyBufferToImage(interface: *Interface, src: *base.Buffer, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.BufferImageCopy) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
}
|
||||
|
||||
pub fn copyImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageCopy) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src;
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = regions;
|
||||
}
|
||||
|
||||
pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Buffer, regions: []const vk.BufferImageCopy) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src;
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = regions;
|
||||
}
|
||||
|
||||
pub fn copyQueryPoolResults(interface: *Interface, pool: *base.QueryPool, first: u32, count: u32, dst: *base.Buffer, offset: vk.DeviceSize, stride: vk.DeviceSize, flags: vk.QueryResultFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = pool;
|
||||
_ = first;
|
||||
_ = count;
|
||||
_ = dst;
|
||||
_ = offset;
|
||||
_ = stride;
|
||||
_ = flags;
|
||||
}
|
||||
|
||||
pub fn dispatch(interface: *Interface, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = group_count_x;
|
||||
_ = group_count_y;
|
||||
_ = group_count_z;
|
||||
}
|
||||
|
||||
pub fn dispatchBase(interface: *Interface, base_group_x: u32, base_group_y: u32, base_group_z: u32, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = base_group_x;
|
||||
_ = base_group_y;
|
||||
_ = base_group_z;
|
||||
_ = group_count_x;
|
||||
_ = group_count_y;
|
||||
_ = group_count_z;
|
||||
}
|
||||
|
||||
pub fn setDeviceMask(interface: *Interface, device_mask: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = device_mask;
|
||||
}
|
||||
|
||||
pub fn dispatchIndirect(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
}
|
||||
|
||||
pub fn draw(interface: *Interface, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = vertex_count;
|
||||
_ = instance_count;
|
||||
_ = first_vertex;
|
||||
_ = first_instance;
|
||||
}
|
||||
|
||||
pub fn drawIndexed(interface: *Interface, index_count: usize, instance_count: usize, first_index: usize, vertex_offset: i32, first_instance: usize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = index_count;
|
||||
_ = instance_count;
|
||||
_ = first_index;
|
||||
_ = vertex_offset;
|
||||
_ = first_instance;
|
||||
}
|
||||
|
||||
pub fn drawIndexedIndirect(interface: *Interface, buffer: *base.Buffer, offset: usize, count: usize, stride: usize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
_ = count;
|
||||
_ = stride;
|
||||
}
|
||||
|
||||
pub fn drawIndirect(interface: *Interface, buffer: *base.Buffer, offset: usize, count: usize, stride: usize) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
_ = count;
|
||||
_ = stride;
|
||||
}
|
||||
|
||||
pub fn endRenderPass(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
}
|
||||
|
||||
pub fn executeCommands(interface: *Interface, commands: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = commands;
|
||||
}
|
||||
|
||||
pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
|
||||
const memory = try remoteMemory(buffer);
|
||||
|
||||
try self.appendCommand(proto.PhiCmdFillBuffer, proto.PHI_CMD_FILL_BUFFER, .{
|
||||
.size = if (size == vk.WHOLE_SIZE) buffer.size - offset else size,
|
||||
.memory = @intCast(memory.remote_handle),
|
||||
.offset = offset,
|
||||
.data = data,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn updateBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, data: []const u8) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = buffer;
|
||||
_ = offset;
|
||||
_ = data;
|
||||
}
|
||||
|
||||
pub fn nextSubpass(interface: *Interface, contents: vk.SubpassContents) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = contents;
|
||||
}
|
||||
|
||||
pub fn pipelineBarrier(interface: *Interface, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, dependency: vk.DependencyFlags, memory: []const vk.MemoryBarrier, buffers: []const vk.BufferMemoryBarrier, images: []const vk.ImageMemoryBarrier) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src_stage;
|
||||
_ = dst_stage;
|
||||
_ = dependency;
|
||||
_ = memory;
|
||||
_ = buffers;
|
||||
_ = images;
|
||||
}
|
||||
|
||||
pub fn pushConstants(interface: *Interface, stages: vk.ShaderStageFlags, offset: u32, blob: []const u8) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = stages;
|
||||
_ = offset;
|
||||
_ = blob;
|
||||
}
|
||||
|
||||
pub fn resetEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineStageFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = stage;
|
||||
try event.reset();
|
||||
}
|
||||
|
||||
pub fn resolveImage(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Image, dst_layout: vk.ImageLayout, region: vk.ImageResolve) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = src;
|
||||
_ = src_layout;
|
||||
_ = dst;
|
||||
_ = dst_layout;
|
||||
_ = region;
|
||||
}
|
||||
|
||||
pub fn setEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineStageFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = stage;
|
||||
try event.signal();
|
||||
}
|
||||
|
||||
pub fn setScissor(interface: *Interface, first: u32, scissor: []const vk.Rect2D) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = first;
|
||||
_ = scissor;
|
||||
}
|
||||
|
||||
pub fn setViewport(interface: *Interface, first: u32, viewports: []const vk.Viewport) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = first;
|
||||
_ = viewports;
|
||||
}
|
||||
|
||||
pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = constants;
|
||||
}
|
||||
|
||||
pub fn setDepthBias(interface: *Interface, constant_factor: f32, clamp: f32, slope_factor: f32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = constant_factor;
|
||||
_ = clamp;
|
||||
_ = slope_factor;
|
||||
}
|
||||
|
||||
pub fn setDepthBounds(interface: *Interface, min: f32, max: f32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = min;
|
||||
_ = max;
|
||||
}
|
||||
|
||||
pub fn setLineWidth(interface: *Interface, width: f32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = width;
|
||||
}
|
||||
|
||||
pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = face_mask;
|
||||
_ = compare_mask;
|
||||
}
|
||||
|
||||
pub fn setStencilReference(interface: *Interface, face_mask: vk.StencilFaceFlags, reference: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = face_mask;
|
||||
_ = reference;
|
||||
}
|
||||
|
||||
pub fn setStencilWriteMask(interface: *Interface, face_mask: vk.StencilFaceFlags, write_mask: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = face_mask;
|
||||
_ = write_mask;
|
||||
}
|
||||
|
||||
pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = event;
|
||||
_ = src_stage;
|
||||
_ = dst_stage;
|
||||
_ = memory_barriers;
|
||||
_ = buffer_barriers;
|
||||
_ = image_barriers;
|
||||
}
|
||||
|
||||
pub fn writeTimestamp(interface: *Interface, stage: vk.PipelineStageFlags, pool: *base.QueryPool, query: u32) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.cmd_count += 1;
|
||||
_ = stage;
|
||||
try pool.writeTimestamp(query, 0);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const PhiCommandBuffer = @import("PhiCommandBuffer.zig");
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.CommandPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.CommandPoolCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.createCommandBuffer = createCommandBuffer,
|
||||
.destroy = destroy,
|
||||
.reset = reset,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn createCommandBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.CommandBufferAllocateInfo) VkError!*base.CommandBuffer {
|
||||
const cmd = try PhiCommandBuffer.create(interface.owner, allocator, info);
|
||||
return &cmd.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, flags: vk.CommandPoolResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = flags;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.allocateDescriptorSet = allocateDescriptorSet,
|
||||
.destroy = destroy,
|
||||
.freeDescriptorSet = freeDescriptorSet,
|
||||
.reset = reset,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetLayout) VkError!*base.DescriptorSet {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = layout;
|
||||
return VkError.Unknown;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = set;
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSet;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.DescriptorSetLayout) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, layout);
|
||||
|
||||
interface.vtable = &.{
|
||||
.copy = copy,
|
||||
.destroy = destroy,
|
||||
.write = write,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = src_interface;
|
||||
_ = data;
|
||||
}
|
||||
|
||||
pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = write_data;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DescriptorSetLayout;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
|
||||
const PhiQueue = @import("PhiQueue.zig");
|
||||
const PhiPhysicalDevice = @import("PhiPhysicalDevice.zig");
|
||||
const PhiTransport = @import("PhiTransport.zig");
|
||||
const phi_daemon = @import("phi_daemon");
|
||||
|
||||
pub const PhiBinarySemaphore = @import("PhiBinarySemaphore.zig");
|
||||
pub const PhiBuffer = @import("PhiBuffer.zig");
|
||||
pub const PhiBufferView = @import("PhiBufferView.zig");
|
||||
pub const PhiCommandBuffer = @import("PhiCommandBuffer.zig");
|
||||
pub const PhiCommandPool = @import("PhiCommandPool.zig");
|
||||
pub const PhiDescriptorPool = @import("PhiDescriptorPool.zig");
|
||||
pub const PhiDescriptorSetLayout = @import("PhiDescriptorSetLayout.zig");
|
||||
pub const PhiDeviceMemory = @import("PhiDeviceMemory.zig");
|
||||
pub const PhiEvent = @import("PhiEvent.zig");
|
||||
pub const PhiFence = @import("PhiFence.zig");
|
||||
pub const PhiFramebuffer = @import("PhiFramebuffer.zig");
|
||||
pub const PhiImage = @import("PhiImage.zig");
|
||||
pub const PhiInstance = @import("PhiInstance.zig");
|
||||
pub const PhiImageView = @import("PhiImageView.zig");
|
||||
pub const PhiPipeline = @import("PhiPipeline.zig");
|
||||
pub const PhiPipelineCache = @import("PhiPipelineCache.zig");
|
||||
pub const PhiPipelineLayout = @import("PhiPipelineLayout.zig");
|
||||
pub const PhiQueryPool = @import("PhiQueryPool.zig");
|
||||
pub const PhiRenderPass = @import("PhiRenderPass.zig");
|
||||
pub const PhiSampler = @import("PhiSampler.zig");
|
||||
pub const PhiShaderModule = @import("PhiShaderModule.zig");
|
||||
|
||||
const config = lib.config;
|
||||
const daemon_binary = phi_daemon.data;
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Device;
|
||||
|
||||
interface: Interface,
|
||||
transport: PhiTransport,
|
||||
|
||||
pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, instance, physical_device, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.createQueue = PhiQueue.create,
|
||||
.destroyQueue = PhiQueue.destroy,
|
||||
};
|
||||
|
||||
interface.dispatch_table = &.{
|
||||
.allocateMemory = allocateMemory,
|
||||
.createBuffer = createBuffer,
|
||||
.createBufferView = createBufferView,
|
||||
.createCommandPool = createCommandPool,
|
||||
.createComputePipeline = createComputePipeline,
|
||||
.createDescriptorPool = createDescriptorPool,
|
||||
.createDescriptorSetLayout = createDescriptorSetLayout,
|
||||
.createEvent = createEvent,
|
||||
.createFence = createFence,
|
||||
.createFramebuffer = createFramebuffer,
|
||||
.createGraphicsPipeline = createGraphicsPipeline,
|
||||
.createImage = createImage,
|
||||
.createImageView = createImageView,
|
||||
.createPipelineCache = createPipelineCache,
|
||||
.createPipelineLayout = createPipelineLayout,
|
||||
.createQueryPool = createQueryPool,
|
||||
.createRenderPass = createRenderPass,
|
||||
.createSampler = createSampler,
|
||||
.createSemaphore = createSemaphore,
|
||||
.createShaderModule = createShaderModule,
|
||||
.destroy = destroy,
|
||||
.getDeviceGroupPeerMemoryFeatures = getDeviceGroupPeerMemoryFeatures,
|
||||
.getDeviceGroupPresentCapabilitiesKHR = getDeviceGroupPresentCapabilitiesKHR,
|
||||
.getDeviceGroupSurfacePresentModesKHR = getDeviceGroupSurfacePresentModesKHR,
|
||||
};
|
||||
|
||||
const phi_physical_device: *PhiPhysicalDevice = @alignCast(@fieldParentPtr("interface", physical_device));
|
||||
|
||||
const transport = PhiTransport.init(instance, phi_physical_device.scif_node_id) catch blk: {
|
||||
// If first connect failed try to upload the daemon to the card
|
||||
try uploadAndLaunchDaemon(instance, allocator, phi_physical_device.mic_device_num);
|
||||
break :blk try PhiTransport.init(instance, phi_physical_device.scif_node_id);
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.transport = transport,
|
||||
};
|
||||
|
||||
try self.interface.createQueues(allocator, info);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.transport.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn allocateMemory(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.MemoryAllocateInfo) VkError!*base.DeviceMemory {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device_memory = try PhiDeviceMemory.create(self, allocator, info.allocation_size, info.memory_type_index);
|
||||
return &device_memory.interface;
|
||||
}
|
||||
|
||||
pub fn createBuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferCreateInfo) VkError!*base.Buffer {
|
||||
const buffer = try PhiBuffer.create(interface, allocator, info);
|
||||
return &buffer.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*base.DescriptorPool {
|
||||
const pool = try PhiDescriptorPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createDescriptorSetLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.DescriptorSetLayoutCreateInfo) VkError!*base.DescriptorSetLayout {
|
||||
const layout = try PhiDescriptorSetLayout.create(interface, allocator, info);
|
||||
return &layout.interface;
|
||||
}
|
||||
|
||||
pub fn createFence(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*base.Fence {
|
||||
const fence = try PhiFence.create(interface, allocator, info);
|
||||
return &fence.interface;
|
||||
}
|
||||
|
||||
pub fn createCommandPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.CommandPoolCreateInfo) VkError!*base.CommandPool {
|
||||
const pool = try PhiCommandPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createImage(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*base.Image {
|
||||
const image = try PhiImage.create(interface, allocator, info);
|
||||
return &image.interface;
|
||||
}
|
||||
|
||||
pub fn createImageView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ImageViewCreateInfo) VkError!*base.ImageView {
|
||||
const view = try PhiImageView.create(interface, allocator, info);
|
||||
return &view.interface;
|
||||
}
|
||||
|
||||
pub fn createBufferView(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) VkError!*base.BufferView {
|
||||
const view = try PhiBufferView.create(interface, allocator, info);
|
||||
return &view.interface;
|
||||
}
|
||||
|
||||
pub fn createComputePipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*base.Pipeline {
|
||||
const pipeline = try PhiPipeline.createCompute(interface, allocator, cache, info);
|
||||
return &pipeline.interface;
|
||||
}
|
||||
|
||||
pub fn createEvent(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.EventCreateInfo) VkError!*base.Event {
|
||||
const event = try PhiEvent.create(interface, allocator, info);
|
||||
return &event.interface;
|
||||
}
|
||||
|
||||
pub fn createFramebuffer(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.FramebufferCreateInfo) VkError!*base.Framebuffer {
|
||||
const framebuffer = try PhiFramebuffer.create(interface, allocator, info);
|
||||
return &framebuffer.interface;
|
||||
}
|
||||
|
||||
pub fn createGraphicsPipeline(interface: *Interface, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*base.Pipeline {
|
||||
const pipeline = try PhiPipeline.createGraphics(interface, allocator, cache, info);
|
||||
return &pipeline.interface;
|
||||
}
|
||||
|
||||
pub fn createPipelineCache(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!*base.PipelineCache {
|
||||
const cache = try PhiPipelineCache.create(interface, allocator, info);
|
||||
return &cache.interface;
|
||||
}
|
||||
|
||||
pub fn createPipelineLayout(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.PipelineLayoutCreateInfo) VkError!*base.PipelineLayout {
|
||||
const layout = try PhiPipelineLayout.create(interface, allocator, info);
|
||||
return &layout.interface;
|
||||
}
|
||||
|
||||
pub fn createQueryPool(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) VkError!*base.QueryPool {
|
||||
const pool = try PhiQueryPool.create(interface, allocator, info);
|
||||
return &pool.interface;
|
||||
}
|
||||
|
||||
pub fn createRenderPass(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) VkError!*base.RenderPass {
|
||||
const pass = try PhiRenderPass.create(interface, allocator, info);
|
||||
return &pass.interface;
|
||||
}
|
||||
|
||||
pub fn createSampler(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!*base.Sampler {
|
||||
const sampler = try PhiSampler.create(interface, allocator, info);
|
||||
return &sampler.interface;
|
||||
}
|
||||
|
||||
pub fn createSemaphore(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*base.BinarySemaphore {
|
||||
const semaphore = try PhiBinarySemaphore.create(interface, allocator, info);
|
||||
return &semaphore.interface;
|
||||
}
|
||||
|
||||
pub fn createShaderModule(interface: *Interface, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*base.ShaderModule {
|
||||
const module = try PhiShaderModule.create(interface, allocator, info);
|
||||
return &module.interface;
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupPeerMemoryFeatures(interface: *Interface, heap_index: u32, local_device_index: u32, remote_device_index: u32) VkError!vk.PeerMemoryFeatureFlags {
|
||||
if (heap_index >= interface.physical_device.mem_props.memory_heap_count) return VkError.ValidationFailed;
|
||||
if (local_device_index != 0 or remote_device_index != 0) return VkError.ValidationFailed;
|
||||
|
||||
return .{
|
||||
.copy_src_bit = true,
|
||||
.copy_dst_bit = true,
|
||||
.generic_src_bit = true,
|
||||
.generic_dst_bit = true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupPresentCapabilitiesKHR(_: *Interface, capabilities: *vk.DeviceGroupPresentCapabilitiesKHR) VkError!void {
|
||||
capabilities.present_mask = @splat(0);
|
||||
capabilities.present_mask[0] = 1;
|
||||
capabilities.modes = .{ .local_bit_khr = true };
|
||||
}
|
||||
|
||||
pub fn getDeviceGroupSurfacePresentModesKHR(_: *Interface, _: *base.SurfaceKHR) VkError!vk.DeviceGroupPresentModeFlagsKHR {
|
||||
return .{ .local_bit_khr = true };
|
||||
}
|
||||
|
||||
fn uploadAndLaunchDaemon(instance: *base.Instance, allocator: std.mem.Allocator, mic_device_num: u32) VkError!void {
|
||||
const io = instance.io();
|
||||
|
||||
const local_path = std.fmt.allocPrint(allocator, "/tmp/ape_phi_device_{d}_{d}.mic", .{ std.os.linux.getpid(), mic_device_num }) catch return VkError.OutOfHostMemory;
|
||||
defer allocator.free(local_path);
|
||||
defer std.Io.Dir.deleteFileAbsolute(io, local_path) catch @panic("Caught an error while handling an error");
|
||||
|
||||
std.Io.Dir.writeFile(.cwd(), io, .{
|
||||
.sub_path = local_path,
|
||||
.data = daemon_binary,
|
||||
}) catch |err| {
|
||||
std.log.scoped(.PhiDevice).err("Failed to write embedded Phi daemon: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
|
||||
const host = std.fmt.allocPrint(allocator, "{s}{d}", .{ config.phi_daemon_host_prefix, mic_device_num }) catch return VkError.OutOfHostMemory;
|
||||
defer allocator.free(host);
|
||||
|
||||
const remote_target = std.fmt.allocPrint(allocator, "{s}:{s}", .{ host, config.phi_daemon_remote_path }) catch return VkError.OutOfHostMemory;
|
||||
defer allocator.free(remote_target);
|
||||
|
||||
try runHostCommand(instance, allocator, &.{
|
||||
"scp",
|
||||
local_path,
|
||||
remote_target,
|
||||
});
|
||||
|
||||
const launch_command = std.fmt.allocPrint(
|
||||
allocator,
|
||||
"chmod +x {s} && nohup {s} >/tmp/phi_device.log 2>&1 </dev/null &",
|
||||
.{ config.phi_daemon_remote_path, config.phi_daemon_remote_path },
|
||||
) catch return VkError.OutOfHostMemory;
|
||||
defer allocator.free(launch_command);
|
||||
|
||||
try runHostCommand(instance, allocator, &.{
|
||||
"ssh",
|
||||
host,
|
||||
launch_command,
|
||||
});
|
||||
}
|
||||
|
||||
fn runHostCommand(instance: *base.Instance, allocator: std.mem.Allocator, argv: []const []const u8) VkError!void {
|
||||
const result = std.process.run(allocator, instance.io(), .{
|
||||
.argv = argv,
|
||||
.stdout_limit = .limited(4096),
|
||||
.stderr_limit = .limited(4096),
|
||||
}) catch |err| {
|
||||
std.log.scoped(.PhiDevice).err("Failed to run {s}: {s}", .{ argv[0], @errorName(err) });
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
defer allocator.free(result.stdout);
|
||||
defer allocator.free(result.stderr);
|
||||
|
||||
switch (result.term) {
|
||||
.exited => |code| if (code == 0) return,
|
||||
else => {},
|
||||
}
|
||||
|
||||
std.log.scoped(.PhiDevice).err("{s} failed: stdout=\"{s}\" stderr=\"{s}\"", .{ argv[0], result.stdout, result.stderr });
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const proto = lib.proto;
|
||||
|
||||
const PhiDevice = @import("PhiDevice.zig");
|
||||
const PhiTransport = @import("PhiTransport.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.DeviceMemory;
|
||||
|
||||
interface: Interface,
|
||||
remote_handle: u64,
|
||||
data: ?[]u8,
|
||||
|
||||
pub fn create(device: *PhiDevice, allocator: std.mem.Allocator, size: vk.DeviceSize, memory_type_index: u32) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(&device.interface, size, memory_type_index);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.map = map,
|
||||
.unmap = unmap,
|
||||
.flushRange = flushRange,
|
||||
.invalidateRange = invalidateRange,
|
||||
};
|
||||
|
||||
if (memory_type_index >= device.interface.physical_device.mem_props.memory_type_count) {
|
||||
return VkError.ValidationFailed;
|
||||
}
|
||||
|
||||
const memory_type = device.interface.physical_device.mem_props.memory_types[memory_type_index];
|
||||
const host_visible = memory_type.property_flags.host_visible_bit;
|
||||
const device_local = memory_type.property_flags.device_local_bit;
|
||||
const allocation_size = std.math.cast(usize, size) orelse return VkError.OutOfDeviceMemory;
|
||||
|
||||
const remote_handle = if (device_local) blk: {
|
||||
const alloc_request: proto.PhiAllocMemoryRequest = .{
|
||||
.size = size,
|
||||
.memory_type_index = memory_type_index,
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
var reply = std.mem.zeroes(proto.PhiAllocMemoryReply);
|
||||
try device.transport.request(proto.PHI_PACKET_ALLOC_MEMORY, std.mem.asBytes(&alloc_request), std.mem.asBytes(&reply));
|
||||
|
||||
if (reply.result.status != proto.PHI_STATUS_OK) {
|
||||
return PhiTransport.statusToErr(reply.result.status);
|
||||
}
|
||||
|
||||
std.log.scoped(.PhiDeviceMemory).info("Recieved remote handle 0x{X}", .{reply.remote_handle});
|
||||
|
||||
break :blk reply.remote_handle;
|
||||
} else 0;
|
||||
errdefer if (remote_handle != 0) self.interface.destroy(allocator);
|
||||
|
||||
const data = if (host_visible)
|
||||
device.interface.device_allocator.allocator().alloc(u8, allocation_size) catch return VkError.OutOfDeviceMemory
|
||||
else
|
||||
null;
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.remote_handle = remote_handle,
|
||||
.data = data,
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const device: *PhiDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
if (self.data) |data| {
|
||||
interface.owner.device_allocator.allocator().free(data);
|
||||
}
|
||||
if (self.remote_handle != 0) {
|
||||
const request_payload: proto.PhiFreeMemoryRequest = .{
|
||||
.remote_handle = self.remote_handle,
|
||||
};
|
||||
var reply: proto.PhiFreeMemoryReply = undefined;
|
||||
device.transport.request(proto.PHI_PACKET_FREE_MEMORY, std.mem.asBytes(&request_payload), std.mem.asBytes(&reply)) catch |err| {
|
||||
std.log.scoped(.PhiTransport).err("Remote free failed: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
if (reply.result.status != proto.PHI_STATUS_OK) {
|
||||
std.log.scoped(.PhiTransport).err("Remote free returned status {d}", .{reply.result.status});
|
||||
}
|
||||
}
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
}
|
||||
|
||||
pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
|
||||
_ = interface;
|
||||
_ = offset;
|
||||
_ = size;
|
||||
}
|
||||
|
||||
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
const data = self.data orelse return VkError.MemoryMapFailed;
|
||||
const map_offset = std.math.cast(usize, offset) orelse return VkError.MemoryMapFailed;
|
||||
if (map_offset >= data.len) {
|
||||
return VkError.MemoryMapFailed;
|
||||
}
|
||||
const map_size = if (size == vk.WHOLE_SIZE)
|
||||
data.len - map_offset
|
||||
else
|
||||
std.math.cast(usize, size) orelse return VkError.MemoryMapFailed;
|
||||
if (map_size > data.len - map_offset) {
|
||||
return VkError.MemoryMapFailed;
|
||||
}
|
||||
return data[map_offset..(map_offset + map_size)];
|
||||
}
|
||||
|
||||
pub fn unmap(_: *Interface) void {}
|
||||
@@ -0,0 +1,55 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Event;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.EventCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getStatus = getStatus,
|
||||
.reset = reset,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getStatus(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const Device = base.Device;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Fence;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getStatus = getStatus,
|
||||
.reset = reset,
|
||||
.signal = signal,
|
||||
.wait = wait,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getStatus(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn reset(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn signal(interface: *Interface) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn wait(interface: *Interface, timeout: u64) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
_ = self;
|
||||
_ = timeout;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Framebuffer;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.FramebufferCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Image;
|
||||
|
||||
pub const F32x4 = @Vector(4, f32);
|
||||
pub const U32x4 = @Vector(4, u32);
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getMemoryRequirements = getMemoryRequirements,
|
||||
.getSubresourceLayout = getSubresourceLayout,
|
||||
.getTotalSizeForAspect = getTotalSizeForAspect,
|
||||
.getSliceMemSizeForMipLevel = getSliceMemSizeForMipLevel,
|
||||
.getRowPitchMemSizeForMipLevel = getRowPitchMemSizeForMipLevel,
|
||||
.copyToMemory = copyToMemory,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getMemoryRequirements(_: *Interface, requirements: *vk.MemoryRequirements) VkError!void {
|
||||
_ = requirements;
|
||||
}
|
||||
|
||||
pub fn copyToMemory(interface: *const Interface, memory: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
|
||||
_ = interface;
|
||||
_ = subresource;
|
||||
@memset(memory, 0);
|
||||
}
|
||||
|
||||
pub fn getTotalSizeForAspect(interface: *const Interface, aspect_mask: vk.ImageAspectFlags) VkError!usize {
|
||||
_ = aspect_mask;
|
||||
return interface.extent.width * interface.extent.height * interface.extent.depth * base.format.texelSize(interface.format);
|
||||
}
|
||||
|
||||
pub fn getSubresourceLayout(interface: *const Interface, subresource: vk.ImageSubresource) VkError!vk.SubresourceLayout {
|
||||
_ = subresource;
|
||||
return .{
|
||||
.offset = 0,
|
||||
.size = try getTotalSizeForAspect(interface, base.format.toAspect(interface.format)),
|
||||
.row_pitch = getRowPitchMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
.array_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
.depth_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
_ = aspect_mask;
|
||||
_ = mip_level;
|
||||
return interface.extent.width * interface.extent.height * base.format.texelSize(interface.format);
|
||||
}
|
||||
|
||||
pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
|
||||
_ = aspect_mask;
|
||||
_ = mip_level;
|
||||
return interface.extent.width * base.format.texelSize(interface.format);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.ImageView;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageViewCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const mic = lib.mic;
|
||||
|
||||
const PhiPhysicalDevice = @import("PhiPhysicalDevice.zig");
|
||||
|
||||
const Dispatchable = base.Dispatchable;
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Instance;
|
||||
|
||||
interface: Interface,
|
||||
threaded: std.Io.Threaded,
|
||||
io_impl: std.Io,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
var props: vk.ExtensionProperties = .{
|
||||
.extension_name = @splat(0),
|
||||
.spec_version = @bitCast(ext.version),
|
||||
};
|
||||
@memcpy(props.extension_name[0..ext.name.len], ext.name);
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group_creation),
|
||||
castExtension(vk.extensions.khr_get_physical_device_properties_2),
|
||||
castExtension(vk.extensions.khr_surface),
|
||||
castExtension(vk.extensions.khr_wayland_surface),
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
self.allocator = std.heap.smp_allocator;
|
||||
self.threaded = std.Io.Threaded.init(self.allocator, .{});
|
||||
self.io_impl = self.threaded.io();
|
||||
|
||||
self.interface = try base.Instance.init(allocator, infos);
|
||||
self.interface.dispatch_table = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.interface.vtable = &.{
|
||||
.requestPhysicalDevices = requestPhysicalDevices,
|
||||
.releasePhysicalDevices = releasePhysicalDevices,
|
||||
.io = io,
|
||||
};
|
||||
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.threaded.deinit();
|
||||
allocator.destroy(self);
|
||||
|
||||
mic.unload();
|
||||
}
|
||||
|
||||
fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator, _: []base.drm.Card) VkError!void {
|
||||
if (interface.physical_devices.items.len != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mic.load() catch |err| {
|
||||
std.log.scoped(.MIC).err("Failed to load libmicmgmt: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
|
||||
var devices = mic.DeviceList.init() catch |err| {
|
||||
std.log.scoped(.MIC).err("Failed to create device list: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
defer devices.deinit();
|
||||
|
||||
const count = devices.count() catch |err| {
|
||||
std.log.scoped(.MIC).err("Failed to fetch device list count: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
};
|
||||
|
||||
for (0..count) |index| {
|
||||
const device_num = devices.deviceAtIndex(index) catch |err| {
|
||||
std.log.scoped(.MIC).err("Failed to fetch device: {s}", .{@errorName(err)});
|
||||
continue;
|
||||
};
|
||||
|
||||
var device = mic.Device.open(device_num) catch |err| {
|
||||
std.log.scoped(.MIC).err("Failed to open device {d}: {s}", .{ device_num, @errorName(err) });
|
||||
continue;
|
||||
};
|
||||
defer device.deinit();
|
||||
|
||||
const physical_device = try PhiPhysicalDevice.create(allocator, interface, device, device_num);
|
||||
errdefer physical_device.interface.release(allocator) catch @panic("Caught an error while handling an error");
|
||||
|
||||
const dispatchable = try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface);
|
||||
errdefer dispatchable.destroy(allocator);
|
||||
|
||||
interface.physical_devices.append(allocator, dispatchable) catch return VkError.OutOfHostMemory;
|
||||
}
|
||||
}
|
||||
|
||||
fn releasePhysicalDevices(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
var result: ?VkError = null;
|
||||
|
||||
for (interface.physical_devices.items) |physical_device| {
|
||||
physical_device.object.release(allocator) catch |err| {
|
||||
if (result == null) {
|
||||
result = err;
|
||||
}
|
||||
};
|
||||
physical_device.destroy(allocator);
|
||||
}
|
||||
|
||||
interface.physical_devices.deinit(allocator);
|
||||
interface.physical_devices = .empty;
|
||||
|
||||
if (result) |err| {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
fn io(interface: *Interface) std.Io {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
return self.io_impl;
|
||||
}
|
||||
|
||||
fn mapDeviceEnumerationError(err: anyerror) VkError {
|
||||
return switch (err) {
|
||||
error.OutOfMemory => VkError.OutOfHostMemory,
|
||||
else => VkError.InitializationFailed,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const mic = lib.mic;
|
||||
|
||||
const pci_ids = @import("pci_ids.zig").map;
|
||||
|
||||
const PhiDevice = @import("PhiDevice.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const SurfaceKHR = base.SurfaceKHR;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PhysicalDevice;
|
||||
|
||||
fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
|
||||
var props: vk.ExtensionProperties = .{
|
||||
.extension_name = @splat(0),
|
||||
.spec_version = @bitCast(ext.version),
|
||||
};
|
||||
@memcpy(props.extension_name[0..ext.name.len], ext.name);
|
||||
return props;
|
||||
}
|
||||
|
||||
pub const extensions = [_]vk.ExtensionProperties{
|
||||
castExtension(vk.extensions.khr_device_group),
|
||||
castExtension(vk.extensions.khr_swapchain),
|
||||
};
|
||||
|
||||
interface: Interface,
|
||||
scif_node_id: u16,
|
||||
mic_device_num: u32,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, mic_device: mic.Device, mic_device_num: u32) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, instance);
|
||||
|
||||
interface.dispatch_table = &.{
|
||||
.createDevice = createDevice,
|
||||
.getFormatProperties = getFormatProperties,
|
||||
.getImageFormatProperties = getImageFormatProperties,
|
||||
.getSparseImageFormatProperties = getSparseImageFormatProperties,
|
||||
.enumerateLayerProperties = enumerateLayerProperties,
|
||||
.enumerateExtensionProperties = enumerateExtensionProperties,
|
||||
.release = destroy,
|
||||
|
||||
// VK_KHR_get_physical_device_properties_2
|
||||
.getSparseImageFormatProperties2 = getSparseImageFormatProperties2,
|
||||
|
||||
// VK_KHR_surface
|
||||
.getSurfaceSupportKHR = getSurfaceSupportKHR,
|
||||
};
|
||||
|
||||
interface.props.api_version = @bitCast(lib.vulkan_version);
|
||||
interface.props.driver_version = @bitCast(base.driver_version);
|
||||
interface.props.device_type = .other;
|
||||
|
||||
@memset(interface.props.device_name[0..], 0);
|
||||
|
||||
if (mic_device.pciConfig()) |pci_value| {
|
||||
var pci = pci_value;
|
||||
defer pci.deinit();
|
||||
|
||||
interface.props.vendor_id = pci.vendorId() catch 0;
|
||||
interface.props.device_id = pci.deviceId() catch 0;
|
||||
|
||||
for (pci_ids[0..]) |pci_info| {
|
||||
if (pci_info.id != pci.deviceId() catch 0)
|
||||
continue;
|
||||
|
||||
const len = @min(vk.MAX_PHYSICAL_DEVICE_NAME_SIZE, pci_info.name.len);
|
||||
@memcpy(interface.props.device_name[0..len], pci_info.name[0..len]);
|
||||
|
||||
const driver_mark = " [Phi ApeDriver]";
|
||||
|
||||
@memcpy(interface.props.device_name[len .. len + driver_mark.len], driver_mark);
|
||||
|
||||
break;
|
||||
}
|
||||
} else |err| {
|
||||
std.log.scoped(.MIC).err("Failed to fetch device PCI config: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
|
||||
interface.props.pipeline_cache_uuid = @splat(0);
|
||||
interface.props.limits = .{
|
||||
.max_image_dimension_1d = 4096,
|
||||
.max_image_dimension_2d = 4096,
|
||||
.max_image_dimension_3d = 256,
|
||||
.max_image_dimension_cube = 4096,
|
||||
.max_image_array_layers = 256,
|
||||
.max_texel_buffer_elements = 65536,
|
||||
.max_uniform_buffer_range = 16384,
|
||||
.max_storage_buffer_range = 134217728,
|
||||
.max_push_constants_size = 256,
|
||||
.max_memory_allocation_count = 1024,
|
||||
.max_sampler_allocation_count = 4096,
|
||||
.buffer_image_granularity = 131072,
|
||||
.sparse_address_space_size = 0,
|
||||
.max_bound_descriptor_sets = 8,
|
||||
.max_per_stage_descriptor_samplers = 16,
|
||||
.max_per_stage_descriptor_uniform_buffers = 12,
|
||||
.max_per_stage_descriptor_storage_buffers = 4,
|
||||
.max_per_stage_descriptor_sampled_images = 16,
|
||||
.max_per_stage_descriptor_storage_images = 4,
|
||||
.max_per_stage_descriptor_input_attachments = 4,
|
||||
.max_per_stage_resources = 128,
|
||||
.max_descriptor_set_samplers = 96,
|
||||
.max_descriptor_set_uniform_buffers = 72,
|
||||
.max_descriptor_set_uniform_buffers_dynamic = 8,
|
||||
.max_descriptor_set_storage_buffers = 24,
|
||||
.max_descriptor_set_storage_buffers_dynamic = 4,
|
||||
.max_descriptor_set_sampled_images = 96,
|
||||
.max_descriptor_set_storage_images = 24,
|
||||
.max_descriptor_set_input_attachments = 4,
|
||||
.max_vertex_input_attributes = 32,
|
||||
.max_vertex_input_bindings = 32,
|
||||
.max_vertex_input_attribute_offset = 2047,
|
||||
.max_vertex_input_binding_stride = 2048,
|
||||
.max_vertex_output_components = 64,
|
||||
.max_tessellation_generation_level = 0,
|
||||
.max_tessellation_patch_size = 0,
|
||||
.max_tessellation_control_per_vertex_input_components = 0,
|
||||
.max_tessellation_control_per_vertex_output_components = 0,
|
||||
.max_tessellation_control_per_patch_output_components = 0,
|
||||
.max_tessellation_control_total_output_components = 0,
|
||||
.max_tessellation_evaluation_input_components = 0,
|
||||
.max_tessellation_evaluation_output_components = 0,
|
||||
.max_geometry_shader_invocations = 0,
|
||||
.max_geometry_input_components = 0,
|
||||
.max_geometry_output_components = 0,
|
||||
.max_geometry_output_vertices = 0,
|
||||
.max_geometry_total_output_components = 0,
|
||||
.max_fragment_input_components = 64,
|
||||
.max_fragment_output_attachments = 4,
|
||||
.max_fragment_dual_src_attachments = 0,
|
||||
.max_fragment_combined_output_resources = 4,
|
||||
.max_compute_shared_memory_size = 16384,
|
||||
.max_compute_work_group_count = .{ 65535, 65535, 65535 },
|
||||
.max_compute_work_group_invocations = 128,
|
||||
.max_compute_work_group_size = .{ 128, 128, 64 },
|
||||
.sub_pixel_precision_bits = 4,
|
||||
.sub_texel_precision_bits = 4,
|
||||
.mipmap_precision_bits = 4,
|
||||
.max_draw_indexed_index_value = 4294967295,
|
||||
.max_draw_indirect_count = 65535,
|
||||
.max_sampler_lod_bias = 2.0,
|
||||
.max_sampler_anisotropy = 1.0,
|
||||
.max_viewports = 1,
|
||||
.max_viewport_dimensions = .{ 4096, 4096 },
|
||||
.viewport_bounds_range = .{ -8192.0, 8191.0 },
|
||||
.viewport_sub_pixel_bits = 0,
|
||||
.min_memory_map_alignment = 64,
|
||||
.min_texel_buffer_offset_alignment = 256,
|
||||
.min_uniform_buffer_offset_alignment = 256,
|
||||
.min_storage_buffer_offset_alignment = 256,
|
||||
.min_texel_offset = -8,
|
||||
.max_texel_offset = 7,
|
||||
.min_texel_gather_offset = 0,
|
||||
.max_texel_gather_offset = 0,
|
||||
.min_interpolation_offset = 0.0,
|
||||
.max_interpolation_offset = 0.0,
|
||||
.sub_pixel_interpolation_offset_bits = 0,
|
||||
.max_framebuffer_width = 4096,
|
||||
.max_framebuffer_height = 4096,
|
||||
.max_framebuffer_layers = 256,
|
||||
.framebuffer_color_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_depth_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_stencil_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.framebuffer_no_attachments_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.max_color_attachments = 4,
|
||||
.sampled_image_color_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_integer_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_depth_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.sampled_image_stencil_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.storage_image_sample_counts = .{ .@"1_bit" = true, .@"4_bit" = true },
|
||||
.max_sample_mask_words = 1,
|
||||
.timestamp_compute_and_graphics = .false,
|
||||
.timestamp_period = 1.0,
|
||||
.max_clip_distances = 0,
|
||||
.max_cull_distances = 0,
|
||||
.max_combined_clip_and_cull_distances = 0,
|
||||
.discrete_queue_priorities = 2,
|
||||
.point_size_range = .{ 1.0, 1.0 },
|
||||
.line_width_range = .{ 1.0, 1.0 },
|
||||
.point_size_granularity = 0.0,
|
||||
.line_width_granularity = 0.0,
|
||||
.strict_lines = .false,
|
||||
.standard_sample_locations = .true,
|
||||
.optimal_buffer_copy_offset_alignment = 1,
|
||||
.optimal_buffer_copy_row_pitch_alignment = 1,
|
||||
.non_coherent_atom_size = 256,
|
||||
};
|
||||
|
||||
{
|
||||
interface.mem_props.memory_type_count = 3;
|
||||
|
||||
interface.mem_props.memory_types[0] = .{
|
||||
.heap_index = 0,
|
||||
.property_flags = .{
|
||||
.device_local_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
interface.mem_props.memory_types[1] = .{
|
||||
.heap_index = 1,
|
||||
.property_flags = .{
|
||||
.host_visible_bit = true,
|
||||
.host_coherent_bit = true,
|
||||
},
|
||||
};
|
||||
|
||||
interface.mem_props.memory_types[2] = .{
|
||||
.heap_index = 1,
|
||||
.property_flags = .{
|
||||
.host_visible_bit = true,
|
||||
.host_coherent_bit = true,
|
||||
.host_cached_bit = true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (mic_device.memoryInfo()) |memory_value| {
|
||||
var memory = memory_value;
|
||||
defer memory.deinit();
|
||||
|
||||
interface.mem_props.memory_heap_count = 2;
|
||||
|
||||
interface.mem_props.memory_heaps[0] = .{
|
||||
.size = memory.size() catch 0,
|
||||
.flags = .{ .device_local_bit = true },
|
||||
};
|
||||
interface.mem_props.memory_heaps[1] = .{
|
||||
.size = std.process.totalSystemMemory() catch 0,
|
||||
.flags = .{},
|
||||
};
|
||||
} else |err| {
|
||||
std.log.scoped(.MIC).err("Failed to fetch device memory infos: {s}", .{@errorName(err)});
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
|
||||
interface.features = .{
|
||||
.shader_float_64 = .true,
|
||||
.shader_int_64 = .true,
|
||||
.shader_int_16 = .true,
|
||||
};
|
||||
|
||||
var queue_family_props = [_]vk.QueueFamilyProperties{
|
||||
.{
|
||||
.queue_flags = .{ .graphics_bit = true, .compute_bit = true, .transfer_bit = true },
|
||||
.queue_count = 1,
|
||||
.timestamp_valid_bits = 0,
|
||||
.min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 },
|
||||
},
|
||||
};
|
||||
interface.queue_family_props.appendSlice(allocator, queue_family_props[0..]) catch return VkError.OutOfHostMemory;
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.scif_node_id = deviceNumToScifNode(mic_device_num),
|
||||
.mic_device_num = mic_device_num,
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn deviceNumToScifNode(device_num: u32) u16 {
|
||||
return @intCast(device_num + 1);
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn createDevice(interface: *Interface, allocator: std.mem.Allocator, infos: *const vk.DeviceCreateInfo) VkError!*base.Device {
|
||||
const device = try PhiDevice.create(interface.instance, interface, allocator, infos);
|
||||
return &device.interface;
|
||||
}
|
||||
|
||||
pub fn enumerateLayerProperties(_: *const Interface, count: *u32, p_properties: ?[*]vk.LayerProperties) VkError!void {
|
||||
count.* = 0;
|
||||
_ = p_properties;
|
||||
}
|
||||
|
||||
pub fn enumerateExtensionProperties(_: *const Interface, layer_name: ?[]const u8, count: *u32, p_properties: ?[*]vk.ExtensionProperties) VkError!void {
|
||||
if (layer_name) |_| {
|
||||
return VkError.LayerNotPresent;
|
||||
}
|
||||
|
||||
const available = extensions.len;
|
||||
if (p_properties) |properties| {
|
||||
const write_count = @min(count.*, available);
|
||||
for (extensions[0..write_count], properties[0..write_count]) |ext, *prop| {
|
||||
prop.* = ext;
|
||||
}
|
||||
count.* = @intCast(write_count);
|
||||
if (write_count < available) return VkError.Incomplete;
|
||||
} else {
|
||||
count.* = @intCast(available);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.FormatProperties {
|
||||
_ = interface;
|
||||
var properties: vk.FormatProperties = .{};
|
||||
|
||||
switch (format) {
|
||||
// Formats which can be sampled *and* filtered
|
||||
.r4g4b4a4_unorm_pack16,
|
||||
.b4g4r4a4_unorm_pack16,
|
||||
.a4r4g4b4_unorm_pack16,
|
||||
.a4b4g4r4_unorm_pack16,
|
||||
.r5g6b5_unorm_pack16,
|
||||
.b5g6r5_unorm_pack16,
|
||||
.r5g5b5a1_unorm_pack16,
|
||||
.b5g5r5a1_unorm_pack16,
|
||||
.a1r5g5b5_unorm_pack16,
|
||||
.r8_unorm,
|
||||
.r8_srgb,
|
||||
.r8_snorm,
|
||||
.r8g8_unorm,
|
||||
.r8g8_srgb,
|
||||
.r8g8_snorm,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_srgb,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_srgb_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.r16_unorm,
|
||||
.r16_snorm,
|
||||
.r16_sfloat,
|
||||
.r16g16_unorm,
|
||||
.r16g16_snorm,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.e5b9g9r9_ufloat_pack32,
|
||||
//.bc1_rgb_unorm_block,
|
||||
//.bc1_rgb_srgb_block,
|
||||
//.bc1_rgba_unorm_block,
|
||||
//.bc1_rgba_srgb_block,
|
||||
//.bc2_unorm_block,
|
||||
//.bc2_srgb_block,
|
||||
//.bc3_unorm_block,
|
||||
//.bc3_srgb_block,
|
||||
//.bc4_unorm_block,
|
||||
//.bc4_snorm_block,
|
||||
//.bc5_unorm_block,
|
||||
//.bc5_snorm_block,
|
||||
//.bc6h_ufloat_block,
|
||||
//.bc6h_sfloat_block,
|
||||
//.bc7_unorm_block,
|
||||
//.bc7_srgb_block,
|
||||
//.etc2_r8g8b8_unorm_block,
|
||||
//.etc2_r8g8b8_srgb_block,
|
||||
//.etc2_r8g8b8a1_unorm_block,
|
||||
//.etc2_r8g8b8a1_srgb_block,
|
||||
//.etc2_r8g8b8a8_unorm_block,
|
||||
//.etc2_r8g8b8a8_srgb_block,
|
||||
//.eac_r11_unorm_block,
|
||||
//.eac_r11_snorm_block,
|
||||
//.eac_r11g11_unorm_block,
|
||||
//.eac_r11g11_snorm_block,
|
||||
//.astc_4x_4_unorm_block,
|
||||
//.astc_5x_4_unorm_block,
|
||||
//.astc_5x_5_unorm_block,
|
||||
//.astc_6x_5_unorm_block,
|
||||
//.astc_6x_6_unorm_block,
|
||||
//.astc_8x_5_unorm_block,
|
||||
//.astc_8x_6_unorm_block,
|
||||
//.astc_8x_8_unorm_block,
|
||||
//.astc_1_0x_5_unorm_block,
|
||||
//.astc_1_0x_6_unorm_block,
|
||||
//.astc_1_0x_8_unorm_block,
|
||||
//.astc_1_0x_10_unorm_block,
|
||||
//.astc_1_2x_10_unorm_block,
|
||||
//.astc_1_2x_12_unorm_block,
|
||||
//.astc_4x_4_srgb_block,
|
||||
//.astc_5x_4_srgb_block,
|
||||
//.astc_5x_5_srgb_block,
|
||||
//.astc_6x_5_srgb_block,
|
||||
//.astc_6x_6_srgb_block,
|
||||
//.astc_8x_5_srgb_block,
|
||||
//.astc_8x_6_srgb_block,
|
||||
//.astc_8x_8_srgb_block,
|
||||
//.astc_1_0x_5_srgb_block,
|
||||
//.astc_1_0x_6_srgb_block,
|
||||
//.astc_1_0x_8_srgb_block,
|
||||
//.astc_1_0x_10_srgb_block,
|
||||
//.astc_1_2x_10_srgb_block,
|
||||
//.astc_1_2x_12_srgb_block,
|
||||
.d16_unorm,
|
||||
.d32_sfloat,
|
||||
.d32_sfloat_s8_uint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.blit_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
},
|
||||
|
||||
// Formats which can be sampled, but don't support filtering
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.s8_uint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.blit_src_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
},
|
||||
|
||||
// YCbCr formats
|
||||
.g8_b8_r8_3plane_420_unorm,
|
||||
.g8_b8r8_2plane_420_unorm,
|
||||
.g10x6_b10x6r10x6_2plane_420_unorm_3pack16,
|
||||
=> {
|
||||
properties.optimal_tiling_features.sampled_image_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_filter_linear_bit = true;
|
||||
properties.optimal_tiling_features.sampled_image_ycbcr_conversion_linear_filter_bit = true;
|
||||
properties.optimal_tiling_features.transfer_src_bit = true;
|
||||
properties.optimal_tiling_features.transfer_dst_bit = true;
|
||||
properties.optimal_tiling_features.cosited_chroma_samples_bit = true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
// Vulkan 1.0 mandatory storage image formats supporting atomic operations
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
=> {
|
||||
properties.buffer_features.storage_texel_buffer_bit = true;
|
||||
properties.buffer_features.storage_texel_buffer_atomic_bit = true;
|
||||
properties.optimal_tiling_features.storage_image_bit = true;
|
||||
properties.optimal_tiling_features.storage_image_atomic_bit = true;
|
||||
},
|
||||
// vulkan 1.0 mandatory storage image formats
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
// vulkan 1.0 shaderstorageimageextendedformats
|
||||
.r16g16_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.r16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16_unorm,
|
||||
.r8g8_unorm,
|
||||
.r16_unorm,
|
||||
.r8_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16_snorm,
|
||||
.r8g8_snorm,
|
||||
.r16_snorm,
|
||||
.r8_snorm,
|
||||
.r16g16_sint,
|
||||
.r8g8_sint,
|
||||
.r16_sint,
|
||||
.r8_sint,
|
||||
.r16g16_uint,
|
||||
.r8g8_uint,
|
||||
.r16_uint,
|
||||
.r8_uint,
|
||||
// additional formats not listed under "formats without shader storage format"
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
=> {
|
||||
properties.optimal_tiling_features.storage_image_bit = true;
|
||||
properties.buffer_features.storage_texel_buffer_bit = true;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
.r5g6b5_unorm_pack16,
|
||||
.a1r5g5b5_unorm_pack16,
|
||||
.r4g4b4a4_unorm_pack16,
|
||||
.b4g4r4a4_unorm_pack16,
|
||||
.a4r4g4b4_unorm_pack16,
|
||||
.a4b4g4r4_unorm_pack16,
|
||||
.b5g6r5_unorm_pack16,
|
||||
.r5g5b5a1_unorm_pack16,
|
||||
.b5g5r5a1_unorm_pack16,
|
||||
.r8_unorm,
|
||||
.r8g8_unorm,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_srgb,
|
||||
.b8g8r8a8_unorm,
|
||||
.b8g8r8a8_srgb,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_srgb_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.r16_sfloat,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_sfloat,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.r16_unorm,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16g16_unorm,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
=> {
|
||||
properties.optimal_tiling_features.color_attachment_bit = true;
|
||||
properties.optimal_tiling_features.blit_dst_bit = true;
|
||||
},
|
||||
.s8_uint,
|
||||
.d16_unorm,
|
||||
.d32_sfloat, // note: either vk_format_d32_sfloat or vk_format_x8_d24_unorm_pack32 must be supported
|
||||
.d32_sfloat_s8_uint,
|
||||
=> { // note: either vk_format_d24_unorm_s8_uint or vk_format_d32_sfloat_s8_uint must be supported
|
||||
properties.optimal_tiling_features.depth_stencil_attachment_bit = true;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (base.format.supportsColorAttachemendBlend(format)) {
|
||||
properties.optimal_tiling_features.color_attachment_blend_bit = true;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
.r8_unorm,
|
||||
.r8_snorm,
|
||||
.r8_uscaled,
|
||||
.r8_sscaled,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_unorm,
|
||||
.r8g8_snorm,
|
||||
.r8g8_uscaled,
|
||||
.r8g8_sscaled,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uscaled,
|
||||
.r8g8b8a8_sscaled,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.b8g8r8a8_unorm,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uscaled_pack32,
|
||||
.a8b8g8r8_sscaled_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.a2r10g10b10_snorm_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
.a2r10g10b10_sint_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_snorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.a2b10g10r10_sint_pack32,
|
||||
.r16_unorm,
|
||||
.r16_snorm,
|
||||
.r16_uscaled,
|
||||
.r16_sscaled,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16_sfloat,
|
||||
.r16g16_unorm,
|
||||
.r16g16_snorm,
|
||||
.r16g16_uscaled,
|
||||
.r16g16_sscaled,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_unorm,
|
||||
.r16g16b16a16_snorm,
|
||||
.r16g16b16a16_uscaled,
|
||||
.r16g16b16a16_sscaled,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32_uint,
|
||||
.r32g32b32_sint,
|
||||
.r32g32b32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
=> properties.buffer_features.vertex_buffer_bit = true,
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
// Vulkan 1.1 mandatory
|
||||
.r8_unorm,
|
||||
.r8_snorm,
|
||||
.r8_uint,
|
||||
.r8_sint,
|
||||
.r8g8_unorm,
|
||||
.r8g8_snorm,
|
||||
.r8g8_uint,
|
||||
.r8g8_sint,
|
||||
.r8g8b8a8_unorm,
|
||||
.r8g8b8a8_snorm,
|
||||
.r8g8b8a8_uint,
|
||||
.r8g8b8a8_sint,
|
||||
.b8g8r8a8_unorm,
|
||||
.a8b8g8r8_unorm_pack32,
|
||||
.a8b8g8r8_snorm_pack32,
|
||||
.a8b8g8r8_uint_pack32,
|
||||
.a8b8g8r8_sint_pack32,
|
||||
.a2b10g10r10_unorm_pack32,
|
||||
.a2b10g10r10_uint_pack32,
|
||||
.r16_uint,
|
||||
.r16_sint,
|
||||
.r16_sfloat,
|
||||
.r16g16_uint,
|
||||
.r16g16_sint,
|
||||
.r16g16_sfloat,
|
||||
.r16g16b16a16_uint,
|
||||
.r16g16b16a16_sint,
|
||||
.r16g16b16a16_sfloat,
|
||||
.r32_uint,
|
||||
.r32_sint,
|
||||
.r32_sfloat,
|
||||
.r32g32_uint,
|
||||
.r32g32_sint,
|
||||
.r32g32_sfloat,
|
||||
.r32g32b32a32_uint,
|
||||
.r32g32b32a32_sint,
|
||||
.r32g32b32a32_sfloat,
|
||||
.b10g11r11_ufloat_pack32,
|
||||
// optional
|
||||
.a2r10g10b10_unorm_pack32,
|
||||
.a2r10g10b10_uint_pack32,
|
||||
=> properties.buffer_features.uniform_texel_buffer_bit = true,
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (properties.optimal_tiling_features.toInt() != 0) {
|
||||
// "Formats that are required to support VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT must also support
|
||||
// VK_FORMAT_FEATURE_TRANSFER_SRC_BIT and VK_FORMAT_FEATURE_TRANSFER_DST_BIT."
|
||||
|
||||
properties.linear_tiling_features.transfer_src_bit = true;
|
||||
properties.linear_tiling_features.transfer_dst_bit = true;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
pub fn getImageFormatProperties(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
_: vk.ImageCreateFlags,
|
||||
) VkError!vk.ImageFormatProperties {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
if (!try self.isFormatSupported(format, image_type, tiling, usage))
|
||||
return VkError.FormatNotSupported;
|
||||
|
||||
const properties: vk.ImageFormatProperties = .{
|
||||
.max_extent = .{ .width = 0, .height = 0, .depth = 1 },
|
||||
.max_mip_levels = 1,
|
||||
.max_array_layers = 1,
|
||||
.sample_counts = .{ .@"1_bit" = true },
|
||||
.max_resource_size = std.math.maxInt(u32),
|
||||
};
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/// Phi does not support sparse images.
|
||||
pub fn getSparseImageFormatProperties(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
samples: vk.SampleCountFlags,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
properties: ?[*]vk.SparseImageFormatProperties,
|
||||
) VkError!u32 {
|
||||
_ = interface;
|
||||
_ = format;
|
||||
_ = image_type;
|
||||
_ = samples;
|
||||
_ = tiling;
|
||||
_ = usage;
|
||||
_ = properties;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Phi does not support sparse images.
|
||||
pub fn getSparseImageFormatProperties2(
|
||||
interface: *Interface,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
samples: vk.SampleCountFlags,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
properties: ?[*]vk.SparseImageFormatProperties2,
|
||||
) VkError!u32 {
|
||||
_ = interface;
|
||||
_ = format;
|
||||
_ = image_type;
|
||||
_ = samples;
|
||||
_ = tiling;
|
||||
_ = usage;
|
||||
_ = properties;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn isFormatSupported(
|
||||
self: *Self,
|
||||
format: vk.Format,
|
||||
image_type: vk.ImageType,
|
||||
tiling: vk.ImageTiling,
|
||||
usage: vk.ImageUsageFlags,
|
||||
) VkError!bool {
|
||||
const format_properties = try self.interface.getFormatProperties(format);
|
||||
const format_features = switch (tiling) {
|
||||
.linear => format_properties.linear_tiling_features,
|
||||
.optimal => format_properties.optimal_tiling_features,
|
||||
else => return false,
|
||||
};
|
||||
|
||||
if (!checkFormatUsage(usage, format_features))
|
||||
return false;
|
||||
|
||||
const all_recognized_usages: vk.ImageUsageFlags = .{
|
||||
.sampled_bit = true,
|
||||
.storage_bit = true,
|
||||
.color_attachment_bit = true,
|
||||
.depth_stencil_attachment_bit = true,
|
||||
.input_attachment_bit = true,
|
||||
.transfer_src_bit = true,
|
||||
.transfer_dst_bit = true,
|
||||
.transient_attachment_bit = true,
|
||||
};
|
||||
|
||||
if (usage.subtract(all_recognized_usages).toInt() != 0)
|
||||
return false;
|
||||
|
||||
if (usage.sampled_bit) {
|
||||
if (tiling != .linear and !format_features.sampled_image_bit)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tiling == .linear) {
|
||||
if (image_type != .@"2d")
|
||||
return false;
|
||||
if (base.format.isDepth(format) or base.format.isStencil(format))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn checkFormatUsage(usage: vk.ImageUsageFlags, features: vk.FormatFeatureFlags) bool {
|
||||
if (usage.sampled_bit and !features.sampled_image_bit)
|
||||
return false;
|
||||
if (usage.storage_bit and !features.storage_image_bit)
|
||||
return false;
|
||||
if (usage.color_attachment_bit and !features.color_attachment_bit)
|
||||
return false;
|
||||
if (usage.depth_stencil_attachment_bit and !features.depth_stencil_attachment_bit)
|
||||
return false;
|
||||
if (usage.input_attachment_bit and !(features.color_attachment_bit or features.depth_stencil_attachment_bit))
|
||||
return false;
|
||||
if (usage.transfer_src_bit and !features.transfer_src_bit)
|
||||
return false;
|
||||
if (usage.transfer_dst_bit and !features.transfer_dst_bit)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getSurfaceSupportKHR(_: *Interface, _: u32, _: *SurfaceKHR) VkError!bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
const CpuidRegs = packed struct {
|
||||
eax: u32,
|
||||
ebx: u32,
|
||||
ecx: u32,
|
||||
edx: u32,
|
||||
};
|
||||
|
||||
fn cpuid(leaf_id: u32, subleaf_id: u32) CpuidRegs {
|
||||
comptime {
|
||||
switch (builtin.cpu.arch) {
|
||||
.x86, .x86_64 => {},
|
||||
else => @compileError("cpuid is only available on x86/x86_64"),
|
||||
}
|
||||
}
|
||||
|
||||
var eax: u32 = 0;
|
||||
var ebx: u32 = 0;
|
||||
var ecx: u32 = 0;
|
||||
var edx: u32 = 0;
|
||||
|
||||
asm volatile ("cpuid"
|
||||
: [_] "={eax}" (eax),
|
||||
[_] "={ebx}" (ebx),
|
||||
[_] "={ecx}" (ecx),
|
||||
[_] "={edx}" (edx),
|
||||
: [_] "{eax}" (leaf_id),
|
||||
[_] "{ecx}" (subleaf_id),
|
||||
);
|
||||
|
||||
return .{
|
||||
.eax = eax,
|
||||
.ebx = ebx,
|
||||
.ecx = ecx,
|
||||
.edx = edx,
|
||||
};
|
||||
}
|
||||
|
||||
fn writeU32Le(dst: []u8, offset: usize, value: u32) void {
|
||||
dst[offset + 0] = @truncate(value);
|
||||
dst[offset + 1] = @truncate(value >> 8);
|
||||
dst[offset + 2] = @truncate(value >> 16);
|
||||
dst[offset + 3] = @truncate(value >> 24);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Pipeline;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.initCompute(device, allocator, cache, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.initGraphics(device, allocator, cache, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PipelineCache;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.PipelineLayout;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.PipelineLayoutCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.QueryPool;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.QueryPoolCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.free(interface.queries);
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const proto = lib.proto;
|
||||
|
||||
const PhiCommandBuffer = @import("PhiCommandBuffer.zig");
|
||||
const PhiDevice = @import("PhiDevice.zig");
|
||||
const PhiTransport = @import("PhiTransport.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Queue;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Interface {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(allocator, device, index, family_index, flags);
|
||||
interface.dispatch_table = &.{
|
||||
.bindSparse = bindSparse,
|
||||
.submit = submit,
|
||||
.waitIdle = waitIdle,
|
||||
};
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return &self.interface;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn bindSparse(interface: *Interface, info: []const vk.BindSparseInfo, fence: ?*base.Fence) VkError!void {
|
||||
_ = interface;
|
||||
_ = info;
|
||||
_ = fence;
|
||||
return VkError.FeatureNotPresent;
|
||||
}
|
||||
|
||||
pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, fence: ?*base.Fence) VkError!void {
|
||||
const device: *PhiDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
|
||||
|
||||
for (infos) |info| {
|
||||
for (info.wait_semaphores.items) |semaphore| {
|
||||
try semaphore.wait();
|
||||
}
|
||||
|
||||
for (info.command_buffers.items) |command_buffer| {
|
||||
const phi_command_buffer: *PhiCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
|
||||
|
||||
const work_execution_request: proto.PhiWorkExecutionRequest = .{
|
||||
.cmd_count = phi_command_buffer.serialized_cmd_count,
|
||||
.command_buffer_size = phi_command_buffer.commands.items.len,
|
||||
};
|
||||
const payload_size = @sizeOf(proto.PhiWorkExecutionRequest) + phi_command_buffer.commands.items.len;
|
||||
const allocator = interface.host_allocator.allocator();
|
||||
const payload = allocator.alloc(u8, payload_size) catch return VkError.OutOfHostMemory;
|
||||
defer allocator.free(payload);
|
||||
|
||||
@memcpy(payload[0..@sizeOf(proto.PhiWorkExecutionRequest)], std.mem.asBytes(&work_execution_request));
|
||||
@memcpy(payload[@sizeOf(proto.PhiWorkExecutionRequest)..], phi_command_buffer.commands.items);
|
||||
|
||||
// Synchronous queues for now
|
||||
var reply = std.mem.zeroes(proto.PhiWorkExecutionReply);
|
||||
try device.transport.request(proto.PHI_PACKET_WORK_EXECUTION, payload, std.mem.asBytes(&reply));
|
||||
|
||||
if (reply.result.status != proto.PHI_STATUS_OK) {
|
||||
return PhiTransport.statusToErr(reply.result.status);
|
||||
}
|
||||
}
|
||||
|
||||
for (info.signal_semaphores.items) |semaphore| {
|
||||
try semaphore.signal();
|
||||
}
|
||||
}
|
||||
if (fence) |value| {
|
||||
try value.signal();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn waitIdle(interface: *Interface) VkError!void {
|
||||
_ = interface;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.RenderPass;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.RenderPassCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
|
||||
interface.vtable = &.{
|
||||
.destroy = destroy,
|
||||
.getRenderAreaGranularity = getRenderAreaGranularity,
|
||||
};
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn getRenderAreaGranularity(_: *Interface) vk.Extent2D {
|
||||
return .{
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.Sampler;
|
||||
|
||||
interface: Interface,
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{ .interface = interface };
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
allocator.destroy(self);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
const base = @import("base");
|
||||
|
||||
const VkError = base.VkError;
|
||||
|
||||
const Self = @This();
|
||||
pub const Interface = base.ShaderModule;
|
||||
|
||||
interface: Interface,
|
||||
ref_count: std.atomic.Value(usize),
|
||||
|
||||
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*Self {
|
||||
const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
|
||||
errdefer allocator.destroy(self);
|
||||
|
||||
var interface = try Interface.init(device, allocator, info);
|
||||
interface.vtable = &.{ .destroy = destroy };
|
||||
|
||||
self.* = .{
|
||||
.interface = interface,
|
||||
.ref_count = std.atomic.Value(usize).init(1),
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
|
||||
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
|
||||
self.unref(allocator);
|
||||
}
|
||||
|
||||
pub fn drop(self: *Self, allocator: std.mem.Allocator) void {
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn ref(self: *Self) void {
|
||||
_ = self.ref_count.fetchAdd(1, .monotonic);
|
||||
}
|
||||
|
||||
pub fn unref(self: *Self, allocator: std.mem.Allocator) void {
|
||||
if (self.ref_count.fetchSub(1, .release) == 1) {
|
||||
self.drop(allocator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
const std = @import("std");
|
||||
const base = @import("base");
|
||||
const lib = @import("lib.zig");
|
||||
const scif = @import("scif.zig");
|
||||
|
||||
const VkError = base.VkError;
|
||||
const proto = lib.proto;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
epd: scif.epd_t,
|
||||
sequence: u64 = 1,
|
||||
mutex: std.Io.Mutex = .init,
|
||||
instance: *base.Instance,
|
||||
|
||||
pub fn init(instance: *base.Instance, node_id: u16) VkError!Self {
|
||||
try scif.load();
|
||||
errdefer scif.unload();
|
||||
|
||||
const epd = scif.open();
|
||||
if (epd < 0) {
|
||||
std.log.scoped(.PhiTransport).err("SCIF open failed", .{});
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
errdefer _ = scif.close(epd);
|
||||
|
||||
var dst: scif.PortId = .{
|
||||
.node = node_id,
|
||||
.port = @intCast(proto.PHI_SCIF_PORT),
|
||||
};
|
||||
|
||||
if (scif.connect(epd, &dst) < 0) {
|
||||
std.log.scoped(.PhiTransport).err("SCIF connection to node {d} port {d} failed", .{ dst.node, dst.port });
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
|
||||
var self: Self = .{
|
||||
.epd = epd,
|
||||
.instance = instance,
|
||||
};
|
||||
try self.handshake();
|
||||
|
||||
std.log.scoped(.PhiTransport).info("Successfully connected", .{});
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = scif.close(self.epd);
|
||||
scif.unload();
|
||||
std.log.scoped(.PhiTransport).info("Closed connection", .{});
|
||||
}
|
||||
|
||||
pub fn request(self: *Self, command: c_uint, payload: []const u8, reply_payload: []u8) VkError!void {
|
||||
self.mutex.lock(self.instance.io()) catch return VkError.DeviceLost;
|
||||
defer self.mutex.unlock(self.instance.io());
|
||||
|
||||
const sequence = self.sequence;
|
||||
self.sequence += 1;
|
||||
|
||||
const header: proto.PhiMessageHeader = .{
|
||||
.magic = proto.PHI_PROTOCOL_MAGIC,
|
||||
.version = proto.PHI_PROTOCOL_VERSION,
|
||||
.type = @intCast(command),
|
||||
.sequence = sequence,
|
||||
.payload_size = payload.len,
|
||||
};
|
||||
|
||||
try self.writeAll(std.mem.asBytes(&header));
|
||||
try self.writeAll(payload);
|
||||
|
||||
// SAFETY: will be entirely written with the readAll
|
||||
var reply_header: proto.PhiMessageHeader = undefined;
|
||||
try self.readAll(std.mem.asBytes(&reply_header));
|
||||
|
||||
if (reply_header.magic != proto.PHI_PROTOCOL_MAGIC or
|
||||
reply_header.version != proto.PHI_PROTOCOL_VERSION or
|
||||
reply_header.type != header.type or
|
||||
reply_header.sequence != sequence or
|
||||
reply_header.payload_size != reply_payload.len)
|
||||
{
|
||||
std.log.scoped(.PhiTransport).err("Invalid Phi reply header", .{});
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
|
||||
try self.readAll(reply_payload);
|
||||
}
|
||||
|
||||
pub fn statusToErr(status: c_int) VkError {
|
||||
return switch (status) {
|
||||
proto.PHI_STATUS_OUT_OF_MEMORY => VkError.OutOfDeviceMemory,
|
||||
proto.PHI_STATUS_UNSUPPORTED_VERSION => VkError.InitializationFailed,
|
||||
else => VkError.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
fn writeAll(self: *Self, bytes: []const u8) VkError!void {
|
||||
var offset: usize = 0;
|
||||
while (offset < bytes.len) {
|
||||
const written = scif.send(self.epd, bytes[offset..].ptr, bytes.len - offset, scif.send_block);
|
||||
if (written <= 0) {
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
offset += @intCast(written);
|
||||
}
|
||||
}
|
||||
|
||||
fn readAll(self: *Self, bytes: []u8) VkError!void {
|
||||
var offset: usize = 0;
|
||||
while (offset < bytes.len) {
|
||||
const read = scif.recv(self.epd, bytes[offset..].ptr, bytes.len - offset, scif.recv_block);
|
||||
if (read <= 0) {
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
offset += @intCast(read);
|
||||
}
|
||||
}
|
||||
|
||||
fn handshake(self: *Self) VkError!void {
|
||||
const request_payload: proto.PhiHelloRequest = .{
|
||||
.host_protocol_version = proto.PHI_PROTOCOL_VERSION,
|
||||
.reserved = 0,
|
||||
};
|
||||
|
||||
// SAFETY: will be entirely written by the request
|
||||
var reply: proto.PhiHelloReply = undefined;
|
||||
try self.request(proto.PHI_PACKET_HELLO, std.mem.asBytes(&request_payload), std.mem.asBytes(&reply));
|
||||
if (reply.result.status != proto.PHI_STATUS_OK) {
|
||||
return statusToErr(reply.result.status);
|
||||
}
|
||||
if (reply.device_protocol_version != proto.PHI_PROTOCOL_VERSION) {
|
||||
std.log.scoped(.PhiTransport).err("Unsupported Phi protocol version {d}", .{reply.device_protocol_version});
|
||||
return VkError.InitializationFailed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
const std = @import("std");
|
||||
const vk = @import("vulkan");
|
||||
pub const base = @import("base");
|
||||
|
||||
pub const c = @import("phi_c");
|
||||
pub const proto = @import("phi_protocol_c");
|
||||
pub const config = base.config;
|
||||
pub const mic = @import("miclib");
|
||||
pub const scif = @import("scif.zig");
|
||||
|
||||
pub const PhiInstance = @import("PhiInstance.zig");
|
||||
pub const PhiDevice = @import("PhiDevice.zig");
|
||||
pub const PhiPhysicalDevice = @import("PhiPhysicalDevice.zig");
|
||||
pub const PhiQueue = @import("PhiQueue.zig");
|
||||
pub const PhiTransport = @import("PhiTransport.zig");
|
||||
|
||||
pub const PhiBinarySemaphore = @import("PhiBinarySemaphore.zig");
|
||||
pub const PhiBuffer = @import("PhiBuffer.zig");
|
||||
pub const PhiBufferView = @import("PhiBufferView.zig");
|
||||
pub const PhiCommandBuffer = @import("PhiCommandBuffer.zig");
|
||||
pub const PhiCommandPool = @import("PhiCommandPool.zig");
|
||||
pub const PhiDescriptorPool = @import("PhiDescriptorPool.zig");
|
||||
pub const PhiDescriptorSet = @import("PhiDescriptorSet.zig");
|
||||
pub const PhiDescriptorSetLayout = @import("PhiDescriptorSetLayout.zig");
|
||||
pub const PhiDeviceMemory = @import("PhiDeviceMemory.zig");
|
||||
pub const PhiEvent = @import("PhiEvent.zig");
|
||||
pub const PhiFence = @import("PhiFence.zig");
|
||||
pub const PhiFramebuffer = @import("PhiFramebuffer.zig");
|
||||
pub const PhiImage = @import("PhiImage.zig");
|
||||
pub const PhiImageView = @import("PhiImageView.zig");
|
||||
pub const PhiPipeline = @import("PhiPipeline.zig");
|
||||
pub const PhiPipelineCache = @import("PhiPipelineCache.zig");
|
||||
pub const PhiPipelineLayout = @import("PhiPipelineLayout.zig");
|
||||
pub const PhiQueryPool = @import("PhiQueryPool.zig");
|
||||
pub const PhiRenderPass = @import("PhiRenderPass.zig");
|
||||
pub const PhiSampler = @import("PhiSampler.zig");
|
||||
pub const PhiShaderModule = @import("PhiShaderModule.zig");
|
||||
|
||||
pub const Instance = PhiInstance;
|
||||
|
||||
pub const driver_name = "Phi";
|
||||
|
||||
pub const physical_device_default_name = "Intel(R) Xeon Phi(TM) Coprocessor";
|
||||
|
||||
pub const vulkan_version = vk.makeApiVersion(
|
||||
0,
|
||||
config.phi_vulkan_version.major,
|
||||
config.phi_vulkan_version.minor,
|
||||
config.phi_vulkan_version.patch,
|
||||
);
|
||||
|
||||
pub const std_options = base.std_options;
|
||||
|
||||
comptime {
|
||||
_ = base;
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(PhiBinarySemaphore);
|
||||
std.testing.refAllDecls(PhiBuffer);
|
||||
std.testing.refAllDecls(PhiBufferView);
|
||||
std.testing.refAllDecls(PhiCommandBuffer);
|
||||
std.testing.refAllDecls(PhiCommandPool);
|
||||
std.testing.refAllDecls(PhiDescriptorPool);
|
||||
std.testing.refAllDecls(PhiDescriptorSet);
|
||||
std.testing.refAllDecls(PhiDescriptorSetLayout);
|
||||
std.testing.refAllDecls(PhiDevice);
|
||||
std.testing.refAllDecls(PhiDeviceMemory);
|
||||
std.testing.refAllDecls(PhiEvent);
|
||||
std.testing.refAllDecls(PhiFence);
|
||||
std.testing.refAllDecls(PhiFramebuffer);
|
||||
std.testing.refAllDecls(PhiImage);
|
||||
std.testing.refAllDecls(PhiImageView);
|
||||
std.testing.refAllDecls(PhiInstance);
|
||||
std.testing.refAllDecls(PhiPhysicalDevice);
|
||||
std.testing.refAllDecls(PhiTransport);
|
||||
std.testing.refAllDecls(scif);
|
||||
std.testing.refAllDecls(PhiPipeline);
|
||||
std.testing.refAllDecls(PhiPipelineCache);
|
||||
std.testing.refAllDecls(PhiPipelineLayout);
|
||||
std.testing.refAllDecls(PhiQueryPool);
|
||||
std.testing.refAllDecls(PhiQueue);
|
||||
std.testing.refAllDecls(PhiRenderPass);
|
||||
std.testing.refAllDecls(PhiSampler);
|
||||
std.testing.refAllDecls(PhiShaderModule);
|
||||
std.testing.refAllDecls(base);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#include <Buffer.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int PhiIsBufferCommand(const PhiCmdHeader* header)
|
||||
{
|
||||
switch((PhiCmdType)header->type)
|
||||
{
|
||||
case PHI_CMD_COPY_BUFFER:
|
||||
case PHI_CMD_FILL_BUFFER:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static PhiStatus CopyBuffer(PhiCommandReader* reader)
|
||||
{
|
||||
PhiCmdCopyBuffer command;
|
||||
PhiStatus status = PhiReadCommandData(reader, &command, sizeof(command));
|
||||
if(status != PHI_STATUS_OK)
|
||||
return status;
|
||||
|
||||
if(command.src_memory == 0 || command.dst_memory == 0)
|
||||
return PHI_STATUS_INVALID_HANDLE;
|
||||
|
||||
void* dst = (void*)((uintptr_t)command.dst_memory + (uintptr_t)command.dst_offset);
|
||||
const void* src = (const void*)((uintptr_t)command.src_memory + (uintptr_t)command.src_offset);
|
||||
|
||||
memcpy(dst, src, (size_t)command.size);
|
||||
|
||||
return PHI_STATUS_OK;
|
||||
}
|
||||
|
||||
static PhiStatus FillBuffer(PhiCommandReader* reader)
|
||||
{
|
||||
PhiCmdFillBuffer command;
|
||||
PhiStatus status = PhiReadCommandData(reader, &command, sizeof(command));
|
||||
if(status != PHI_STATUS_OK)
|
||||
return status;
|
||||
|
||||
if(command.memory == 0)
|
||||
return PHI_STATUS_INVALID_HANDLE;
|
||||
|
||||
uint32_t* dst = (uint32_t*)((uintptr_t)command.memory + (uintptr_t)command.offset);
|
||||
|
||||
for(; command.size >= 4; command.size -= 4, dst++)
|
||||
*dst = command.data;
|
||||
|
||||
return PHI_STATUS_OK;
|
||||
}
|
||||
|
||||
PhiStatus PhiExecuteBufferCommand(PhiCommandReader* reader, const PhiCmdHeader* header)
|
||||
{
|
||||
switch((PhiCmdType)header->type)
|
||||
{
|
||||
case PHI_CMD_COPY_BUFFER:
|
||||
return CopyBuffer(reader);
|
||||
case PHI_CMD_FILL_BUFFER:
|
||||
return FillBuffer(reader);
|
||||
|
||||
default:
|
||||
return PHI_STATUS_BAD_MESSAGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef APE_PHI_BUFFER_H
|
||||
#define APE_PHI_BUFFER_H
|
||||
|
||||
#include <CommandBuffer.h>
|
||||
|
||||
int PhiIsBufferCommand(const PhiCmdHeader* header);
|
||||
PhiStatus PhiExecuteBufferCommand(PhiCommandReader* reader, const PhiCmdHeader* header);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
#include <CommandBuffer.h>
|
||||
|
||||
#include <Buffer.h>
|
||||
|
||||
PhiStatus PhiReadCommandData(PhiCommandReader* reader, void* data, uint64_t size)
|
||||
{
|
||||
if(reader->remaining < size)
|
||||
return PHI_STATUS_BAD_MESSAGE;
|
||||
|
||||
if(ReadAll(reader->endpoint, data, (size_t)size) < 0)
|
||||
return PHI_STATUS_BAD_MESSAGE;
|
||||
|
||||
reader->remaining -= size;
|
||||
return PHI_STATUS_OK;
|
||||
}
|
||||
|
||||
int PhiDrainCommandReader(PhiCommandReader* reader)
|
||||
{
|
||||
if(reader->remaining == 0)
|
||||
return 0;
|
||||
|
||||
int result = DrainPayload(reader->endpoint, reader->remaining);
|
||||
reader->remaining = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static PhiStatus ReadCommandHeader(PhiCommandReader* reader, PhiCmdHeader* command_header)
|
||||
{
|
||||
PhiStatus status = PhiReadCommandData(reader, command_header, sizeof(*command_header));
|
||||
if(status != PHI_STATUS_OK)
|
||||
return status;
|
||||
|
||||
if(command_header->magic != PHI_COMMAND_MAGIC)
|
||||
return PHI_STATUS_BAD_MESSAGE;
|
||||
|
||||
return PHI_STATUS_OK;
|
||||
}
|
||||
|
||||
static PhiStatus ExecuteCommand(PhiCommandReader* reader, const PhiCmdHeader* command_header)
|
||||
{
|
||||
if(PhiIsBufferCommand(command_header))
|
||||
return PhiExecuteBufferCommand(reader, command_header);
|
||||
|
||||
return PHI_STATUS_BAD_MESSAGE;
|
||||
}
|
||||
|
||||
int HandleWorkExecution(scif_epd_t endpoint, const PhiMessageHeader* header)
|
||||
{
|
||||
PhiWorkExecutionRequest request;
|
||||
PhiWorkExecutionReply reply = {
|
||||
.result = {
|
||||
.status = PHI_STATUS_OK,
|
||||
.reserved = 0,
|
||||
},
|
||||
};
|
||||
|
||||
if(header->payload_size < sizeof(request))
|
||||
{
|
||||
if(DrainPayload(endpoint, header->payload_size) < 0)
|
||||
return -1;
|
||||
reply.result.status = PHI_STATUS_BAD_MESSAGE;
|
||||
return SendReply(endpoint, header, &reply, sizeof(reply));
|
||||
}
|
||||
if(ReadAll(endpoint, &request, sizeof(request)) < 0)
|
||||
return -1;
|
||||
|
||||
PhiCommandReader reader = {
|
||||
.endpoint = endpoint,
|
||||
.remaining = header->payload_size - sizeof(request),
|
||||
};
|
||||
|
||||
if(reader.remaining != request.command_buffer_size)
|
||||
{
|
||||
if(PhiDrainCommandReader(&reader) < 0)
|
||||
return -1;
|
||||
reply.result.status = PHI_STATUS_BAD_MESSAGE;
|
||||
return SendReply(endpoint, header, &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
for(uint64_t cmd_index = 0; cmd_index < request.cmd_count; ++cmd_index)
|
||||
{
|
||||
PhiCmdHeader cmd_header;
|
||||
reply.result.status = ReadCommandHeader(&reader, &cmd_header);
|
||||
if(reply.result.status != PHI_STATUS_OK)
|
||||
break;
|
||||
|
||||
reply.result.status = ExecuteCommand(&reader, &cmd_header);
|
||||
if(reply.result.status != PHI_STATUS_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
if(reader.remaining > 0 && PhiDrainCommandReader(&reader) < 0)
|
||||
return -1;
|
||||
|
||||
return SendReply(endpoint, header, &reply, sizeof(reply));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef APE_PHI_COMMAND_BUFFER_H
|
||||
#define APE_PHI_COMMAND_BUFFER_H
|
||||
|
||||
#include <Daemon.h>
|
||||
|
||||
typedef struct PhiCommandReader
|
||||
{
|
||||
scif_epd_t endpoint;
|
||||
uint64_t remaining;
|
||||
} PhiCommandReader;
|
||||
|
||||
int HandleWorkExecution(scif_epd_t endpoint, const PhiMessageHeader* header);
|
||||
int PhiDrainCommandReader(PhiCommandReader* reader);
|
||||
PhiStatus PhiReadCommandData(PhiCommandReader* reader, void* data, uint64_t size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,199 @@
|
||||
#include <Daemon.h>
|
||||
#include <CommandBuffer.h>
|
||||
#include <Logger.h>
|
||||
#include <Memory.h>
|
||||
|
||||
static int HandleHello(scif_epd_t endpoint, const PhiMessageHeader* header)
|
||||
{
|
||||
PhiHelloRequest request;
|
||||
PhiHelloReply reply = {
|
||||
.result = {
|
||||
.status = PHI_STATUS_OK,
|
||||
.reserved = 0,
|
||||
},
|
||||
.device_protocol_version = PHI_PROTOCOL_VERSION,
|
||||
.pointer_bits = (uint32_t)(sizeof(void *) * 8u),
|
||||
};
|
||||
|
||||
if(header->payload_size != sizeof(request))
|
||||
{
|
||||
if(DrainPayload(endpoint, header->payload_size) < 0)
|
||||
return -1;
|
||||
reply.result.status = PHI_STATUS_BAD_MESSAGE;
|
||||
return SendReply(endpoint, header, &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
if(ReadAll(endpoint, &request, sizeof(request)) < 0)
|
||||
return -1;
|
||||
|
||||
if(request.host_protocol_version != PHI_PROTOCOL_VERSION)
|
||||
reply.result.status = PHI_STATUS_UNSUPPORTED_VERSION;
|
||||
|
||||
return SendReply(endpoint, header, &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
scif_epd_t StartDaemon()
|
||||
{
|
||||
PhiLogInfo("Starting the daemon...");
|
||||
|
||||
scif_epd_t endpoint = scif_open();
|
||||
if(endpoint < 0)
|
||||
{
|
||||
PhiLogError("Failed to create SCIF endpoint");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(scif_bind(endpoint, PHI_SCIF_PORT) < 0)
|
||||
{
|
||||
PhiLogError("Failed to bind SCIF port");
|
||||
scif_close(endpoint);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(scif_listen(endpoint, 1) < 0)
|
||||
{
|
||||
PhiLogError("Could not listen to SCIF port");
|
||||
scif_close(endpoint);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
void ShutdownDaemon(scif_epd_t endpoint)
|
||||
{
|
||||
PhiLogInfo("Shuting down the daemon...");
|
||||
scif_close(endpoint);
|
||||
}
|
||||
|
||||
int HandlePacket(scif_epd_t endpoint)
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
PhiMessageHeader header;
|
||||
|
||||
if(ReadAll(endpoint, &header, sizeof(header)) < 0)
|
||||
return -1;
|
||||
|
||||
if(header.magic != PHI_PROTOCOL_MAGIC || header.version != PHI_PROTOCOL_VERSION)
|
||||
{
|
||||
if(DrainPayload(endpoint, header.payload_size) < 0)
|
||||
return -1;
|
||||
if(SendStatus(endpoint, &header, PHI_STATUS_BAD_MESSAGE) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch((PhiPacketType)header.type)
|
||||
{
|
||||
case PHI_PACKET_HELLO:
|
||||
if(HandleHello(endpoint, &header) < 0)
|
||||
return -1;
|
||||
break;
|
||||
|
||||
case PHI_PACKET_ALLOC_MEMORY:
|
||||
if(HandleAllocMemory(endpoint, &header) < 0)
|
||||
return -1;
|
||||
break;
|
||||
|
||||
case PHI_PACKET_FREE_MEMORY:
|
||||
if(HandleFreeMemory(endpoint, &header) < 0)
|
||||
return -1;
|
||||
break;
|
||||
|
||||
case PHI_PACKET_WORK_EXECUTION:
|
||||
if(HandleWorkExecution(endpoint, &header) < 0)
|
||||
return -1;
|
||||
break;
|
||||
|
||||
case PHI_PACKET_SHUTDOWN:
|
||||
if(DrainPayload(endpoint, header.payload_size) < 0)
|
||||
return -1;
|
||||
if(SendStatus(endpoint, &header, PHI_STATUS_OK) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
if(DrainPayload(endpoint, header.payload_size) < 0)
|
||||
return -1;
|
||||
if(SendStatus(endpoint, &header, PHI_STATUS_UNSUPPORTED_PACKET) < 0)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ReadAll(scif_epd_t endpoint, void* data, size_t size)
|
||||
{
|
||||
uint8_t* bytes = data;
|
||||
size_t offset = 0;
|
||||
|
||||
while(offset < size)
|
||||
{
|
||||
int got = scif_recv(endpoint, bytes + offset, size - offset, SCIF_RECV_BLOCK);
|
||||
if(got <= 0)
|
||||
return -1;
|
||||
offset += (size_t)got;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WriteAll(scif_epd_t endpoint, const void* data, size_t size)
|
||||
{
|
||||
const uint8_t* bytes = data;
|
||||
size_t offset = 0;
|
||||
|
||||
while(offset < size)
|
||||
{
|
||||
int sent = scif_send(endpoint, (void*)(bytes + offset), size - offset, SCIF_SEND_BLOCK);
|
||||
if(sent <= 0)
|
||||
return -1;
|
||||
offset += (size_t)sent;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SendReply(scif_epd_t endpoint, const PhiMessageHeader* request, const void* payload, uint64_t payload_size)
|
||||
{
|
||||
PhiMessageHeader reply = {
|
||||
.magic = PHI_PROTOCOL_MAGIC,
|
||||
.version = PHI_PROTOCOL_VERSION,
|
||||
.type = request->type,
|
||||
.sequence = request->sequence,
|
||||
.payload_size = payload_size,
|
||||
};
|
||||
|
||||
if(WriteAll(endpoint, &reply, sizeof(reply)) < 0)
|
||||
return -1;
|
||||
|
||||
return WriteAll(endpoint, payload, (size_t)payload_size);
|
||||
}
|
||||
|
||||
int SendStatus(scif_epd_t endpoint, const PhiMessageHeader* request, PhiStatus status)
|
||||
{
|
||||
PhiFreeMemoryReply reply = {
|
||||
.result = {
|
||||
.status = status,
|
||||
.reserved = 0,
|
||||
},
|
||||
};
|
||||
|
||||
return SendReply(endpoint, request, &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
int DrainPayload(scif_epd_t endpoint, uint64_t size)
|
||||
{
|
||||
uint8_t buffer[256];
|
||||
|
||||
while(size > 0)
|
||||
{
|
||||
size_t chunk = size < sizeof(buffer) ? (size_t)size : sizeof(buffer);
|
||||
if(ReadAll(endpoint, buffer, chunk) < 0)
|
||||
return -1;
|
||||
size -= chunk;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef APE_PHI_DAEMON_H
|
||||
#define APE_PHI_DAEMON_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <scif.h>
|
||||
|
||||
#include <Protocol.h>
|
||||
|
||||
scif_epd_t StartDaemon();
|
||||
void ShutdownDaemon(scif_epd_t endpoint);
|
||||
|
||||
int DrainPayload(scif_epd_t endpoint, uint64_t size);
|
||||
int HandlePacket(scif_epd_t endpoint);
|
||||
int ReadAll(scif_epd_t endpoint, void* data, size_t size);
|
||||
int SendReply(scif_epd_t endpoint, const PhiMessageHeader* request, const void* payload, uint64_t payload_size);
|
||||
int SendStatus(scif_epd_t endpoint, const PhiMessageHeader* request, PhiStatus status);
|
||||
int WriteAll(scif_epd_t endpoint, const void* data, size_t size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,95 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <Logger.h>
|
||||
|
||||
#define RED 31
|
||||
#define GREEN 32
|
||||
#define BLUE 34
|
||||
#define DEF 0
|
||||
#define BLACK 30
|
||||
#define YELLOW 33
|
||||
#define MAGENTA 35
|
||||
#define CYAN 36
|
||||
#define WHITE 37
|
||||
#define BG_RED 41
|
||||
#define BG_GREEN 42
|
||||
#define BG_BLUE 44
|
||||
#define BG_DEF 0
|
||||
#define BG_BLACK 40
|
||||
#define BG_YELLOW 43
|
||||
#define BG_MAGENTA 45
|
||||
#define BG_CYAN 46
|
||||
#define BG_WHITE 47
|
||||
#define RESET 0
|
||||
#define BOLD 1
|
||||
#define UNDERLINE 4
|
||||
#define INVERSE 7
|
||||
#define BOLD_OFF 21
|
||||
#define UNDERLINE_OFF 24
|
||||
#define INVERSE_OFF 27
|
||||
|
||||
inline static void SetConsoleColor(FILE* file, int code)
|
||||
{
|
||||
fprintf(file, "\033[1;%dm", code);
|
||||
}
|
||||
|
||||
void PhiLog(PhiLogLevel level, const char* fmt, const char* file, const char* function, int line, ...)
|
||||
{
|
||||
time_t now = time(0);
|
||||
struct tm tstruct = *localtime(&now);
|
||||
char buffer[128];
|
||||
strftime(buffer, sizeof(buffer), "%X", &tstruct);
|
||||
|
||||
FILE* out = stdout;
|
||||
|
||||
if(level != PHI_LOG_LEVEL_INFO)
|
||||
out = stderr;
|
||||
|
||||
// Way too much printf calls
|
||||
SetConsoleColor(out, MAGENTA);
|
||||
fprintf(out, "[ApeDriver ");
|
||||
SetConsoleColor(out, GREEN);
|
||||
fprintf(out, "Phi ");
|
||||
SetConsoleColor(out, YELLOW);
|
||||
fprintf(out, "%s", buffer);
|
||||
SetConsoleColor(out, MAGENTA);
|
||||
fputc(']', out);
|
||||
|
||||
switch(level)
|
||||
{
|
||||
case PHI_LOG_LEVEL_INFO:
|
||||
SetConsoleColor(out, BLUE);
|
||||
fprintf(out, "[info] ");
|
||||
break;
|
||||
case PHI_LOG_LEVEL_WARN:
|
||||
SetConsoleColor(out, MAGENTA);
|
||||
fprintf(out, "[warn] ");
|
||||
break;
|
||||
case PHI_LOG_LEVEL_ERR:
|
||||
case PHI_LOG_LEVEL_FATAL:
|
||||
SetConsoleColor(out, RED);
|
||||
fprintf(out, "[err] ");
|
||||
break;
|
||||
}
|
||||
|
||||
SetConsoleColor(out, RESET);
|
||||
|
||||
va_list argptr;
|
||||
va_start(argptr, line);
|
||||
vfprintf(out, fmt, argptr);
|
||||
va_end(argptr);
|
||||
|
||||
fputc('\n', out);
|
||||
|
||||
if(level == PHI_LOG_LEVEL_FATAL)
|
||||
{
|
||||
SetConsoleColor(out, BG_RED);
|
||||
fprintf(out, "Fatal Error: emergency exit\n");
|
||||
SetConsoleColor(out, BG_DEF);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user