Compare commits

...

47 Commits

Author SHA1 Message Date
kbz_8 9b1b223186 adding backend agnostic IR
Test / build_and_test (push) Successful in 5m16s
Build / build (push) Successful in 6m50s
2026-07-20 22:07:41 +02:00
kbz_8 2375abf688 fixing linter errors
Test / build_and_test (push) Successful in 5m28s
Build / build (push) Successful in 7m26s
2026-07-15 15:39:16 +02:00
kbz_8 1640013546 [Flint] adding binary semaphores
Test / build_and_test (push) Successful in 5m0s
Build / build (push) Successful in 7m28s
2026-07-14 00:29:42 +02:00
kbz_8 b3c4248cae [Flint] implementing fences and improving image/buffer copy
Test / build_and_test (push) Successful in 5m6s
Build / build (push) Successful in 6m59s
2026-07-13 18:50:13 +02:00
kbz_8 348e8ac66e [Fint] adding basic copy and blit commands
Test / build_and_test (push) Successful in 4m56s
Build / build (push) Successful in 7m20s
2026-07-11 19:09:03 +02:00
kbz_8 d3cd6b18c8 updating readme
Test / build_and_test (push) Successful in 5m20s
Build / build (push) Successful in 7m16s
2026-07-10 17:44:25 +02:00
kbz_8 0224b5675f implementing device creation, device memory, basic buffers copy and buffers fill in Flint
Test / build_and_test (push) Successful in 4m51s
Build / build (push) Successful in 6m44s
2026-07-10 00:19:29 +02:00
kbz_8 3044bb37a5 fixing fillBuffer
Test / build_and_test (push) Successful in 5m23s
Build / build (push) Successful in 6m39s
2026-07-08 01:03:09 +02:00
kbz_8 23f6d70e6c adding base command buffer submit and some buffer commands in phi
Build / build (push) Failing after 3m12s
Test / build_and_test (push) Failing after 3m12s
2026-07-08 00:58:30 +02:00
kbz_8 4f89d24801 fixing CI
Test / build_and_test (push) Successful in 4m54s
Build / build (push) Successful in 6m46s
2026-07-07 02:14:31 +02:00
kbz_8 54941a7702 fixing CI
Build / build (push) Failing after 49s
Test / build_and_test (push) Failing after 50s
2026-07-07 02:07:49 +02:00
kbz_8 9ed90a6e26 fixing CI
Test / build_and_test (push) Failing after 48s
Build / build (push) Failing after 48s
2026-07-07 01:59:31 +02:00
kbz_8 a7fce43f70 fixing ape build
Test / build_and_test (push) Failing after 2m24s
Build / build (push) Failing after 2m25s
2026-07-07 01:36:52 +02:00
kbz_8 11ed946c63 fixing ape build
Test / build_and_test (push) Failing after 2m24s
Build / build (push) Failing after 2m59s
2026-07-07 01:32:42 +02:00
kbz_8 1f6b0b1f37 adding daemon upload from phi device
Test / build_and_test (push) Failing after 2m40s
Build / build (push) Failing after 3m14s
2026-07-07 01:02:45 +02:00
kbz_8 6541d4dfd6 fixing CI
Test / build_and_test (push) Successful in 4m19s
Build / build (push) Successful in 6m16s
2026-07-06 03:01:19 +02:00
kbz_8 9fa97919b3 implementing foundations of xeon phi daemon
Build / build (push) Has been cancelled
Test / build_and_test (push) Has been cancelled
2026-07-06 02:58:03 +02:00
kbz_8 b6ef13c198 whoops
Test / build_and_test (push) Successful in 2m36s
Build / build (push) Successful in 4m22s
2026-07-04 22:35:56 +02:00
kbz_8 67014b9c4e cleaning some corners
Build / build (push) Failing after 35s
Test / build_and_test (push) Failing after 39s
2026-07-04 22:33:57 +02:00
kbz_8 e6538d221f fixing FragCoord derivatives
Test / build_and_test (push) Successful in 1m35s
Build / build (push) Successful in 3m10s
2026-07-04 01:49:17 +02:00
kbz_8 bfc5c3933c adding BC compressed format support
Test / build_and_test (push) Successful in 2m21s
Build / build (push) Successful in 3m58s
2026-07-04 01:17:18 +02:00
kbz_8 3a79103ca3 fixing crash
Test / build_and_test (push) Successful in 1m37s
Build / build (push) Successful in 5m26s
2026-07-03 22:06:14 +02:00
kbz_8 ea1e613fd1 bumping spirv interpreter
Test / build_and_test (push) Successful in 1m32s
Build / build (push) Successful in 3m4s
2026-07-03 14:46:11 +02:00
kbz_8 912b510c67 added per-output centroid flag to vertex outputs
Test / build_and_test (push) Successful in 1m32s
Build / build (push) Successful in 3m16s
2026-07-03 00:52:48 +02:00
kbz_8 63a39f1e72 bumping spirv interpreter
Test / build_and_test (push) Successful in 1m36s
Build / build (push) Successful in 3m15s
2026-07-03 00:32:21 +02:00
kbz_8 d4ac14ae92 fixing vertex outputs
Test / build_and_test (push) Successful in 2m19s
Build / build (push) Successful in 4m2s
2026-07-02 21:00:56 +02:00
kbz_8 23c3731f06 reswitching to async queue
Test / build_and_test (push) Successful in 1m28s
Build / build (push) Successful in 3m3s
2026-07-02 03:14:09 +02:00
kbz_8 a7c1fd6ee3 bumping spirv interpreter
Test / build_and_test (push) Successful in 1m30s
Build / build (push) Successful in 3m12s
2026-07-02 02:19:53 +02:00
kbz_8 7dc2ba7549 fixing vulkan entrypoints for extensions
Test / build_and_test (push) Successful in 2m13s
Build / build (push) Successful in 3m57s
2026-07-02 00:39:24 +02:00
kbz_8 df0d5b7321 whoops
Build / build (push) Successful in 4m38s
Test / build_and_test (push) Successful in 2m50s
2026-07-01 21:46:18 +02:00
kbz_8 0d57851bc0 fixing test CI
Test / build_and_test (push) Failing after 24s
Build / build (push) Has been cancelled
2026-07-01 21:45:20 +02:00
kbz_8 cb6af550e2 fixing varyings
Test / build_and_test (push) Failing after 29s
Build / build (push) Successful in 43s
2026-07-01 21:27:05 +02:00
kbz_8 75637b7177 fixing phi physical device
Test / build_and_test (push) Failing after 20s
Build / build (push) Failing after 22s
2026-07-01 16:08:11 +02:00
kbz_8 5621c62e3d adding xeon phi enumeration
Build / build (push) Failing after 26s
Test / build_and_test (push) Failing after 26s
2026-07-01 17:00:15 +02:00
kbz_8 bbc975fb38 adding base for Xeon Phi driver
Test / build_and_test (push) Successful in 36s
Build / build (push) Successful in 42s
2026-07-01 14:58:55 +02:00
kbz_8 5b182580a7 fixing wsi crashes
Build / build (push) Successful in 2m19s
Test / build_and_test (push) Successful in 1m54s
2026-07-01 13:56:47 +02:00
kbz_8 762d414d54 minor fixes
Build / build (push) Successful in 2m27s
Test / build_and_test (push) Successful in 2m11s
2026-06-30 20:48:47 +02:00
kbz_8 7933bea68a fixing synchronization issues, implementing missing entrypoints
Test / build_and_test (push) Successful in 2m13s
Build / build (push) Successful in 2m30s
2026-06-30 14:36:20 +02:00
kbz_8 b83ddd52a4 bumping spirv interpreter
Build / build (push) Successful in 2m26s
Test / build_and_test (push) Successful in 2m10s
2026-06-30 01:50:49 +02:00
kbz_8 d145f85d17 improving robust buffer support
Build / build (push) Successful in 2m33s
Test / build_and_test (push) Successful in 2m11s
2026-06-29 23:22:31 +02:00
kbz_8 14ce2c6f9b fixing compute shared atomic memory, fixing vertex/fragment payloads, implementing early fragment depth tests
Test / build_and_test (push) Successful in 2m9s
Build / build (push) Successful in 2m35s
2026-06-29 14:31:31 +02:00
kbz_8 7c6e0dc2a8 improving push constants support, fixing image resolving, lots of rasterizer fixes
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 0s
2026-06-28 13:26:15 +02:00
kbz_8 b13a256eb6 adding vkGetBufferDeviceAddress
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 7s
2026-06-26 23:28:32 +02:00
kbz_8 47c63705e2 fixing spirv interpreter image API functions
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 0s
2026-06-21 14:26:32 +02:00
kbz_8 d1a6728864 lod go brrrrrr
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 1s
2026-06-21 00:54:52 +02:00
kbz_8 291c65ed18 improving renderpasses support
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 7s
2026-06-20 02:41:37 +02:00
kbz_8 4d4578ff46 adding fragdepth propagation, adjusting attachment sizing
Build / build (push) Failing after 0s
Test / build_and_test (push) Failing after 0s
2026-06-19 22:20:59 +02:00
184 changed files with 18027 additions and 1131 deletions
+295
View File
@@ -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
+59 -3
View File
@@ -18,14 +18,54 @@ jobs:
- uses: https://codeberg.org/mlugg/setup-zig@v2 - 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 - name: Building Ape
run: zig build ape run: zig build ape --release=safe
- name: Building Soft - name: Building Soft
run: zig build soft run: zig build soft --release=safe
- name: Building Flint - 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 - name: Generating docs
run: zig build docs run: zig build docs
@@ -42,3 +82,19 @@ jobs:
local-path: "./zig-out/docs" local-path: "./zig-out/docs"
remote-path: "/www" remote-path: "/www"
sync: full 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
+46 -3
View File
@@ -15,11 +15,54 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: https://codeberg.org/mlugg/setup-zig@v2 - 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 - name: Ape Tests
run: zig build test-ape run: zig build test-ape --release=safe
- name: Soft Tests - name: Soft Tests
run: zig build test-soft run: zig build test-soft --release=safe
- name: Flint Tests - 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
-2
View File
@@ -8,8 +8,6 @@ scripts/__pycache__/
*.o *.o
.gdb_history .gdb_history
*.json *.json
*.png
!logo.png
*.bin *.bin
*.qpa *.qpa
*.xml *.xml
+409 -6
View File
@@ -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. \ It does not seek to produce a performant or production-worthy driver. \
*The gods are merciful, but not that merciful.* *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.\ 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. 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 vkCmdResetQueryPool | âś… Implemented
vkCmdResolveImage | âś… Implemented vkCmdResolveImage | âś… Implemented
vkCmdSetBlendConstants | âś… Implemented vkCmdSetBlendConstants | âś… Implemented
vkCmdSetDepthBias | ⚙️ WIP vkCmdSetDepthBias | ✅ Implemented
vkCmdSetDepthBounds | ⚙️ WIP vkCmdSetDepthBounds | ✅ Implemented
vkCmdSetDeviceMaskKHR | âś… Implemented vkCmdSetDeviceMaskKHR | âś… Implemented
vkCmdSetEvent | âś… Implemented vkCmdSetEvent | âś… Implemented
vkCmdSetLineWidth | ⚙️ WIP vkCmdSetLineWidth | ✅ Implemented
vkCmdSetScissor | âś… Implemented vkCmdSetScissor | âś… Implemented
vkCmdSetStencilCompareMask | âś… Implemented vkCmdSetStencilCompareMask | âś… Implemented
vkCmdSetStencilReference | âś… Implemented vkCmdSetStencilReference | âś… Implemented
@@ -101,7 +101,7 @@ vkCmdSetStencilWriteMask | âś… Implemented
vkCmdSetViewport | âś… Implemented vkCmdSetViewport | âś… Implemented
vkCmdUpdateBuffer | âś… Implemented vkCmdUpdateBuffer | âś… Implemented
vkCmdWaitEvents | âś… Implemented vkCmdWaitEvents | âś… Implemented
vkCmdWriteTimestamp | ⚙️ WIP vkCmdWriteTimestamp | ✅ Implemented
vkCreateBuffer | âś… Implemented vkCreateBuffer | âś… Implemented
vkCreateBufferView | âś… Implemented vkCreateBufferView | âś… Implemented
vkCreateCommandPool | âś… Implemented vkCreateCommandPool | âś… Implemented
@@ -162,11 +162,14 @@ vkFlushMappedMemoryRanges | âś… Implemented
vkFreeCommandBuffers | âś… Implemented vkFreeCommandBuffers | âś… Implemented
vkFreeDescriptorSets | âś… Implemented vkFreeDescriptorSets | âś… Implemented
vkFreeMemory | âś… Implemented vkFreeMemory | âś… Implemented
vkGetBufferDeviceAddress | âś… Implemented
vkGetBufferDeviceAddressEXT | âś… Implemented
vkGetBufferDeviceAddressKHR | âś… Implemented
vkGetBufferMemoryRequirements | âś… Implemented vkGetBufferMemoryRequirements | âś… Implemented
vkGetDeviceGroupPeerMemoryFeaturesKHR | âś… Implemented vkGetDeviceGroupPeerMemoryFeaturesKHR | âś… Implemented
vkGetDeviceGroupPresentCapabilitiesKHR | âś… Implemented vkGetDeviceGroupPresentCapabilitiesKHR | âś… Implemented
vkGetDeviceGroupSurfacePresentModesKHR | âś… Implemented vkGetDeviceGroupSurfacePresentModesKHR | âś… Implemented
vkGetDeviceMemoryCommitment | ⚙️ WIP vkGetDeviceMemoryCommitment | ✅ Implemented
vkGetDeviceProcAddr | âś… Implemented vkGetDeviceProcAddr | âś… Implemented
vkGetDeviceQueue | âś… Implemented vkGetDeviceQueue | âś… Implemented
vkGetEventStatus | âś… 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. [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 ## License
Released unto the world as MIT for study, experimentation, and the occasional horrified whisper. Released unto the world as MIT for study, experimentation, and the occasional horrified whisper.
+301 -94
View File
@@ -2,7 +2,7 @@ const std = @import("std");
const Step = std.Build.Step; const Step = std.Build.Step;
const builtin = @import("builtin"); 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 { const ImplementationDesc = struct {
name: []const u8, name: []const u8,
@@ -45,6 +45,13 @@ const implementations = [_]ImplementationDesc{
.custom = customFlint, .custom = customFlint,
.options = optionsFlint, .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 { const RunningMode = enum {
@@ -64,6 +71,37 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); 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(.{ const base_mod = b.createModule(.{
.root_source_file = b.path("src/vulkan/lib.zig"), .root_source_file = b.path("src/vulkan/lib.zig"),
.target = target, .target = target,
@@ -81,10 +119,12 @@ pub fn build(b: *std.Build) !void {
const drm = b.dependency("drm", .{}).module("drm"); const drm = b.dependency("drm", .{}).module("drm");
const logs_option: LogType = b.option(LogType, "logs", "Driver logs") orelse .none; 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(); const options = b.addOptions();
options.addOption(std.SemanticVersion, "driver_version", driver_version); options.addOption(std.SemanticVersion, "driver_version", driver_version);
options.addOption(LogType, "logs", logs_option); options.addOption(LogType, "logs", logs_option);
options.addOption(bool, "device_debug_allocator", debug_allocator_option);
base_mod.addImport("vulkan", vulkan); base_mod.addImport("vulkan", vulkan);
base_mod.addImport("zmath", zmath); 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})); const install_step = b.step(impl.name, b.fmt("Build libvulkan_{s}", .{impl.name}));
install_step.dependOn(&lib_install.step); 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 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})); 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); 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 { 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", .{}); 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("run");
run.addArg("--verbose"); run.addArg("--timeout");
run.addArg("60");
run.addArg("--deqp"); run.addArg("--deqp");
run.addArg(cts_exe_path); run.addArg(cts_exe_path);
run.addArg("--caselist"); run.addArg("--caselist");
@@ -469,3 +425,254 @@ fn addMultithreadedCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *c
return &run.step; 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
View File
@@ -1,6 +1,7 @@
.{ .{
.name = .VulkanDriver, .name = .VulkanDriver,
.version = "0.0.1", .version = "2026.0.0",
.fingerprint = 0x52cb73649f1107de, .fingerprint = 0x52cb73649f1107de,
.minimum_zig_version = "0.16.0", .minimum_zig_version = "0.16.0",
@@ -26,14 +27,14 @@
.hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59", .hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59",
}, },
.drm = .{ .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", .hash = "drm-0.0.1-uWWar5YwAQCiTNOcqtYqt_yL19B0_Q-YLjPNkRysFSnR",
}, },
// Soft dependencies // Soft dependencies
.SPIRV_Interpreter = .{ .SPIRV_Interpreter = .{
.url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#f82e3e1629af4dc751a80a3a4b04ff1b43725c24", .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#92e86ba34fccf32a30e6f717a9cafe0b9e95bb2a",
.hash = "SPIRV_Interpreter-0.0.1-ajmpn6mrBwDl75NH4NndKIOjnn-1ooUY-0NRyWR2eW-P", .hash = "SPIRV_Interpreter-1.0.0-ajmpnwSKCQDjTAGgiw6N0rWdZiD3VdEMAE352RSpN4vl",
.lazy = true, .lazy = true,
}, },
//.SPIRV_Interpreter = .{ //.SPIRV_Interpreter = .{
@@ -41,6 +42,13 @@
// .path = "../SPIRV-Interpreter", // .path = "../SPIRV-Interpreter",
// .lazy = true, // .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 = .{ .paths = .{
+5
View File
@@ -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
View File
@@ -4,8 +4,8 @@ const base = @import("base");
const soft = @import("soft"); const soft = @import("soft");
const flint = @import("flint"); const flint = @import("flint");
const phi = @import("phi");
const Dispatchable = base.Dispatchable;
const VkError = base.VkError; const VkError = base.VkError;
const Self = @This(); const Self = @This();
@@ -14,7 +14,7 @@ pub const Interface = base.Instance;
interface: Interface, interface: Interface,
backend_instances: std.ArrayList(*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 { pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Interface {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory; 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); 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; self.backend_instances.append(allocator, soft_instance) catch return VkError.OutOfHostMemory;
const flint_instance = try flint.Instance.create(allocator, infos); 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; 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; return &self.interface;
} }
@@ -57,13 +61,13 @@ fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator, _
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
for (self.backend_instances.items) |backend| { 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 { fn appendBackendPhysicalDevices(self: *Self, allocator: std.mem.Allocator, backend: *Interface) VkError!void {
try backend.requestPhysicalDevices(allocator); 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; self.interface.physical_devices.appendSlice(allocator, backend.physical_devices.items) catch return VkError.OutOfHostMemory;
backend.physical_devices.deinit(allocator); backend.physical_devices.deinit(allocator);
+2 -2
View File
@@ -10,9 +10,9 @@ pub const ApeInstance = @import("ApeInstance.zig");
pub const Instance = ApeInstance; 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, 0,
config.ape_vulkan_version.major, config.ape_vulkan_version.major,
config.ape_vulkan_version.minor, config.ape_vulkan_version.minor,
+612
View File
@@ -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/).
+227
View File
@@ -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),
};
}
+323
View File
@@ -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,
};
}
+198
View File
@@ -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;
}
+18
View File
@@ -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,
};
+84
View File
@@ -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;
}
};
}
+228
View File
@@ -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;
}
+58
View File
@@ -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");
+235
View File
@@ -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;
}
+196
View File
@@ -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 == '_';
}
+93
View File
@@ -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 },
};
+380
View File
@@ -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;
}
+767
View File
@@ -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);
}
+83
View File
@@ -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",
};
+336
View File
@@ -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;
}
+106
View File
@@ -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,
},
};
}
};
+93
View File
@@ -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;
},
};
}
+470
View File
@@ -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,
};
}
+31
View File
@@ -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,
};
+98
View File
@@ -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;
};
}
+826
View File
@@ -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;
}
+155
View File
@@ -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;
}
+16
View File
@@ -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");
+169
View File
@@ -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
+121 -3
View File
@@ -1,20 +1,70 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const kmd = @import("kmd.zig");
const VkError = base.VkError; 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(); const Self = @This();
pub const Interface = base.BinarySemaphore; pub const Interface = base.BinarySemaphore;
interface: Interface, interface: Interface,
handle: u32,
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.SemaphoreCreateInfo) VkError!*Self { 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; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
var interface = try Interface.init(device, allocator, info); 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 = &.{ interface.vtable = &.{
.destroy = destroy, .destroy = destroy,
@@ -24,21 +74,89 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.handle = create_info.handle,
}; };
return self; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); allocator.destroy(self);
} }
pub fn signal(interface: *Interface) VkError!void { pub fn signal(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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 { pub fn wait(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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");
} }
+12 -4
View File
@@ -2,10 +2,8 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const lib = @import("lib.zig");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.Buffer; 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); errdefer allocator.destroy(self);
var interface = try Interface.init(device, allocator, info); 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 = &.{ interface.vtable = &.{
.destroy = destroy, .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 { pub fn getMemoryRequirements(interface: *Interface, requirements: *vk.MemoryRequirements) void {
_ = interface; requirements.alignment = 16;
_ = requirements; 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);
}
} }
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.BufferView; pub const Interface = base.BufferView;
+153 -45
View File
@@ -1,13 +1,26 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const kmd = @import("kmd.zig");
const VkError = base.VkError; 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(); const Self = @This();
pub const Interface = base.CommandBuffer; pub const Interface = base.CommandBuffer;
interface: Interface, 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 { 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; 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, .resolveImage = resolveImage,
.setEvent = setEvent, .setEvent = setEvent,
.setBlendConstants = setBlendConstants, .setBlendConstants = setBlendConstants,
.setDepthBias = setDepthBias,
.setDepthBounds = setDepthBounds,
.setDeviceMask = setDeviceMask, .setDeviceMask = setDeviceMask,
.setLineWidth = setLineWidth,
.setScissor = setScissor, .setScissor = setScissor,
.setStencilCompareMask = setStencilCompareMask, .setStencilCompareMask = setStencilCompareMask,
.setStencilReference = setStencilReference, .setStencilReference = setStencilReference,
@@ -61,20 +77,35 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
.setViewport = setViewport, .setViewport = setViewport,
.updateBuffer = updateBuffer, .updateBuffer = updateBuffer,
.waitEvent = waitEvent, .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; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); 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(); 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 { 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 { pub fn reset(interface: *Interface, flags: vk.CommandBufferResetFlags) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = flags; 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 { 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; _ = 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 { 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; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = bind_point; if (bind_point != .compute) return;
_ = first_set; if (first_set >= base.vulkan_max_descriptor_sets) return VkError.ValidationFailed;
_ = sets;
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; _ = dynamic_offsets;
} }
pub fn bindPipeline(interface: *Interface, bind_point: vk.PipelineBindPoint, pipeline: *base.Pipeline) VkError!void { pub fn bindPipeline(interface: *Interface, bind_point: vk.PipelineBindPoint, pipeline: *base.Pipeline) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = bind_point; if (bind_point != .compute)
_ = pipeline; 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 { 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 { 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; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = src;
_ = src_layout; _ = src_layout;
_ = dst;
_ = dst_layout; _ = dst_layout;
_ = regions; if (filter != .nearest) return VkError.FeatureNotPresent;
_ = filter;
for (regions) |region|
try blitter.blitImageRegion(self, src, dst, region);
} }
pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, rect: vk.ClearRect) VkError!void { 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 { pub fn copyBuffer(interface: *Interface, src: *base.Buffer, dst: *base.Buffer, regions: []const vk.BufferCopy) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = src;
_ = dst; for (regions) |region| {
_ = regions; 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 { pub fn copyBufferToImage(interface: *Interface, src: *base.Buffer, dst: *base.Image, dst_layout: vk.ImageLayout, regions: []const vk.BufferImageCopy) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = src;
_ = dst;
_ = dst_layout; _ = 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 { 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; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = src;
_ = src_layout; _ = src_layout;
_ = dst;
_ = dst_layout; _ = 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 { pub fn copyImageToBuffer(interface: *Interface, src: *base.Image, src_layout: vk.ImageLayout, dst: *base.Buffer, regions: []const vk.BufferImageCopy) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = src;
_ = src_layout; _ = src_layout;
_ = dst; for (regions) |region|
_ = regions; 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 { 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 { pub fn dispatch(interface: *Interface, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
_ = interface; try dispatchBase(interface, 0, 0, 0, group_count_x, group_count_y, group_count_z);
_ = 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 { 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 { pub fn executeCommands(interface: *Interface, commands: *Interface) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = commands; 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 { pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.DeviceSize, size: vk.DeviceSize, data: u32) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = buffer; const dst_range = try copy.fillRange(buffer, offset, size);
_ = offset;
_ = size; var filled: vk.DeviceSize = 0;
_ = data; 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 { 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; _ = 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 { pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void {
_ = interface; _ = interface;
_ = face_mask; _ = face_mask;
@@ -390,3 +492,9 @@ pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.Pipeli
_ = buffer_barriers; _ = buffer_barriers;
_ = image_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
View File
@@ -2,9 +2,7 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const NonDispatchable = base.NonDispatchable;
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const FlintCommandBuffer = @import("FlintCommandBuffer.zig"); const FlintCommandBuffer = @import("FlintCommandBuffer.zig");
+21 -7
View File
@@ -5,12 +5,13 @@ const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const VulkanAllocator = base.VulkanAllocator; const VulkanAllocator = base.VulkanAllocator;
const Device = base.Device; const FlintDescriptorSet = @import("FlintDescriptorSet.zig");
const Self = @This(); const Self = @This();
pub const Interface = base.DescriptorPool; pub const Interface = base.DescriptorPool;
interface: Interface, interface: Interface,
sets: std.ArrayList(*FlintDescriptorSet),
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.DescriptorPoolCreateInfo) VkError!*Self { 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; 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.* = .{ self.* = .{
.interface = interface, .interface = interface,
.sets = std.ArrayList(*FlintDescriptorSet).initCapacity(allocator, info.max_sets) catch return VkError.OutOfHostMemory,
}; };
return self; return self;
} }
pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetLayout) VkError!*base.DescriptorSet { pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetLayout) VkError!*base.DescriptorSet {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self; if (self.sets.items.len == self.sets.capacity) return VkError.OutOfPoolMemory;
_ = layout;
return VkError.Unknown; 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 { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); allocator.destroy(self);
} }
pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkError!void { pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self; const flint_set: *FlintDescriptorSet = @alignCast(@fieldParentPtr("interface", set));
_ = 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 { pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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();
} }
+132 -5
View File
@@ -3,11 +3,25 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const NonDispatchable = base.NonDispatchable;
const Self = @This(); const Self = @This();
pub const Interface = base.DescriptorSet; 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, interface: Interface,
heap: []u8,
descriptors: []Descriptor,
pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.DescriptorSetLayout) VkError!*Self { pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.DescriptorSetLayout) VkError!*Self {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory; 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, .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.* = .{ self.* = .{
.interface = interface, .interface = interface,
.heap = heap,
.descriptors = descriptors,
}; };
return self; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
allocator.free(self.heap);
allocator.destroy(self); allocator.destroy(self);
} }
pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void { pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self; const src: *const Self = @alignCast(@fieldParentPtr("interface", src_interface));
_ = src_interface;
_ = data; 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 { pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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];
} }
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.DescriptorSetLayout; pub const Interface = base.DescriptorSetLayout;
+8
View File
@@ -3,6 +3,8 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const FlintQueue = @import("FlintQueue.zig"); const FlintQueue = @import("FlintQueue.zig");
const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
const Kmd = @import("kmd.zig");
pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig"); pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
pub const FlintBuffer = @import("FlintBuffer.zig"); pub const FlintBuffer = @import("FlintBuffer.zig");
@@ -32,12 +34,16 @@ const Self = @This();
pub const Interface = base.Device; pub const Interface = base.Device;
interface: Interface, 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 { 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; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
var interface = try Interface.init(allocator, instance, physical_device, info); 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 = &.{ interface.vtable = &.{
.createQueue = FlintQueue.create, .createQueue = FlintQueue.create,
@@ -73,6 +79,7 @@ pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, a
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.kmd = kmd_device,
}; };
try self.interface.createQueues(allocator, info); 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 { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
self.kmd.close(interface.io());
allocator.destroy(self); allocator.destroy(self);
} }
+28 -12
View File
@@ -1,9 +1,9 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const lib = @import("lib.zig");
const FlintDevice = @import("FlintDevice.zig"); const FlintDevice = @import("FlintDevice.zig");
const kmd = @import("kmd.zig");
const VkError = base.VkError; const VkError = base.VkError;
@@ -11,12 +11,19 @@ const Self = @This();
pub const Interface = base.DeviceMemory; pub const Interface = base.DeviceMemory;
interface: Interface, interface: Interface,
allocation: kmd.Memory,
pub fn create(device: *FlintDevice, allocator: std.mem.Allocator, size: vk.DeviceSize, memory_type_index: u32) VkError!*Self { 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; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); 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 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 = &.{ interface.vtable = &.{
.destroy = destroy, .destroy = destroy,
@@ -28,33 +35,42 @@ pub fn create(device: *FlintDevice, allocator: std.mem.Allocator, size: vk.Devic
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.allocation = allocation,
}; };
return self; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); allocator.destroy(self);
} }
pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void { pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = offset; const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
_ = size; try self.allocation.flushRange(&device.kmd, interface.owner.io(), offset, size);
} }
pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void { pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = offset; const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
_ = size; try self.allocation.invalidateRange(&device.kmd, interface.owner.io(), offset, size);
} }
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 { pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self; if (interface.is_mapped) return VkError.MemoryMapFailed;
_ = offset;
_ = size; const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
return VkError.Unknown; 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;
}
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.Event; pub const Interface = base.Event;
+131 -6
View File
@@ -1,20 +1,73 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const kmd = @import("kmd.zig");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device; 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(); const Self = @This();
pub const Interface = base.Fence; pub const Interface = base.Fence;
interface: Interface, interface: Interface,
handle: u32,
pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Self { pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.FenceCreateInfo) VkError!*Self {
const self = allocator.create(Self) catch return VkError.OutOfHostMemory; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
var interface = try Interface.init(device, allocator, info); 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 = &.{ interface.vtable = &.{
.destroy = destroy, .destroy = destroy,
@@ -26,32 +79,104 @@ pub fn create(device: *Device, allocator: std.mem.Allocator, info: *const vk.Fen
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.handle = create_info.handle,
}; };
return self; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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); allocator.destroy(self);
} }
pub fn getStatus(interface: *Interface) VkError!void { pub fn getStatus(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); wait(interface, 0) catch |err| switch (err) {
_ = self; VkError.Timeout => return VkError.NotReady,
else => return err,
};
} }
pub fn reset(interface: *Interface) VkError!void { pub fn reset(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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 { pub fn signal(interface: *Interface) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); 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 { pub fn wait(interface: *Interface, timeout: u64) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self; const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
_ = timeout; 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
View File
@@ -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 std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const lib = @import("lib.zig"); const lib = @import("lib.zig");
const FlintDeviceMemory = @import("FlintDeviceMemory.zig");
const VkError = base.VkError; const VkError = base.VkError;
const Self = @This(); const Self = @This();
pub const Interface = base.Image; pub const Interface = base.Image;
pub const F32x4 = @Vector(4, f32);
pub const U32x4 = @Vector(4, u32);
interface: Interface, interface: Interface,
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ImageCreateInfo) VkError!*Self { 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); errdefer allocator.destroy(self);
var interface = try Interface.init(device, allocator, info); 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 = &.{ interface.vtable = &.{
.destroy = destroy, .destroy = destroy,
.getMemoryRequirements = getMemoryRequirements, .getMemoryRequirements = getMemoryRequirements,
@@ -28,9 +36,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
.copyToMemory = copyToMemory, .copyToMemory = copyToMemory,
}; };
self.* = .{ self.* = .{ .interface = interface };
.interface = interface,
};
return self; 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 { 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 { pub fn copyToMemory(interface: *const Interface, dst: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
_ = interface; const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
_ = subresource; const memory_interface = interface.memory orelse return VkError.InvalidDeviceMemoryDrv;
@memset(memory, 0); 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 { pub fn getTotalSizeForAspect(interface: *const Interface, aspect_mask: vk.ImageAspectFlags) VkError!usize {
_ = aspect_mask; const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
return interface.extent.width * interface.extent.height * interface.extent.depth * base.format.texelSize(interface.format); 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 { 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 .{ return .{
.offset = 0, .offset = try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer),
.size = try getTotalSizeForAspect(interface, base.format.toAspect(interface.format)), .size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level),
.row_pitch = getRowPitchMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), .row_pitch = getRowPitchMemSizeForMipLevel(interface, subresource.aspect_mask, subresource.mip_level),
.array_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), .array_pitch = self.getLayerSize(subresource.aspect_mask),
.depth_pitch = getSliceMemSizeForMipLevel(interface, base.format.toAspect(interface.format), 0), .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 { pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
_ = aspect_mask; const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
_ = mip_level; const extent = self.getMipLevelExtent(mip_level);
return interface.extent.width * interface.extent.height * base.format.texelSize(interface.format); 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 { pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
_ = aspect_mask; const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
_ = mip_level; const extent = self.getMipLevelExtent(mip_level);
return interface.extent.width * base.format.texelSize(interface.format); 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;
} }
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.ImageView; pub const Interface = base.ImageView;
+17 -4
View File
@@ -26,7 +26,7 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
return props; return props;
} }
pub const EXTENSIONS = [_]vk.ExtensionProperties{ pub const extensions = [_]vk.ExtensionProperties{
castExtension(vk.extensions.khr_device_group_creation), castExtension(vk.extensions.khr_device_group_creation),
castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_get_physical_device_properties_2),
castExtension(vk.extensions.khr_surface), 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 if (drm_device.node_type != .render or
std.meta.activeTag(drm_device.device_info) != .pci 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; continue;
const physical_device = try FlintPhysicalDevice.create(allocator, interface, &drm_device); const version = device.getVersion(io_var, allocator) catch continue;
errdefer physical_device.interface.release(allocator) catch {}; 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); const dispatchable = try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface);
errdefer dispatchable.destroy(allocator); errdefer dispatchable.destroy(allocator);
+59 -20
View File
@@ -9,7 +9,6 @@ const pci_ids = @import("pci_ids.zig").map;
const FlintDevice = @import("FlintDevice.zig"); const FlintDevice = @import("FlintDevice.zig");
const VkError = base.VkError; const VkError = base.VkError;
const VulkanAllocator = base.VulkanAllocator;
const SurfaceKHR = base.SurfaceKHR; const SurfaceKHR = base.SurfaceKHR;
const Self = @This(); const Self = @This();
@@ -24,14 +23,15 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties {
return props; return props;
} }
pub const EXTENSIONS = [_]vk.ExtensionProperties{ pub const extensions = [_]vk.ExtensionProperties{
castExtension(vk.extensions.khr_device_group),
castExtension(vk.extensions.khr_swapchain), castExtension(vk.extensions.khr_swapchain),
}; };
interface: Interface, 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; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
@@ -53,9 +53,9 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
.getSurfaceSupportKHR = getSurfaceSupportKHR, .getSurfaceSupportKHR = getSurfaceSupportKHR,
}; };
interface.props.api_version = @bitCast(lib.VULKAN_VERSION); interface.props.api_version = @bitCast(lib.vulkan_version);
interface.props.vendor_id = lib.INTEL_PCI_VENDOR_ID; interface.props.vendor_id = lib.intel_pci_vendor_id;
interface.props.driver_version = @bitCast(base.DRIVER_VERSION); interface.props.driver_version = @bitCast(base.driver_version);
interface.props.device_id = drm_device.device_info.pci.device_id; interface.props.device_id = drm_device.device_info.pci.device_id;
interface.props.device_type = .integrated_gpu; interface.props.device_type = .integrated_gpu;
@@ -78,7 +78,7 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
break; break;
} }
interface.props.pipeline_cache_uuid = undefined; interface.props.pipeline_cache_uuid = @splat(0);
interface.props.limits = .{ interface.props.limits = .{
.max_image_dimension_1d = 4096, .max_image_dimension_1d = 4096,
.max_image_dimension_2d = 4096, .max_image_dimension_2d = 4096,
@@ -223,11 +223,19 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance, drm_device
self.* = .{ self.* = .{
.interface = interface, .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; 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 { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
allocator.destroy(self); allocator.destroy(self);
@@ -248,10 +256,10 @@ pub fn enumerateExtensionProperties(_: *const Interface, layer_name: ?[]const u8
return VkError.LayerNotPresent; return VkError.LayerNotPresent;
} }
const available = EXTENSIONS.len; const available = extensions.len;
if (p_properties) |properties| { if (p_properties) |properties| {
const write_count = @min(count.*, available); 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; prop.* = ext;
} }
count.* = @intCast(write_count); 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.sampled_image_bit = true;
properties.optimal_tiling_features.transfer_dst_bit = true; properties.optimal_tiling_features.transfer_dst_bit = true;
properties.optimal_tiling_features.transfer_src_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 // 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, .g10x6_b10x6r10x6_2plane_420_unorm_3pack16,
=> { => {
properties.optimal_tiling_features.sampled_image_bit = true; 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.sampled_image_ycbcr_conversion_linear_filter_bit = true;
properties.optimal_tiling_features.transfer_src_bit = true; properties.optimal_tiling_features.transfer_src_bit = true;
properties.optimal_tiling_features.transfer_dst_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; 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; return properties;
} }
@@ -687,20 +698,48 @@ pub fn getImageFormatProperties(
image_type: vk.ImageType, image_type: vk.ImageType,
tiling: vk.ImageTiling, tiling: vk.ImageTiling,
usage: vk.ImageUsageFlags, usage: vk.ImageUsageFlags,
_: vk.ImageCreateFlags, flags: vk.ImageCreateFlags,
) VkError!vk.ImageFormatProperties { ) VkError!vk.ImageFormatProperties {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
if (!try self.isFormatSupported(format, image_type, tiling, usage)) if (!try self.isFormatSupported(format, image_type, tiling, usage))
return VkError.FormatNotSupported; return VkError.FormatNotSupported;
const properties: vk.ImageFormatProperties = .{ var properties: vk.ImageFormatProperties = .{
.max_extent = .{ .width = 0, .height = 0, .depth = 1 }, .max_extent = .{ .width = 1, .height = 1, .depth = 1 },
.max_mip_levels = 1, .max_mip_levels = 1,
.max_array_layers = 1, .max_array_layers = interface.props.limits.max_image_array_layers,
.sample_counts = .{ .@"1_bit" = true }, .sample_counts = .{ .@"1_bit" = true },
.max_resource_size = std.math.maxInt(u32), .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; return properties;
} }
@@ -827,10 +866,10 @@ fn cpuid(leaf_id: u32, subleaf_id: u32) CpuidRegs {
} }
} }
var eax: u32 = undefined; var eax: u32 = 0;
var ebx: u32 = undefined; var ebx: u32 = 0;
var ecx: u32 = undefined; var ecx: u32 = 0;
var edx: u32 = undefined; var edx: u32 = 0;
asm volatile ("cpuid" asm volatile ("cpuid"
: [_] "={eax}" (eax), : [_] "={eax}" (eax),
+12 -2
View File
@@ -8,6 +8,7 @@ const Self = @This();
pub const Interface = base.Pipeline; pub const Interface = base.Pipeline;
interface: Interface, 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 { 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; 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); var interface = try Interface.initCompute(device, allocator, cache, info);
interface.vtable = &.{ .destroy = destroy }; 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; 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); var interface = try Interface.initGraphics(device, allocator, cache, info);
interface.vtable = &.{ .destroy = destroy }; 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; return self;
} }
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.PipelineLayout; pub const Interface = base.PipelineLayout;
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.QueryPool; pub const Interface = base.QueryPool;
+90 -12
View File
@@ -2,7 +2,11 @@ const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
const FlintCommandBuffer = @import("FlintCommandBuffer.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; const VkError = base.VkError;
@@ -10,24 +14,35 @@ const Self = @This();
pub const Interface = base.Queue; pub const Interface = base.Queue;
interface: Interface, interface: Interface,
completion: *FlintFence,
pub fn create(allocator: std.mem.Allocator, device: *base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*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; const self = allocator.create(Self) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
var interface = try Interface.init(allocator, device, index, family_index, flags); 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 = &.{ interface.dispatch_table = &.{
.bindSparse = bindSparse, .bindSparse = bindSparse,
.submit = submit, .submit = submit,
.waitIdle = waitIdle, .waitIdle = waitIdle,
}; };
self.* = .{ .interface = interface }; self.* = .{
.interface = interface,
.completion = completion,
};
return &self.interface; return &self.interface;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
self.completion.interface.destroy(allocator);
allocator.destroy(self); 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 { pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, fence: ?*base.Fence) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
for (infos) |info| { const device: *FlintDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
for (info.wait_semaphores.items) |semaphore| { const allocator = interface.host_allocator.allocator();
try semaphore.wait();
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)); 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| { if (first_request) {
try semaphore.signal(); 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 { pub fn waitIdle(interface: *Interface) VkError!void {
_ = interface; const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
try self.completion.interface.wait(std.math.maxInt(u64));
} }
-1
View File
@@ -3,7 +3,6 @@ const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const VkError = base.VkError; const VkError = base.VkError;
const Device = base.Device;
const Self = @This(); const Self = @This();
pub const Interface = base.RenderPass; pub const Interface = base.RenderPass;
+6
View File
@@ -8,6 +8,7 @@ const Self = @This();
pub const Interface = base.ShaderModule; pub const Interface = base.ShaderModule;
interface: Interface, interface: Interface,
code: []u32,
ref_count: std.atomic.Value(usize), ref_count: std.atomic.Value(usize),
pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const vk.ShaderModuleCreateInfo) VkError!*Self { 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); var interface = try Interface.init(device, allocator, info);
interface.vtable = &.{ .destroy = destroy }; 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.* = .{ self.* = .{
.interface = interface, .interface = interface,
.code = code,
.ref_count = std.atomic.Value(usize).init(1), .ref_count = std.atomic.Value(usize).init(1),
}; };
return self; 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 { pub fn drop(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(self.code);
allocator.destroy(self); allocator.destroy(self);
} }
+42
View File
@@ -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,
};
}
+171
View File
@@ -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);
}
+246
View File
@@ -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);
}
+80
View File
@@ -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,
};
+247
View File
@@ -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;
}
};
+168
View File
@@ -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
View File
@@ -9,6 +9,7 @@ pub const FlintInstance = @import("FlintInstance.zig");
pub const FlintDevice = @import("FlintDevice.zig"); pub const FlintDevice = @import("FlintDevice.zig");
pub const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig"); pub const FlintPhysicalDevice = @import("FlintPhysicalDevice.zig");
pub const FlintQueue = @import("FlintQueue.zig"); pub const FlintQueue = @import("FlintQueue.zig");
pub const kmd = @import("kmd.zig");
pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig"); pub const FlintBinarySemaphore = @import("FlintBinarySemaphore.zig");
pub const FlintBuffer = @import("FlintBuffer.zig"); pub const FlintBuffer = @import("FlintBuffer.zig");
@@ -34,23 +35,26 @@ pub const FlintShaderModule = @import("FlintShaderModule.zig");
pub const Instance = FlintInstance; 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, 0,
config.flint_vulkan_version.major, config.flint_vulkan_version.major,
config.flint_vulkan_version.minor, config.flint_vulkan_version.minor,
config.flint_vulkan_version.patch, 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 { pub const KmdType = enum {
Invalid, invalid,
I915, i915,
Xe, xe,
}; };
pub const std_options = base.std_options; pub const std_options = base.std_options;
@@ -85,5 +89,6 @@ test {
std.testing.refAllDecls(FlintRenderPass); std.testing.refAllDecls(FlintRenderPass);
std.testing.refAllDecls(FlintSampler); std.testing.refAllDecls(FlintSampler);
std.testing.refAllDecls(FlintShaderModule); std.testing.refAllDecls(FlintShaderModule);
std.testing.refAllDecls(kmd);
std.testing.refAllDecls(base); std.testing.refAllDecls(base);
} }
-2
View File
@@ -1,5 +1,3 @@
const std = @import("std");
const PciInfo = struct { const PciInfo = struct {
id: u16, id: u16,
name: []const u8, name: []const u8,
+47
View File
@@ -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;
}
};
+43
View File
@@ -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;
}
+38
View File
@@ -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;
}
+31
View File
@@ -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);
}
+516
View File
@@ -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);
}
+46
View File
@@ -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;
}
+53
View File
@@ -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;
}
+46
View File
@@ -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;
}
+31
View File
@@ -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);
}
+287
View File
@@ -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;
}
+127
View File
@@ -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 {}
+55
View File
@@ -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;
}
+57
View File
@@ -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;
}
+26
View File
@@ -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);
}
+77
View File
@@ -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);
}
+31
View File
@@ -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);
}
+140
View File
@@ -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,
};
}
+901
View File
@@ -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);
}
+37
View File
@@ -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);
}
+26
View File
@@ -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);
}
+31
View File
@@ -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);
}
+32
View File
@@ -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);
}
+88
View File
@@ -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;
}
+39
View File
@@ -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,
};
}
+26
View File
@@ -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);
}
+44
View File
@@ -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);
}
}
+134
View File
@@ -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;
}
}
+87
View File
@@ -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);
}
+66
View File
@@ -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;
}
}
+9
View File
@@ -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
+96
View File
@@ -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));
}
+16
View File
@@ -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
+199
View File
@@ -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;
}
+20
View File
@@ -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
+95
View File
@@ -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