Compare commits

...

77 Commits

Author SHA1 Message Date
kbz_8 42554a5cc1 fixing ffi
Build / build (push) Successful in 46s
Test / build (push) Successful in 1m16s
2026-06-14 23:47:34 +02:00
kbz_8 52ded6b490 adding more image queries
Test / build (push) Failing after 26s
Build / build (push) Failing after 26s
2026-06-14 22:53:39 +02:00
kbz_8 5c1eb25b48 adding runtime init from other
Build / build (push) Successful in 1m23s
Test / build (push) Successful in 9m39s
2026-06-13 22:43:13 +02:00
kbz_8 c7320325fd adding lots of unit tests, improving image sampling, adding image sampling deref
Build / build (push) Successful in 1m24s
Test / build (push) Successful in 9m42s
2026-06-12 23:05:16 +02:00
kbz_8 089a33981c improving derivatives support, adding matrix stride management, adding matrix times scalar
Build / build (push) Successful in 41s
Test / build (push) Successful in 1m12s
2026-06-11 22:50:57 +02:00
kbz_8 75099f7e1d fixing sinh and cosh
Build / build (push) Successful in 1m8s
Test / build (push) Successful in 10m31s
2026-06-11 12:33:20 +02:00
kbz_8 c868a4481b improving GLSL_std450 support
Build / build (push) Successful in 38s
Test / build (push) Successful in 1m5s
2026-06-10 19:15:39 +02:00
kbz_8 11be3ea41d fixing weird issues with release fast
Build / build (push) Successful in 52s
Test / build (push) Successful in 1m20s
2026-06-08 18:14:00 +02:00
kbz_8 9270d83464 improving spec constant support
Build / build (push) Successful in 43s
Test / build (push) Failing after 20s
2026-06-08 14:53:02 +02:00
kbz_8 291290f1b8 adding derivatives support
Build / build (push) Successful in 47s
Test / build (push) Successful in 1m14s
2026-06-07 23:27:49 +02:00
kbz_8 d66d1dd021 adding documentattion
Test / build (push) Successful in 30s
Build / build (push) Successful in 38s
2026-06-05 23:00:51 +02:00
kbz_8 7c6da62e3c fixing ffi
Test / build (push) Successful in 26s
Build / build (push) Successful in 37s
2026-06-05 22:11:37 +02:00
kbz_8 fda0e2143d adding LOD support and int image sampling
Build / build (push) Failing after 24s
Test / build (push) Successful in 1m0s
2026-06-05 22:04:27 +02:00
kbz_8 fb6e5beeff fixing tests
Build / build (push) Successful in 31s
Test / build (push) Successful in 1m3s
2026-06-05 17:54:23 +02:00
kbz_8 ad87703ea8 adding util to runtime api
Test / build (push) Failing after 31s
Build / build (push) Successful in 48s
2026-06-05 17:48:21 +02:00
kbz_8 ee5a400010 adding component locations
Test / build (push) Failing after 22s
Build / build (push) Successful in 37s
2026-06-05 01:21:43 +02:00
kbz_8 8677e8a683 yes
Build / build (push) Successful in 32s
Test / build (push) Successful in 1m3s
2026-06-04 02:44:40 +02:00
kbz_8 431a634290 fixing CI
Test / build (push) Successful in 24s
Build / build (push) Successful in 31s
2026-06-03 19:27:21 +02:00
kbz_8 df57df44cb fixing descriptor sets managment
Test / build (push) Successful in 58s
Build / build (push) Successful in 29s
2026-06-03 19:14:44 +02:00
kbz_8 9c355fe126 yes
Test / build (push) Successful in 58s
Build / build (push) Successful in 58s
2026-06-01 20:36:24 +02:00
kbz_8 0eefbe63e3 adding opcodes, reworking atomic engine
Build / build (push) Successful in 1m46s
Test / build (push) Successful in 9m39s
2026-06-01 19:32:46 +02:00
kbz_8 e993162d4e adding clamping ocpodes
Build / build (push) Successful in 58s
Test / build (push) Successful in 1m20s
2026-05-29 00:44:49 +02:00
kbz_8 1ffa20d07c adding OpImage
Build / build (push) Successful in 1m1s
Test / build (push) Successful in 1m3s
2026-05-24 22:03:29 +02:00
kbz_8 a8372ce736 adding image fetch
Build / build (push) Successful in 58s
Test / build (push) Successful in 1m7s
2026-05-22 10:42:56 +02:00
kbz_8 bc84a9f553 adding cleaning engines, adding image engine
Test / build (push) Successful in 1m6s
Build / build (push) Successful in 1m14s
2026-05-20 18:04:53 +02:00
kbz_8 45453c1b9e fixing opCompositeConstruct
Test / build (push) Successful in 2m31s
Build / build (push) Successful in 59s
2026-05-18 23:52:05 +02:00
kbz_8 b82d37e7b6 improving value read/write, improving cond engine
Build / build (push) Successful in 1m35s
Test / build (push) Successful in 9m14s
2026-05-18 18:18:39 +02:00
kbz_8 a765246ee9 improving image api
Test / build (push) Successful in 1m19s
Build / build (push) Successful in 1m22s
2026-05-17 01:57:36 +02:00
kbz_8 236c6496ff fixing external structs
Test / build (push) Successful in 1m8s
Build / build (push) Successful in 1m15s
2026-05-15 00:55:34 +02:00
kbz_8 dc80a6a348 updating nzsl
Build / build (push) Successful in 1m17s
Test / build (push) Successful in 7m41s
2026-05-14 14:24:18 +02:00
kbz_8 c0825d5315 fixing example
Build / build (push) Successful in 1m12s
Test / build (push) Successful in 1m10s
2026-05-12 16:45:19 +02:00
kbz_8 3139f3cfdd drafting push constants
Test / build (push) Successful in 1m11s
Build / build (push) Successful in 1m13s
2026-05-12 03:01:04 +02:00
kbz_8 ca33cfe3e9 adding some matrix operations
Build / build (push) Successful in 1m50s
Test / build (push) Successful in 9m32s
2026-05-11 21:36:04 +02:00
kbz_8 769009ad5e adding base matrix management
Build / build (push) Successful in 1m33s
Test / build (push) Successful in 10m31s
2026-05-11 01:48:13 +02:00
kbz_8 9d20363ae8 fixing vector times scalar
Build / build (push) Successful in 1m28s
Test / build (push) Successful in 8m38s
2026-05-10 17:56:10 +02:00
kbz_8 4e852b5c07 removing weird free
Build / build (push) Successful in 2m38s
Test / build (push) Successful in 8m52s
2026-05-08 18:52:26 +02:00
kbz_8 5faf8fd305 adding IAddCarry and ISubBorrow
Build / build (push) Successful in 1m24s
Test / build (push) Successful in 7m52s
2026-04-30 00:20:33 +02:00
kbz_8 ce17209004 fixing runtime array access chain
Build / build (push) Successful in 1m22s
Test / build (push) Successful in 8m28s
2026-04-29 23:52:07 +02:00
kbz_8 11a59d8d7f yes 2026-04-29 23:44:55 +02:00
kbz_8 046b1c8f9e adding storage image read and writes
Build / build (push) Successful in 1m42s
Test / build (push) Successful in 8m27s
2026-04-29 01:19:48 +02:00
kbz_8 cc041c9677 adding api functions
Build / build (push) Successful in 1m37s
Test / build (push) Successful in 8m54s
2026-04-27 23:51:08 +02:00
kbz_8 9cdb683f3f adding decoration members propagation
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 7m45s
2026-04-27 15:42:29 +02:00
kbz_8 5b7380eea0 yes
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 8m22s
2026-04-26 03:58:59 +02:00
kbz_8 f617fe417c adding read builtin
Build / build (push) Successful in 1m45s
Test / build (push) Has been cancelled
2026-04-26 03:54:05 +02:00
kbz_8 147126c06f adding result get from location
Build / build (push) Successful in 1m46s
Test / build (push) Successful in 7m49s
2026-04-26 02:39:21 +02:00
kbz_8 ab34c3b49a Update README.md
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 7m50s
2026-04-25 19:42:28 +02:00
kbz_8 ef69470183 adding C bindings
Build / build (push) Successful in 1m52s
Test / build (push) Successful in 7m57s
2026-04-25 19:30:09 +02:00
kbz_8 664ea9b92b fixing pretty
Build / build (push) Successful in 1m25s
Test / build (push) Successful in 8m10s
2026-04-18 14:10:58 +02:00
kbz_8 c61514feea yes
Build / build (push) Successful in 1m30s
Test / build (push) Successful in 7m34s
2026-04-16 02:12:13 +02:00
kbz_8 4bd688cf07 updating to Zig 0.16
Build / build (push) Failing after 32s
Test / build (push) Successful in 7m50s
2026-04-16 01:50:42 +02:00
kbz_8 9f586ae9c0 yes
Build / build (push) Successful in 1m33s
Test / build (push) Successful in 7m17s
2026-04-12 22:34:05 +02:00
kbz_8 b051c71f02 adding base image
Build / build (push) Successful in 2m5s
Test / build (push) Successful in 10m33s
2026-04-06 22:49:23 +02:00
kbz_8 85e0fe4362 adding OpPhi
Build / build (push) Successful in 5m6s
Test / build (push) Successful in 10m36s
2026-04-04 23:43:13 +02:00
kbz_8 a83a761afa yes
Build / build (push) Successful in 5m39s
Test / build (push) Successful in 11m34s
2026-04-04 03:58:39 +02:00
kbz_8 15eb36ea4d adding base atomic management
Build / build (push) Successful in 3m29s
Test / build (push) Successful in 11m30s
2026-04-02 03:33:18 +02:00
kbz_8 1c26e16eb7 adding vector shuffle
Build / build (push) Successful in 2m12s
Test / build (push) Successful in 9m42s
2026-03-31 03:28:09 +02:00
kbz_8 8072d671af adding spec constant management
Build / build (push) Successful in 2m11s
Test / build (push) Successful in 8m24s
2026-03-30 04:45:52 +02:00
kbz_8 6c8b364c7d adding msb, lsb and spec constants
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 8m40s
2026-03-30 01:00:00 +02:00
kbz_8 fbaf85a849 skip CI
Build / build (push) Successful in 5m9s
Test / build (push) Successful in 10m43s
2026-03-29 00:48:33 +01:00
kbz_8 3c7054fae0 adding stride to regular arrays
Build / build (push) Successful in 6m0s
Test / build (push) Successful in 11m19s
2026-03-26 01:10:29 +01:00
kbz_8 a503f54e43 adding some opcodes
Build / build (push) Successful in 1m59s
Test / build (push) Successful in 8m50s
2026-03-24 04:34:04 +01:00
kbz_8 d1bf1c23f2 fixing bit insert and extract
Build / build (push) Successful in 4m58s
Test / build (push) Successful in 11m48s
2026-03-23 04:59:38 +01:00
kbz_8 2d0d3b40fd fixes
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 8m21s
2026-03-22 04:47:59 +01:00
kbz_8 cb0cdaab36 reworking opBitcast, fixing some glsl functions
Build / build (push) Successful in 5m3s
Test / build (push) Successful in 10m46s
2026-03-21 22:30:39 +01:00
kbz_8 569d7fda01 fixing pointer syncing
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 9m35s
2026-03-21 05:18:27 +01:00
kbz_8 c18deb432f yes
Build / build (push) Successful in 2m1s
Test / build (push) Successful in 8m28s
2026-03-20 16:39:06 +01:00
kbz_8 fe47277468 adding array stride to runtime arrays
Build / build (push) Successful in 5m9s
Test / build (push) Successful in 10m34s
2026-03-20 03:14:36 +01:00
kbz_8 e8a08d7885 fixing opSelect and opConstantComposite
Build / build (push) Successful in 4m53s
Test / build (push) Successful in 10m46s
2026-03-16 03:52:59 +01:00
kbz_8 9e72a6d2bc small refactor of values
Build / build (push) Successful in 1m54s
Test / build (push) Successful in 7m56s
2026-03-14 03:59:11 +01:00
kbz_8 7074345540 small improvments of
Build / build (push) Successful in 1m52s
Test / build (push) Successful in 8m23s
2026-03-12 14:49:52 +01:00
kbz_8 72faa35357 fixing example
Build / build (push) Successful in 3m15s
Test / build (push) Successful in 9m50s
2026-03-12 01:06:20 +01:00
kbz_8 7dd86b021d fixing tests failure
Build / build (push) Successful in 2m17s
Test / build (push) Successful in 9m19s
2026-03-11 12:37:08 +01:00
kbz_8 5d704dcc5f adding some opcodes
Build / build (push) Successful in 1m54s
Test / build (push) Failing after 9m37s
2026-03-11 01:28:53 +01:00
kbz_8 1a48af468d adding descriptor index to descriptor write
Build / build (push) Successful in 2m18s
Test / build (push) Successful in 8m55s
2026-03-10 17:46:38 +01:00
kbz_8 ad013d23fc ugly implementation of new runtime arrays management
Build / build (push) Successful in 56s
Test / build (push) Successful in 4m54s
2026-03-08 02:25:25 +01:00
kbz_8 16eb184808 adding SSBO unit test
Build / build (push) Successful in 1m49s
Test / build (push) Successful in 4m36s
2026-03-07 15:20:02 +01:00
kbz_8 7bf671d974 improving tests
Build / build (push) Successful in 2m31s
Test / build (push) Successful in 6m48s
2026-03-06 22:34:47 +01:00
39 changed files with 11371 additions and 1781 deletions
+18 -13
View File
@@ -6,10 +6,6 @@ on:
pull_request:
branches: [ "master" ]
permissions:
contents: read
deployments: write
jobs:
build:
runs-on: ubuntu-latest
@@ -17,21 +13,30 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: actions/setup-node@v6
with:
node-version: 24
fetch-depth: 0
- uses: https://codeberg.org/mlugg/setup-zig@v2
- name: Building
run: zig build -Dno-example=true
- name: Building FFI
run: zig build ffi-c -Dno-example=true
- name: Generating docs
run: zig build docs -Dno-example=true
- name: Publish to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
- name: Deploying docs
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
uses: milanmk/actions-file-deployer@master
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy zig-out/docs --project-name=spirv-interpreter-docs
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
remote-protocol: sftp
remote-host: ${{ secrets.SFTP_HOST_DOCS }}
remote-user: ${{ secrets.SFTP_USER_DOCS }}
remote-password: ${{ secrets.SFTP_PASSWORD_DOCS }}
remote-port: 6969
local-path: "./zig-out/docs"
remote-path: "/www"
sync: full
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: https://codeberg.org/mlugg/setup-zig@v2
- name: Test
run: zig build test -Dno-example=true
run: zig build test -Dno-example=true --release=fast
+1
View File
@@ -1,5 +1,6 @@
.zig-cache/
zig-out/
zig-pkg/
.gdb_history
*.o
vgcore*
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 kbz_8
Copyright (c) 2026 kbz_8
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+61 -2
View File
@@ -1,6 +1,6 @@
# SPIR-V Interpreter <a href="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions?workflows=build.yml"><img src="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions/workflows/build.yml/badge.svg"></a> <a href="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions?workflows=test.yml"><img src="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions/workflows/test.yml/badge.svg"></a>
A small footprint SPIR-V interpreter to execute SPIR-V shaders on the CPU. It is designed to be used with multiple runtimes concurrently.
A small footprint SPIR-V interpreter to execute SPIR-V shaders on the CPU. It is designed to be used with multiple runtimes concurrently and can be SIMD accelerated.
```zig
const std = @import("std");
@@ -23,9 +23,68 @@ pub fn main() !void {
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color"));
try rt.readOutput(std.mem.asBytes(output[0..output.len]), try rt.getResultByName("color"));
std.log.info("Output: Vec4{any}", .{output});
}
std.log.info("Successfully executed", .{});
}
```
## C bindings
### Build
To build the FFI bindings just
```
zig build ffi-c --release=[fast, small, safe]
```
You can also build a shared lib using
```
zig build ffi-c --release=[fast, small, safe] -Dffi-build-static=false
```
You'll find the library in `./zig-out/lib/` and the header file in `./zig-out/include/` or in `./ffi/`.
### Example
```c
#include <stdio.h>
#include <SpirvInterpreter.h>
static const unsigned char shader_source[] = {
/* Shader bytecode */
}
int main(void)
{
SpvModule module;
SpvModuleOptions options;
options.use_simd_vectors_specializations = 1;
if(SpvInitModule(&module, (SpvWord*)shader_source, sizeof(shader_source) / 4, options) != SPV_RESULT_SUCCESS)
return -1;
SpvRuntime runtime;
if(SpvInitRuntime(&runtime, module) != SPV_RESULT_SUCCESS)
return -1;
SpvWord main_entry_index;
SpvGetEntryPointByName(runtime, "main", &main_entry_index);
SpvCallEntryPoint(runtime, main_entry_index);
float output[4];
SpvWord output_result;
SpvGetResultByName(runtime, "color", &output_result);
SpvReadOutput(runtime, (SpvByte*)output, sizeof(output), output_result);
printf("Output: Vec4[%f, %f, %f, %f]\n", output[0], output[1], output[2], output[3]);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return 0;
}
```
## Documentation
A full documentation on how to use this interpreter can be found in [Usage.md](https://git.kbz8.me/kbz_8/SPIRV-Interpreter/src/branch/master/Usage.md).
+651
View File
@@ -0,0 +1,651 @@
# SPIR-V Interpreter Usage
## Concepts
A typical invocation has this lifecycle:
1. Load SPIR-V bytecode.
2. Create a `Module` from the bytecode.
3. Create one or more `Runtime` instances from the module.
4. Write inputs, built-ins, descriptors, push constants, or specialization constants.
5. Execute an entry point.
6. Read outputs or built-ins.
7. Destroy runtimes, then destroy the module.
A `Module` represents parsed SPIR-V bytecode. A `Runtime` represents one executable invocation state. Use separate runtimes when running invocations concurrently.
---
# Zig usage
## Add the dependency
With Zig `0.16.0` or newer:
```sh
zig fetch --save git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter
```
Then import the package module from your `build.zig`:
```zig
const spv_dep = b.dependency("SPIRV_Interpreter", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("spv", spv_dep.module("spv"));
```
In your code:
```zig
const spv = @import("spv");
```
## Minimal Zig example
This example loads an embedded `.spv` file, calls the `main` entry point, and reads a `vec4<f32>` output named `color`.
```zig
const std = @import("std");
const spv = @import("spv");
const shader_source = @embedFile("shader.spv");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var module = try spv.Module.init(
allocator,
@ptrCast(@alignCast(shader_source)),
.{},
);
defer module.deinit(allocator);
// If the shader does not execute image operations, the image API is unused.
// For image load/store/sampling shaders, provide a real spv.Runtime.ImageAPI.
var rt = try spv.Runtime.init(allocator, &module, undefined);
defer rt.deinit(allocator);
const entry = try rt.getEntryPointByName("main");
const color_result = try rt.getResultByName("color");
try rt.callEntryPoint(allocator, entry);
var color: [4]f32 = undefined;
try rt.readOutput(std.mem.sliceAsBytes(color[0..]), color_result);
std.debug.print("color = {any}\n", .{color});
}
```
## Writing inputs
You can write an input by result id:
```zig
const pos_result = try rt.getResultByName("pos");
const pos = [_]f32{ 10.0, 20.0 };
try rt.writeInput(std.mem.sliceAsBytes(pos[0..]), pos_result);
```
Or by location:
```zig
const uv = [_]f32{ 0.25, 0.75 };
try rt.writeInputLocation(std.mem.sliceAsBytes(uv[0..]), 0);
```
For scalar or struct data, use `std.mem.asBytes`:
```zig
const time: f32 = 1.5;
const time_result = try rt.getResultByName("time");
try rt.writeInput(std.mem.asBytes(&time), time_result);
```
## Reading outputs
You can read an output by result id:
```zig
const color_result = try rt.getResultByName("color");
var color: [4]f32 = undefined;
try rt.readOutput(std.mem.sliceAsBytes(color[0..]), color_result);
```
Or locate an output by `Location` decoration:
```zig
const color_result = try rt.getResultByLocation(0, .output);
var color: [4]f32 = undefined;
try rt.readOutput(std.mem.sliceAsBytes(color[0..]), color_result);
```
## Push constants
Push constants are passed as raw bytes:
```zig
const PushConstants = extern struct {
time: f32,
scale: f32,
};
const push_constants = PushConstants{
.time = 1.0,
.scale = 2.0,
};
try rt.populatePushConstants(std.mem.asBytes(&push_constants));
```
## Descriptor sets
Descriptor writes are passed as raw bytes and selected by set, binding, and descriptor index:
```zig
try rt.writeDescriptorSet(
std.mem.sliceAsBytes(buffer[0..]),
0, // set
1, // binding
0, // descriptor index
);
```
For non-array descriptors, use descriptor index `0`.
After running a shader that writes through descriptor-backed memory, flush descriptor sets before reading the backing data:
```zig
try rt.callEntryPoint(allocator, entry);
try rt.flushDescriptorSets(allocator);
```
## Specialization constants
Specialization constants are selected by specialization id:
```zig
const value: u32 = 64;
try rt.addSpecializationInfo(
allocator,
.{
.id = 0,
.offset = 0,
.size = @sizeOf(u32),
},
std.mem.asBytes(&value),
);
```
Add specialization constants before calling the entry point.
To copy specialization constants between runtimes:
```zig
try rt.copySpecializationConstantsFrom(allocator, &source_rt);
```
## Entry points and barriers
For most shaders, `callEntryPoint` is enough:
```zig
try rt.callEntryPoint(allocator, entry);
```
For shaders that may hit barriers, use `beginEntryPoint` and `continueEntryPoint`:
```zig
var status = try rt.beginEntryPoint(allocator, entry);
while (status == .barrier) {
// Synchronize other invocations here if needed.
status = try rt.continueEntryPoint(allocator);
}
```
## Multiple runtimes
A module can be shared by multiple runtimes. This is the preferred model for parallel execution:
```zig
var rt_a = try spv.Runtime.init(allocator, &module, undefined);
defer rt_a.deinit(allocator);
var rt_b = try spv.Runtime.init(allocator, &module, undefined);
defer rt_b.deinit(allocator);
```
Do not mutate the same runtime concurrently from multiple threads. Use one runtime per worker or invocation stream.
## Image operations
Shaders that use image load, image store, image sampling, or image-size queries need a real image API:
```zig
const image_api = spv.Runtime.ImageAPI{
.readImageFloat4 = readImageFloat4,
.readImageInt4 = readImageInt4,
.writeImageFloat4 = writeImageFloat4,
.writeImageInt4 = writeImageInt4,
.sampleImageFloat4 = sampleImageFloat4,
.sampleImageInt4 = sampleImageInt4,
.sampleImageDref = sampleImageDref,
.queryImageSize = queryImageSize,
};
var rt = try spv.Runtime.init(allocator, &module, image_api);
```
Sample callbacks receive optional explicit LOD and an integer texel offset:
```zig
fn sampleImageFloat4(
driver_image: *anyopaque,
driver_sampler: *anyopaque,
dim: spv.SpvDim,
x: f32,
y: f32,
z: f32,
lod: ?f32,
offset: spv.Runtime.ImageOffset,
) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
_ = .{ driver_image, driver_sampler, dim, x, y, z, lod, offset };
return .{ .x = 0, .y = 0, .z = 0, .w = 1 };
}
```
Depth-comparison samplers call `sampleImageDref` and return a scalar `f32`.
## Derivatives
Fragment shaders using derivative operations need derivative data on the source result:
```zig
try rt.setDerivativeFromMemory(
allocator,
input_result,
std.mem.asBytes(&dx),
std.mem.asBytes(&dy),
);
try rt.copyDerivative(allocator, dst_result, input_result);
rt.clearDerivative(allocator, dst_result);
```
For low-level integrations, `setDerivative` accepts interpreter `Value` objects directly.
---
# C usage
## Build the C FFI
Build the static C FFI library:
```sh
zig build ffi-c --release=fast
```
Other release modes are also supported:
```sh
zig build ffi-c --release=safe
zig build ffi-c --release=small
```
To build a shared library instead of a static library:
```sh
zig build ffi-c --release=fast -Dffi-build-static=false
```
The library is installed into:
```text
zig-out/lib/
```
The public header is installed into:
```text
zig-out/include/SpirvInterpreter.h
```
The source header is also available in:
```text
ffi/SpirvInterpreter.h
```
## Minimal C example
```c
#include <stdio.h>
#include <SpirvInterpreter.h>
static const unsigned char shader_source[] = {
/* Shader bytecode */
};
int main(void)
{
SpvModule module;
SpvModuleOptions options;
options.use_simd_vectors_specializations = 1;
if(SpvInitModule(&module, (SpvWord*)shader_source, sizeof(shader_source) / 4, options) != SPV_RESULT_SUCCESS)
return -1;
SpvRuntime runtime;
/**
* A zeroed image API is only safe when the shader does not execute image
* load/store/sample/query operations.
*/
if(SpvInitRuntime(&runtime, module, (SpvImageAPI){0}) != SPV_RESULT_SUCCESS)
return -1;
SpvWord main_entry_index;
SpvGetEntryPointByName(runtime, "main", &main_entry_index);
SpvCallEntryPoint(runtime, main_entry_index);
float output[4];
SpvWord output_result;
SpvGetResultByName(runtime, "color", &output_result);
SpvReadOutput(runtime, (SpvByte*)output, sizeof(output), output_result);
printf("Output: Vec4[%f, %f, %f, %f]\n", output[0], output[1], output[2], output[3]);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return 0;
}
```
## Writing inputs from C
Write by result id:
```c
SpvWord pos_result = 0;
if (SpvGetResultByName(runtime, "pos", &pos_result) != SPV_RESULT_SUCCESS)
return 1;
float pos[2] = {10.0f, 20.0f};
if (SpvWriteInput(runtime, (const SpvByte*)pos, sizeof(pos), pos_result) != SPV_RESULT_SUCCESS)
return 1;
```
Write by input location:
```c
float uv[2] = {0.25f, 0.75f};
if (SpvWriteInputLocation(runtime, (const SpvByte*)uv, sizeof(uv), 0) != SPV_RESULT_SUCCESS)
return 1;
```
## Reading outputs from C
Read by result id:
```c
SpvWord color_result = 0;
float color[4] = {0};
SpvGetResultByName(runtime, "color", &color_result);
SpvReadOutput(runtime, (SpvByte*)color, sizeof(color), color_result);
```
Read by output location:
```c
SpvWord color_result = 0;
SpvGetResultByLocation(runtime, 0, SPV_LOCATION_OUTPUT, &color_result);
```
For component-qualified locations:
```c
SpvWord result = 0;
SpvGetResultByLocationComponent(runtime, 0, 1, SPV_LOCATION_OUTPUT, &result);
```
## Push constants from C
```c
typedef struct PushConstants
{
float time;
float scale;
} PushConstants;
PushConstants push_constants = {
.time = 1.0f,
.scale = 2.0f,
};
SpvPopulatePushConstants(runtime, (const SpvByte*)&push_constants, sizeof(push_constants));
```
## Descriptor sets from C
```c
SpvWriteDescriptorSet(runtime, (const SpvByte*)buffer, buffer_size,
0, /* set */
1, /* binding */
0 /* descriptor index */
);
```
For non-array descriptors, use descriptor index `0`.
You can query a descriptor binding from a module:
```c
SpvWord result = 0;
SpvResult status = SpvModuleGetBindingResult(module, 0, 1, &result);
```
After a shader writes through descriptor-backed memory, flush descriptor sets before reading the backing data:
```c
SpvCallEntryPoint(runtime, entry);
SpvFlushDescriptorSets(runtime);
```
## Specialization constants from C
```c
unsigned int value = 64;
SpvRuntimeSpecializationEntry entry = {
.id = 0,
.offset = 0,
.size = sizeof(value),
};
SpvAddSpecializationInfo(runtime, entry, (const SpvByte*)&value, sizeof(value));
```
Add specialization constants before calling the entry point.
To duplicate specialization constants from another runtime:
```c
SpvCopySpecializationConstantsFrom(runtime, source_runtime);
```
## Derivatives from C
```c
float dx[4] = {1.0f, 0.0f, 0.0f, 0.0f};
float dy[4] = {0.0f, 1.0f, 0.0f, 0.0f};
SpvSetDerivativeFromMemory(
runtime,
result,
(const SpvByte*)dx,
sizeof(dx),
(const SpvByte*)dy,
sizeof(dy));
SpvCopyDerivative(runtime, dst_result, result);
SpvClearDerivative(runtime, dst_result);
```
## Barriers from C
For most shaders:
```c
SpvCallEntryPoint(runtime, entry);
```
For shaders that may hit barriers:
```c
SpvEntryPointStatus status = SPV_ENTRY_POINT_COMPLETED;
SpvBeginEntryPoint(runtime, entry, &status);
while (status == SPV_ENTRY_POINT_BARRIER)
{
/* Synchronize other invocations here if needed. */
SpvContinueEntryPoint(runtime, &status);
}
```
## Image API from C
Shaders that execute image operations must provide callbacks in `SpvImageAPI`.
```c
static SpvResult ReadImageFloat4(
void* driver_image,
SpvDim dim,
int x,
int y,
int z,
SpvVec4f* dst)
{
(void)driver_image;
(void)dim;
(void)x;
(void)y;
(void)z;
dst->x = 0.0f;
dst->y = 0.0f;
dst->z = 0.0f;
dst->w = 1.0f;
return SPV_RESULT_SUCCESS;
}
```
Sampling callbacks include an explicit-LOD flag/value and an offset:
```c
static SpvResult SampleImageFloat4(
void* driver_image,
void* driver_sampler,
SpvDim dim,
float x,
float y,
float z,
SpvBool has_lod,
float lod,
SpvImageOffset offset,
SpvVec4f* dst)
{
(void)driver_image;
(void)driver_sampler;
(void)dim;
(void)has_lod;
(void)lod;
(void)offset;
dst->x = x;
dst->y = y;
dst->z = z;
dst->w = 1.0f;
return SPV_RESULT_SUCCESS;
}
```
Depth-comparison samplers add `dref` and write one `float`:
```c
static SpvResult SampleImageDref(
void* driver_image,
void* driver_sampler,
SpvDim dim,
float x,
float y,
float z,
float dref,
SpvBool has_lod,
float lod,
SpvImageOffset offset,
float* dst)
{
(void)driver_image;
(void)driver_sampler;
(void)dim;
(void)x;
(void)y;
(void)z;
(void)has_lod;
(void)lod;
(void)offset;
*dst = dref;
return SPV_RESULT_SUCCESS;
}
```
The image API table contains these callbacks:
```c
SpvImageAPI image_api = {
.SpvReadImageFloat4 = ReadImageFloat4,
.SpvReadImageInt4 = ReadImageInt4,
.SpvWriteImageFloat4 = WriteImageFloat4,
.SpvWriteImageInt4 = WriteImageInt4,
.SpvSampleImageFloat4 = SampleImageFloat4,
.SpvSampleImageInt4 = SampleImageInt4,
.SpvSampleImageDref = SampleImageDref,
.SpvQueryImageSize = QueryImageSize,
};
```
Pass the table when creating the runtime:
```c
SpvInitRuntime(&runtime, module, image_api);
```
## Cleanup order
Always destroy runtimes before destroying the module they were created from:
```c
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
```
+205 -69
View File
@@ -3,129 +3,265 @@ const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const use_llvm = b.option(bool, "use-llvm", "Use LLVM backend") orelse (b.release_mode != .off);
const use_llvm = b.option(bool, "use-llvm", "use llvm") orelse (b.release_mode != .off);
const mod = b.addModule("spv", .{
const spv_mod = b.addModule("spv", .{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
const zmath = b.dependency("zmath", .{});
mod.addImport("zmath", zmath.module("root"));
spv_mod.addImport("zmath", zmath.module("root"));
const pretty = b.dependency("pretty", .{ .target = target, .optimize = optimize });
mod.addImport("pretty", pretty.module("pretty"));
const pretty = b.dependency("pretty", .{
.target = target,
.optimize = optimize,
});
spv_mod.addImport("pretty", pretty.module("pretty"));
const lib = b.addLibrary(.{
const spv_lib = b.addLibrary(.{
.name = "spirv_interpreter",
.root_module = mod,
.root_module = spv_mod,
.linkage = .dynamic,
.use_llvm = use_llvm,
});
const lib_install = b.addInstallArtifact(lib, .{});
// Zig example setup
const install_spv_lib = b.addInstallArtifact(spv_lib, .{});
const no_example = b.option(bool, "no-example", "skips example dependencies fetch") orelse false;
addSandbox(b, target, optimize, use_llvm, spv_mod, &install_spv_lib.step);
addExample(b, target, optimize, use_llvm, spv_mod, &install_spv_lib.step);
addZigTests(b, target, optimize, use_llvm, spv_mod, zmath);
addCffi(b, target, optimize, use_llvm, spv_mod);
addDocs(b, spv_mod);
}
fn addExample(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
install_spv_lib_step: *std.Build.Step,
) void {
const no_example = b.option(bool, "no-example", "Skip example build") orelse false;
if (!no_example) {
const sdl3 = b.lazyDependency("sdl3", .{ .target = target, .optimize = optimize }) orelse return;
const example_exe = b.addExecutable(.{
const sdl3 = b.lazyDependency("sdl3", .{
.target = target,
.optimize = optimize,
}) orelse return;
const exe = b.addExecutable(.{
.name = "spirv_interpreter_example",
.root_module = b.createModule(.{
.root_source_file = b.path("example/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
.{ .name = "spv", .module = spv_mod },
.{ .name = "sdl3", .module = sdl3.module("sdl3") },
//.{ .name = "pretty", .module = pretty.module("pretty") },
},
}),
.use_llvm = use_llvm,
});
const example_install = b.addInstallArtifact(example_exe, .{});
example_install.step.dependOn(&lib_install.step);
const install_exe = b.addInstallArtifact(exe, .{});
install_exe.step.dependOn(install_spv_lib_step);
const run_example = b.addRunArtifact(example_exe);
run_example.step.dependOn(&example_install.step);
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(&install_exe.step);
const run_example_step = b.step("example", "Run the example");
run_example_step.dependOn(&run_example.step);
const run_step = b.step("example", "Run the example");
run_step.dependOn(&run_exe.step);
const compile_shader_cmd = b.addSystemCommand(&[_][]const u8{ "nzslc", "example/shader.nzsl", "--compile=spv,spv-dis", "-o", "example" });
const compile_shader_step = b.step("example-shader", "Compiles example's shader (needs nzslc installed)");
compile_shader_step.dependOn(&compile_shader_cmd.step);
addShaderCompileStep(
b,
"example-shader",
"Compile example shader using nzslc",
"example/shader.nzsl",
"example",
);
}
}
// Zig sandbox setup
const sandbox_exe = b.addExecutable(.{
fn addSandbox(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
install_spv_lib_step: *std.Build.Step,
) void {
const exe = b.addExecutable(.{
.name = "spirv_interpreter_sandbox",
.root_module = b.createModule(.{
.root_source_file = b.path("sandbox/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
//.{ .name = "pretty", .module = pretty.module("pretty") },
.{ .name = "spv", .module = spv_mod },
},
}),
.use_llvm = use_llvm,
});
const sandbox_install = b.addInstallArtifact(sandbox_exe, .{});
sandbox_install.step.dependOn(&lib_install.step);
const install_exe = b.addInstallArtifact(exe, .{});
install_exe.step.dependOn(install_spv_lib_step);
const run_sandbox = b.addRunArtifact(sandbox_exe);
run_sandbox.step.dependOn(&sandbox_install.step);
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(&install_exe.step);
const run_sandbox_step = b.step("sandbox", "Run the sandbox");
run_sandbox_step.dependOn(&run_sandbox.step);
const run_step = b.step("sandbox", "Run the sandbox");
run_step.dependOn(&run_exe.step);
const compile_shader_cmd = b.addSystemCommand(&[_][]const u8{ "nzslc", "sandbox/shader.nzsl", "--compile=spv,spv-dis", "-o", "sandbox" });
const compile_shader_step = b.step("sandbox-shader", "Compiles sandbox's shader (needs nzslc installed)");
compile_shader_step.dependOn(&compile_shader_cmd.step);
addShaderCompileStep(
b,
"sandbox-shader",
"Compile sandbox shader using nzslc",
"sandbox/shader.nzsl",
"sandbox",
);
}
// Zig unit tests setup
const no_test = b.option(bool, "no-test", "skips unit test dependencies fetch") orelse false;
if (!no_test) {
const nzsl = b.lazyDependency("NZSL", .{ .target = target, .optimize = optimize }) orelse return;
const lib_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
.{ .name = "nzsl", .module = nzsl.module("nzigsl") },
.{ .name = "zmath", .module = zmath.module("root") },
},
}),
.test_runner = .{ .path = b.path("test/test_runner.zig"), .mode = .simple },
});
const run_tests = b.addRunArtifact(lib_tests);
const test_step = b.step("test", "Run Zig unit tests");
test_step.dependOn(&run_tests.step);
}
// Docs generation
const autodoc_test = b.addObject(.{
.name = "lib",
.root_module = mod,
fn addShaderCompileStep(
b: *std.Build,
step_name: []const u8,
description: []const u8,
shader_path: []const u8,
output_dir: []const u8,
) void {
const cmd = b.addSystemCommand(&.{
"nzslc",
shader_path,
"--compile=spv,spv-dis",
"-o",
output_dir,
});
const step = b.step(step_name, description);
step.dependOn(&cmd.step);
}
fn addZigTests(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
zmath: *std.Build.Dependency,
) void {
const no_test = b.option(bool, "no-test", "Skip unit test dependencies fetch") orelse false;
if (no_test) return;
const test_filter = b.option(
[]const u8,
"test-filter",
"Only run tests whose name contains this substring",
);
const nzsl = b.lazyDependency("NZSL", .{
.target = target,
.optimize = .ReleaseFast,
}) orelse return;
const tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = spv_mod },
.{ .name = "nzsl", .module = nzsl.module("nzigsl") },
.{ .name = "zmath", .module = zmath.module("root") },
},
}),
.test_runner = .{
.path = b.path("test/test_runner.zig"),
.mode = .simple,
},
.filters = if (test_filter) |filter| &.{filter} else &.{},
.use_llvm = use_llvm,
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run Zig unit tests");
test_step.dependOn(&run_tests.step);
}
fn addCffi(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
) void {
const static_c_ffi = b.option(bool, "ffi-build-static", "Build C FFI statically") orelse true;
const c_ffi_mod = b.addModule("c_ffi_spv", .{
.root_source_file = b.path("ffi/ffi.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "spv", .module = spv_mod },
},
});
const install_header = b.addInstallHeaderFile(
b.path("ffi/SpirvInterpreter.h"),
"SpirvInterpreter.h",
);
const c_ffi_lib = b.addLibrary(.{
.name = "spirv_interpreter_c_ffi",
.root_module = c_ffi_mod,
.linkage = if (static_c_ffi) .static else .dynamic,
.use_llvm = use_llvm,
});
const install_lib = b.addInstallArtifact(c_ffi_lib, .{});
const ffi_step = b.step("ffi-c", "Build C FFI");
ffi_step.dependOn(&install_lib.step);
ffi_step.dependOn(&install_header.step);
const c_test = b.addExecutable(.{
.name = "c_test",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.use_llvm = use_llvm,
});
c_test.root_module.addCSourceFile(.{ .file = b.path("test_c/main.c") });
c_test.root_module.linkLibrary(c_ffi_lib);
c_test.root_module.addSystemIncludePath(b.path("ffi"));
const install_c_test = b.addInstallArtifact(c_test, .{});
install_c_test.step.dependOn(&install_lib.step);
const run_c_test = b.addRunArtifact(c_test);
run_c_test.step.dependOn(&install_c_test.step);
const test_c_step = b.step("test-c", "Run C test");
test_c_step.dependOn(&run_c_test.step);
}
fn addDocs(b: *std.Build, spv_mod: *std.Build.Module) void {
const autodoc_obj = b.addObject(.{
.name = "lib",
.root_module = spv_mod,
});
const install_docs = b.addInstallDirectory(.{
.source_dir = autodoc_test.getEmittedDocs(),
.source_dir = autodoc_obj.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "docs",
});
const docs_step = b.step("docs", "Build and install the documentation");
const docs_step = b.step("docs", "Build and install documentation");
docs_step.dependOn(&install_docs.step);
}
+9 -9
View File
@@ -6,22 +6,22 @@
.url = "git+https://github.com/zig-gamedev/zmath.git#3a5955b2b72cd081563fbb084eff05bffd1e3fbb",
.hash = "zmath-0.11.0-dev-wjwivdMsAwD-xaLj76YHUq3t9JDH-X16xuMTmnDzqbu2",
},
.pretty = .{ // For debugging purposes
.url = "git+https://github.com/Kbz-8/pretty#117674465efd4d07d5ae9d9d8ca59c2c323a65ba",
.hash = "pretty-0.10.6-Tm65r99UAQDEJMgZysD10qE8dinBHr064fPM6YkxVPfB",
},
.NZSL = .{ // For unit tests
.url = "git+https://git.kbz8.me/kbz_8/NZigSL#5377dbdf9935b0de767f76ea4650e7aba4516b32",
.hash = "NZSL-1.1.2-N0xSVMt6AAC1ncQHA_RafnclWolDA477iTnFmZgdvxd-",
.url = "git+https://git.kbz8.me/kbz_8/NZigSL#3ab28e423cdab27797f779d352094a69800c0c6b",
.hash = "NZSL-1.1.5-N0xSVHV7AAB168H2nqMDYY0hjUXtXJ9_eQGNHasSr9r8",
.lazy = true,
},
.sdl3 = .{
.url = "git+https://codeberg.org/7Games/zig-sdl3?ref=v0.1.6#9c1842246c59f03f87ba59b160ca7e3d5e5ce972",
.hash = "sdl3-0.1.6-NmT1Q5sQJgCzT6hLj7WOSrwxE0Qsef1wIkDopbOOFru0",
.url = "git+https://codeberg.org/7Games/zig-sdl3?ref=v0.2.0#40c2e4b579aa556db37a502c936426aa1c8b5c95",
.hash = "sdl3-0.2.0-NmT1Q0mFJwBi9kZmArzh2rfJ_mFshydV0zPGULVlpACc",
.lazy = true,
},
.pretty = .{
.url = "git+https://github.com/Kbz-8/pretty.git#f91d534d033277ca1ae7fcd598a070e8b3ddc532",
.hash = "pretty-0.10.6-Tm65r7FTAQA5BEL8tcIcF-Wp4XRC7J7BhuRI0KUnFj2X",
},
},
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.paths = .{
"build.zig",
"build.zig.zon",
+53 -42
View File
@@ -4,14 +4,22 @@ const spv = @import("spv");
const shader_source = @embedFile("shader.spv");
const screen_width = 300;
const screen_height = 300;
const screen_width = 400;
const screen_height = 240;
pub fn main() !void {
{
//var gpa: std.heap.DebugAllocator(.{}) = .init;
//defer _ = gpa.deinit();
const allocator = std.heap.smp_allocator;
var threaded: std.Io.Threaded = .init(allocator, .{
.async_limit = @enumFromInt(screen_height),
});
defer threaded.deinit();
const io = threaded.io();
defer sdl3.shutdown();
const init_flags = sdl3.InitFlags{ .video = true, .events = true };
try sdl3.init(init_flags);
@@ -22,22 +30,28 @@ pub fn main() !void {
const surface = try window.getSurface();
const allocator = std.heap.smp_allocator;
var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source)), .{});
defer module.deinit(allocator);
var runner_cache: std.ArrayList(Runner) = try .initCapacity(allocator, screen_height);
const fragment_count = screen_height * screen_width;
const batch_size = switch (threaded.async_limit) {
.nothing => 1,
.unlimited => std.Thread.getCpuCount() catch 1, // If we cannot get the CPU count, fallback on single runtime
else => |count| @intFromEnum(count),
};
const runners: []Runner = try allocator.alloc(Runner, batch_size);
defer {
for (runner_cache.items) |*runner| {
for (runners) |*runner| {
runner.rt.deinit(allocator);
}
runner_cache.deinit(allocator);
allocator.free(runners);
}
for (0..screen_height) |_| {
var rt = try spv.Runtime.init(allocator, &module);
(try runner_cache.addOne(allocator)).* = .{
for (runners) |*runner| {
var rt = try spv.Runtime.init(allocator, &module, undefined);
runner.* = .{
.allocator = allocator,
.surface = surface,
.rt = rt,
@@ -46,14 +60,13 @@ pub fn main() !void {
.time = try rt.getResultByName("time"),
.pos = try rt.getResultByName("pos"),
.res = try rt.getResultByName("res"),
.invocation_count = fragment_count,
.batch_size = batch_size,
};
}
const timer = std.Io.Timestamp.now(io, .real);
var thread_pool: std.Thread.Pool = undefined;
try thread_pool.init(.{ .allocator = allocator });
var timer = try std.time.Timer.start();
var wg: std.Io.Group = .init;
var quit = false;
while (!quit) {
try surface.clear(.{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 });
@@ -71,21 +84,13 @@ pub fn main() !void {
const pixel_map: [*]u32 = @as([*]u32, @ptrCast(@alignCast((surface.getPixels() orelse return).ptr)));
const delta: f32 = @as(f32, @floatFromInt(timer.read())) / std.time.ns_per_s;
const duration = timer.untilNow(io, .real);
const delta: f32 = @as(f32, @floatFromInt(duration.toNanoseconds())) / std.time.ns_per_s;
var frame_timer = try std.time.Timer.start();
defer {
const ns = frame_timer.lap();
const ms = @as(f32, @floatFromInt(ns)) / std.time.ns_per_s;
std.log.info("Took {d:.3}s - {d:.3}fps to render", .{ ms, 1.0 / ms });
for (0..@min(batch_size, fragment_count)) |batch_id| {
wg.async(io, Runner.runWrapper, .{ &runners[batch_id], batch_id, pixel_map, delta });
}
var wait_group: std.Thread.WaitGroup = .{};
for (0..screen_height) |y| {
const runner = &runner_cache.items[y];
thread_pool.spawnWg(&wait_group, Runner.runWrapper, .{ runner, y, pixel_map, delta });
}
thread_pool.waitAndWork(&wait_group);
try wg.await(io);
}
try window.updateSurface();
@@ -105,37 +110,43 @@ const Runner = struct {
time: spv.SpvWord,
pos: spv.SpvWord,
res: spv.SpvWord,
invocation_count: usize,
batch_size: usize,
fn runWrapper(self: *Self, y: usize, pixel_map: [*]u32, timer: f32) void {
@call(.always_inline, Self.run, .{ self, y, pixel_map, timer }) catch |err| {
fn runWrapper(self: *Self, batch_id: usize, pixel_map: [*]u32, timer: f32) void {
@call(.always_inline, Self.run, .{ self, batch_id, pixel_map, timer }) catch |err| {
std.log.err("{s}", .{@errorName(err)});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
std.debug.dumpErrorReturnTrace(trace);
}
std.process.abort();
};
}
fn run(self: *Self, y: usize, pixel_map: [*]u32, timer: f32) !void {
fn run(self: *Self, batch_id: usize, pixel_map: [*]u32, timer: f32) !void {
var rt = self.rt; // Copy to avoid pointer access of `self` at runtime. Okay as Runtime contains only pointers and trivially copyable fields
var output: [4]f32 = undefined;
for (0..screen_width) |x| {
try rt.writeInput(&.{timer}, self.time);
try rt.writeInput(&.{ @floatFromInt(screen_width), @floatFromInt(screen_height) }, self.res);
try rt.writeInput(&.{ @floatFromInt(x), @floatFromInt(y) }, self.pos);
var invocation_index: usize = batch_id;
while (invocation_index < self.invocation_count) : (invocation_index += self.batch_size) {
const y = @divTrunc(invocation_index, screen_width);
const x = @mod(invocation_index, screen_width);
try rt.writeInput(std.mem.asBytes(&timer), self.time);
try rt.writeInput(std.mem.asBytes(&[_]f32{ @floatFromInt(screen_width), @floatFromInt(screen_height) }), self.res);
try rt.writeInput(std.mem.asBytes(&[_]f32{ @floatFromInt(x), @floatFromInt(y) }), self.pos);
try rt.callEntryPoint(self.allocator, self.entry);
try rt.readOutput(output[0..], self.color);
try rt.readOutput(std.mem.asBytes(output[0..]), self.color);
const rgba = self.surface.mapRgba(
@intCast(@max(@min(@as(i32, @intFromFloat(output[0] * 255.0)), 255), 0)),
@intCast(@max(@min(@as(i32, @intFromFloat(output[1] * 255.0)), 255), 0)),
@intCast(@max(@min(@as(i32, @intFromFloat(output[2] * 255.0)), 255), 0)),
@intCast(@max(@min(@as(i32, @intFromFloat(output[3] * 255.0)), 255), 0)),
@intCast(std.math.clamp(@as(i32, @intFromFloat(output[0] * 255.0)), 0, 255)),
@intCast(std.math.clamp(@as(i32, @intFromFloat(output[1] * 255.0)), 0, 255)),
@intCast(std.math.clamp(@as(i32, @intFromFloat(output[2] * 255.0)), 0, 255)),
@intCast(std.math.clamp(@as(i32, @intFromFloat(output[3] * 255.0)), 0, 255)),
);
pixel_map[(y * self.surface.getWidth()) + x] = rgba.value;
pixel_map[invocation_index] = rgba.value;
}
}
};
+639
View File
@@ -0,0 +1,639 @@
/*
Copyright (C) 2026 kbz_8 (contact@kbz8.me)
This file is part of the "SPIR-V Interpreter - FFI C Bindings" project
MIT License
Copyright (c) 2026 kbz_8
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SPIRV_INTERPRETER_H
#define SPIRV_INTERPRETER_H
#ifndef SPV_API
#define SPV_API extern
#endif /* SPV_API */
#ifdef __cplusplus
extern "C"
{
#endif
#ifndef spirv_H
typedef enum SpvBuiltIn_
{
SpvBuiltInPosition = 0,
SpvBuiltInPointSize = 1,
SpvBuiltInClipDistance = 3,
SpvBuiltInCullDistance = 4,
SpvBuiltInVertexId = 5,
SpvBuiltInInstanceId = 6,
SpvBuiltInPrimitiveId = 7,
SpvBuiltInInvocationId = 8,
SpvBuiltInLayer = 9,
SpvBuiltInViewportIndex = 10,
SpvBuiltInTessLevelOuter = 11,
SpvBuiltInTessLevelInner = 12,
SpvBuiltInTessCoord = 13,
SpvBuiltInPatchVertices = 14,
SpvBuiltInFragCoord = 15,
SpvBuiltInPointCoord = 16,
SpvBuiltInFrontFacing = 17,
SpvBuiltInSampleId = 18,
SpvBuiltInSamplePosition = 19,
SpvBuiltInSampleMask = 20,
SpvBuiltInFragDepth = 22,
SpvBuiltInHelperInvocation = 23,
SpvBuiltInNumWorkgroups = 24,
SpvBuiltInWorkgroupSize = 25,
SpvBuiltInWorkgroupId = 26,
SpvBuiltInLocalInvocationId = 27,
SpvBuiltInGlobalInvocationId = 28,
SpvBuiltInLocalInvocationIndex = 29,
SpvBuiltInWorkDim = 30,
SpvBuiltInGlobalSize = 31,
SpvBuiltInEnqueuedWorkgroupSize = 32,
SpvBuiltInGlobalOffset = 33,
SpvBuiltInGlobalLinearId = 34,
SpvBuiltInSubgroupSize = 36,
SpvBuiltInSubgroupMaxSize = 37,
SpvBuiltInNumSubgroups = 38,
SpvBuiltInNumEnqueuedSubgroups = 39,
SpvBuiltInSubgroupId = 40,
SpvBuiltInSubgroupLocalInvocationId = 41,
SpvBuiltInVertexIndex = 42,
SpvBuiltInInstanceIndex = 43,
SpvBuiltInCoreIDARM = 4160,
SpvBuiltInCoreCountARM = 4161,
SpvBuiltInCoreMaxIDARM = 4162,
SpvBuiltInWarpIDARM = 4163,
SpvBuiltInWarpMaxIDARM = 4164,
SpvBuiltInSubgroupEqMask = 4416,
SpvBuiltInSubgroupEqMaskKHR = 4416,
SpvBuiltInSubgroupGeMask = 4417,
SpvBuiltInSubgroupGeMaskKHR = 4417,
SpvBuiltInSubgroupGtMask = 4418,
SpvBuiltInSubgroupGtMaskKHR = 4418,
SpvBuiltInSubgroupLeMask = 4419,
SpvBuiltInSubgroupLeMaskKHR = 4419,
SpvBuiltInSubgroupLtMask = 4420,
SpvBuiltInSubgroupLtMaskKHR = 4420,
SpvBuiltInBaseVertex = 4424,
SpvBuiltInBaseInstance = 4425,
SpvBuiltInDrawIndex = 4426,
SpvBuiltInPrimitiveShadingRateKHR = 4432,
SpvBuiltInDeviceIndex = 4438,
SpvBuiltInViewIndex = 4440,
SpvBuiltInShadingRateKHR = 4444,
SpvBuiltInTileOffsetQCOM = 4492,
SpvBuiltInTileDimensionQCOM = 4493,
SpvBuiltInTileApronSizeQCOM = 4494,
SpvBuiltInBaryCoordNoPerspAMD = 4992,
SpvBuiltInBaryCoordNoPerspCentroidAMD = 4993,
SpvBuiltInBaryCoordNoPerspSampleAMD = 4994,
SpvBuiltInBaryCoordSmoothAMD = 4995,
SpvBuiltInBaryCoordSmoothCentroidAMD = 4996,
SpvBuiltInBaryCoordSmoothSampleAMD = 4997,
SpvBuiltInBaryCoordPullModelAMD = 4998,
SpvBuiltInFragStencilRefEXT = 5014,
SpvBuiltInRemainingRecursionLevelsAMDX = 5021,
SpvBuiltInShaderIndexAMDX = 5073,
SpvBuiltInSamplerHeapEXT = 5122,
SpvBuiltInResourceHeapEXT = 5123,
SpvBuiltInViewportMaskNV = 5253,
SpvBuiltInSecondaryPositionNV = 5257,
SpvBuiltInSecondaryViewportMaskNV = 5258,
SpvBuiltInPositionPerViewNV = 5261,
SpvBuiltInViewportMaskPerViewNV = 5262,
SpvBuiltInFullyCoveredEXT = 5264,
SpvBuiltInTaskCountNV = 5274,
SpvBuiltInPrimitiveCountNV = 5275,
SpvBuiltInPrimitiveIndicesNV = 5276,
SpvBuiltInClipDistancePerViewNV = 5277,
SpvBuiltInCullDistancePerViewNV = 5278,
SpvBuiltInLayerPerViewNV = 5279,
SpvBuiltInMeshViewCountNV = 5280,
SpvBuiltInMeshViewIndicesNV = 5281,
SpvBuiltInBaryCoordKHR = 5286,
SpvBuiltInBaryCoordNV = 5286,
SpvBuiltInBaryCoordNoPerspKHR = 5287,
SpvBuiltInBaryCoordNoPerspNV = 5287,
SpvBuiltInFragSizeEXT = 5292,
SpvBuiltInFragmentSizeNV = 5292,
SpvBuiltInFragInvocationCountEXT = 5293,
SpvBuiltInInvocationsPerPixelNV = 5293,
SpvBuiltInPrimitivePointIndicesEXT = 5294,
SpvBuiltInPrimitiveLineIndicesEXT = 5295,
SpvBuiltInPrimitiveTriangleIndicesEXT = 5296,
SpvBuiltInCullPrimitiveEXT = 5299,
SpvBuiltInLaunchIdKHR = 5319,
SpvBuiltInLaunchIdNV = 5319,
SpvBuiltInLaunchSizeKHR = 5320,
SpvBuiltInLaunchSizeNV = 5320,
SpvBuiltInWorldRayOriginKHR = 5321,
SpvBuiltInWorldRayOriginNV = 5321,
SpvBuiltInWorldRayDirectionKHR = 5322,
SpvBuiltInWorldRayDirectionNV = 5322,
SpvBuiltInObjectRayOriginKHR = 5323,
SpvBuiltInObjectRayOriginNV = 5323,
SpvBuiltInObjectRayDirectionKHR = 5324,
SpvBuiltInObjectRayDirectionNV = 5324,
SpvBuiltInRayTminKHR = 5325,
SpvBuiltInRayTminNV = 5325,
SpvBuiltInRayTmaxKHR = 5326,
SpvBuiltInRayTmaxNV = 5326,
SpvBuiltInInstanceCustomIndexKHR = 5327,
SpvBuiltInInstanceCustomIndexNV = 5327,
SpvBuiltInObjectToWorldKHR = 5330,
SpvBuiltInObjectToWorldNV = 5330,
SpvBuiltInWorldToObjectKHR = 5331,
SpvBuiltInWorldToObjectNV = 5331,
SpvBuiltInHitTNV = 5332,
SpvBuiltInHitKindKHR = 5333,
SpvBuiltInHitKindNV = 5333,
SpvBuiltInCurrentRayTimeNV = 5334,
SpvBuiltInHitTriangleVertexPositionsKHR = 5335,
SpvBuiltInHitMicroTriangleVertexPositionsNV = 5337,
SpvBuiltInHitMicroTriangleVertexBarycentricsNV = 5344,
SpvBuiltInIncomingRayFlagsKHR = 5351,
SpvBuiltInIncomingRayFlagsNV = 5351,
SpvBuiltInRayGeometryIndexKHR = 5352,
SpvBuiltInHitIsSphereNV = 5359,
SpvBuiltInHitIsLSSNV = 5360,
SpvBuiltInHitSpherePositionNV = 5361,
SpvBuiltInWarpsPerSMNV = 5374,
SpvBuiltInSMCountNV = 5375,
SpvBuiltInWarpIDNV = 5376,
SpvBuiltInSMIDNV = 5377,
SpvBuiltInHitLSSPositionsNV = 5396,
SpvBuiltInHitKindFrontFacingMicroTriangleNV = 5405,
SpvBuiltInHitKindBackFacingMicroTriangleNV = 5406,
SpvBuiltInHitSphereRadiusNV = 5420,
SpvBuiltInHitLSSRadiiNV = 5421,
SpvBuiltInClusterIDNV = 5436,
SpvBuiltInCullMaskKHR = 6021,
SpvBuiltInMax = 0x7fffffff
} SpvBuiltIn;
typedef enum SpvDecoration_ {
SpvDecorationRelaxedPrecision = 0,
SpvDecorationSpecId = 1,
SpvDecorationBlock = 2,
SpvDecorationBufferBlock = 3,
SpvDecorationRowMajor = 4,
SpvDecorationColMajor = 5,
SpvDecorationArrayStride = 6,
SpvDecorationMatrixStride = 7,
SpvDecorationGLSLShared = 8,
SpvDecorationGLSLPacked = 9,
SpvDecorationCPacked = 10,
SpvDecorationBuiltIn = 11,
SpvDecorationNoPerspective = 13,
SpvDecorationFlat = 14,
SpvDecorationPatch = 15,
SpvDecorationCentroid = 16,
SpvDecorationSample = 17,
SpvDecorationInvariant = 18,
SpvDecorationRestrict = 19,
SpvDecorationAliased = 20,
SpvDecorationVolatile = 21,
SpvDecorationConstant = 22,
SpvDecorationCoherent = 23,
SpvDecorationNonWritable = 24,
SpvDecorationNonReadable = 25,
SpvDecorationUniform = 26,
SpvDecorationUniformId = 27,
SpvDecorationSaturatedConversion = 28,
SpvDecorationStream = 29,
SpvDecorationLocation = 30,
SpvDecorationComponent = 31,
SpvDecorationIndex = 32,
SpvDecorationBinding = 33,
SpvDecorationDescriptorSet = 34,
SpvDecorationOffset = 35,
SpvDecorationXfbBuffer = 36,
SpvDecorationXfbStride = 37,
SpvDecorationFuncParamAttr = 38,
SpvDecorationFPRoundingMode = 39,
SpvDecorationFPFastMathMode = 40,
SpvDecorationLinkageAttributes = 41,
SpvDecorationNoContraction = 42,
SpvDecorationInputAttachmentIndex = 43,
SpvDecorationAlignment = 44,
SpvDecorationMaxByteOffset = 45,
SpvDecorationAlignmentId = 46,
SpvDecorationMaxByteOffsetId = 47,
SpvDecorationSaturatedToLargestFloat8NormalConversionEXT = 4216,
SpvDecorationNoSignedWrap = 4469,
SpvDecorationNoUnsignedWrap = 4470,
SpvDecorationWeightTextureQCOM = 4487,
SpvDecorationBlockMatchTextureQCOM = 4488,
SpvDecorationBlockMatchSamplerQCOM = 4499,
SpvDecorationExplicitInterpAMD = 4999,
SpvDecorationNodeSharesPayloadLimitsWithAMDX = 5019,
SpvDecorationNodeMaxPayloadsAMDX = 5020,
SpvDecorationTrackFinishWritingAMDX = 5078,
SpvDecorationPayloadNodeNameAMDX = 5091,
SpvDecorationPayloadNodeBaseIndexAMDX = 5098,
SpvDecorationPayloadNodeSparseArrayAMDX = 5099,
SpvDecorationPayloadNodeArraySizeAMDX = 5100,
SpvDecorationPayloadDispatchIndirectAMDX = 5105,
SpvDecorationArrayStrideIdEXT = 5124,
SpvDecorationOffsetIdEXT = 5125,
SpvDecorationUTFEncodedKHR = 5145,
SpvDecorationOverrideCoverageNV = 5248,
SpvDecorationPassthroughNV = 5250,
SpvDecorationViewportRelativeNV = 5252,
SpvDecorationSecondaryViewportRelativeNV = 5256,
SpvDecorationPerPrimitiveEXT = 5271,
SpvDecorationPerPrimitiveNV = 5271,
SpvDecorationPerViewNV = 5272,
SpvDecorationPerTaskNV = 5273,
SpvDecorationPerVertexKHR = 5285,
SpvDecorationPerVertexNV = 5285,
SpvDecorationNonUniform = 5300,
SpvDecorationNonUniformEXT = 5300,
SpvDecorationRestrictPointer = 5355,
SpvDecorationRestrictPointerEXT = 5355,
SpvDecorationAliasedPointer = 5356,
SpvDecorationAliasedPointerEXT = 5356,
SpvDecorationMemberOffsetNV = 5358,
SpvDecorationHitObjectShaderRecordBufferNV = 5386,
SpvDecorationHitObjectShaderRecordBufferEXT = 5389,
SpvDecorationBankNV = 5397,
SpvDecorationBindlessSamplerNV = 5398,
SpvDecorationBindlessImageNV = 5399,
SpvDecorationBoundSamplerNV = 5400,
SpvDecorationBoundImageNV = 5401,
SpvDecorationSIMTCallINTEL = 5599,
SpvDecorationReferencedIndirectlyINTEL = 5602,
SpvDecorationClobberINTEL = 5607,
SpvDecorationSideEffectsINTEL = 5608,
SpvDecorationVectorComputeVariableINTEL = 5624,
SpvDecorationFuncParamIOKindINTEL = 5625,
SpvDecorationVectorComputeFunctionINTEL = 5626,
SpvDecorationStackCallINTEL = 5627,
SpvDecorationGlobalVariableOffsetINTEL = 5628,
SpvDecorationCounterBuffer = 5634,
SpvDecorationHlslCounterBufferGOOGLE = 5634,
SpvDecorationHlslSemanticGOOGLE = 5635,
SpvDecorationUserSemantic = 5635,
SpvDecorationUserTypeGOOGLE = 5636,
SpvDecorationFunctionRoundingModeINTEL = 5822,
SpvDecorationFunctionDenormModeINTEL = 5823,
SpvDecorationRegisterALTERA = 5825,
SpvDecorationRegisterINTEL = 5825,
SpvDecorationMemoryALTERA = 5826,
SpvDecorationMemoryINTEL = 5826,
SpvDecorationNumbanksALTERA = 5827,
SpvDecorationNumbanksINTEL = 5827,
SpvDecorationBankwidthALTERA = 5828,
SpvDecorationBankwidthINTEL = 5828,
SpvDecorationMaxPrivateCopiesALTERA = 5829,
SpvDecorationMaxPrivateCopiesINTEL = 5829,
SpvDecorationSinglepumpALTERA = 5830,
SpvDecorationSinglepumpINTEL = 5830,
SpvDecorationDoublepumpALTERA = 5831,
SpvDecorationDoublepumpINTEL = 5831,
SpvDecorationMaxReplicatesALTERA = 5832,
SpvDecorationMaxReplicatesINTEL = 5832,
SpvDecorationSimpleDualPortALTERA = 5833,
SpvDecorationSimpleDualPortINTEL = 5833,
SpvDecorationMergeALTERA = 5834,
SpvDecorationMergeINTEL = 5834,
SpvDecorationBankBitsALTERA = 5835,
SpvDecorationBankBitsINTEL = 5835,
SpvDecorationForcePow2DepthALTERA = 5836,
SpvDecorationForcePow2DepthINTEL = 5836,
SpvDecorationStridesizeALTERA = 5883,
SpvDecorationStridesizeINTEL = 5883,
SpvDecorationWordsizeALTERA = 5884,
SpvDecorationWordsizeINTEL = 5884,
SpvDecorationTrueDualPortALTERA = 5885,
SpvDecorationTrueDualPortINTEL = 5885,
SpvDecorationBurstCoalesceALTERA = 5899,
SpvDecorationBurstCoalesceINTEL = 5899,
SpvDecorationCacheSizeALTERA = 5900,
SpvDecorationCacheSizeINTEL = 5900,
SpvDecorationDontStaticallyCoalesceALTERA = 5901,
SpvDecorationDontStaticallyCoalesceINTEL = 5901,
SpvDecorationPrefetchALTERA = 5902,
SpvDecorationPrefetchINTEL = 5902,
SpvDecorationStallEnableALTERA = 5905,
SpvDecorationStallEnableINTEL = 5905,
SpvDecorationFuseLoopsInFunctionALTERA = 5907,
SpvDecorationFuseLoopsInFunctionINTEL = 5907,
SpvDecorationMathOpDSPModeALTERA = 5909,
SpvDecorationMathOpDSPModeINTEL = 5909,
SpvDecorationAliasScopeINTEL = 5914,
SpvDecorationNoAliasINTEL = 5915,
SpvDecorationInitiationIntervalALTERA = 5917,
SpvDecorationInitiationIntervalINTEL = 5917,
SpvDecorationMaxConcurrencyALTERA = 5918,
SpvDecorationMaxConcurrencyINTEL = 5918,
SpvDecorationPipelineEnableALTERA = 5919,
SpvDecorationPipelineEnableINTEL = 5919,
SpvDecorationBufferLocationALTERA = 5921,
SpvDecorationBufferLocationINTEL = 5921,
SpvDecorationIOPipeStorageALTERA = 5944,
SpvDecorationIOPipeStorageINTEL = 5944,
SpvDecorationFunctionFloatingPointModeINTEL = 6080,
SpvDecorationSingleElementVectorINTEL = 6085,
SpvDecorationVectorComputeCallableFunctionINTEL = 6087,
SpvDecorationMediaBlockIOINTEL = 6140,
SpvDecorationStallFreeALTERA = 6151,
SpvDecorationStallFreeINTEL = 6151,
SpvDecorationFPMaxErrorDecorationINTEL = 6170,
SpvDecorationLatencyControlLabelALTERA = 6172,
SpvDecorationLatencyControlLabelINTEL = 6172,
SpvDecorationLatencyControlConstraintALTERA = 6173,
SpvDecorationLatencyControlConstraintINTEL = 6173,
SpvDecorationConduitKernelArgumentALTERA = 6175,
SpvDecorationConduitKernelArgumentINTEL = 6175,
SpvDecorationRegisterMapKernelArgumentALTERA = 6176,
SpvDecorationRegisterMapKernelArgumentINTEL = 6176,
SpvDecorationMMHostInterfaceAddressWidthALTERA = 6177,
SpvDecorationMMHostInterfaceAddressWidthINTEL = 6177,
SpvDecorationMMHostInterfaceDataWidthALTERA = 6178,
SpvDecorationMMHostInterfaceDataWidthINTEL = 6178,
SpvDecorationMMHostInterfaceLatencyALTERA = 6179,
SpvDecorationMMHostInterfaceLatencyINTEL = 6179,
SpvDecorationMMHostInterfaceReadWriteModeALTERA = 6180,
SpvDecorationMMHostInterfaceReadWriteModeINTEL = 6180,
SpvDecorationMMHostInterfaceMaxBurstALTERA = 6181,
SpvDecorationMMHostInterfaceMaxBurstINTEL = 6181,
SpvDecorationMMHostInterfaceWaitRequestALTERA = 6182,
SpvDecorationMMHostInterfaceWaitRequestINTEL = 6182,
SpvDecorationStableKernelArgumentALTERA = 6183,
SpvDecorationStableKernelArgumentINTEL = 6183,
SpvDecorationHostAccessINTEL = 6188,
SpvDecorationInitModeALTERA = 6190,
SpvDecorationInitModeINTEL = 6190,
SpvDecorationImplementInRegisterMapALTERA = 6191,
SpvDecorationImplementInRegisterMapINTEL = 6191,
SpvDecorationConditionalINTEL = 6247,
SpvDecorationCacheControlLoadINTEL = 6442,
SpvDecorationCacheControlStoreINTEL = 6443,
SpvDecorationMax = 0x7fffffff
} SpvDecoration;
typedef enum SpvDim_ {
_1D = 0,
_2D = 1,
_3D = 2,
Cube = 3,
Rect = 4,
Buffer = 5,
SubpassData = 6,
TileImageDataEXT = 4173,
Max = 0x7fffffff
} SpvDim;
#endif /* spirv_H */
typedef int SpvBool;
typedef unsigned char SpvByte;
typedef unsigned long SpvWord;
typedef unsigned long SpvSize;
typedef enum
{
SPV_RESULT_SUCCESS = 0,
SPV_RESULT_BARRIER = 1,
SPV_RESULT_KILLED = 2,
SPV_RESULT_DIVISION_BY_ZERO = -1,
SPV_RESULT_INVALID_ENTRY_POINT = -2,
SPV_RESULT_INVALID_SPIRV = -3,
SPV_RESULT_INVALID_VALUE_TYPE = -4,
SPV_RESULT_NOT_FOUND = -5,
SPV_RESULT_OUT_OF_MEMORY = -6,
SPV_RESULT_OUT_OF_BOUNDS = -7,
SPV_RESULT_TODO = -8,
SPV_RESULT_UNREACHABLE = -9,
SPV_RESULT_UNSUPPORTED_SPIRV = -10,
SPV_RESULT_UNSUPPORTED_EXTENSION = -11,
SPV_RESULT_UNSUPPORTED_ENDIANNESS = -12,
SPV_RESULT_INVALID_MAGIC = -13,
SPV_RESULT_UNKNOWN = -14
} SpvResult;
typedef struct
{
SpvBool use_simd_vectors_specializations;
} SpvModuleOptions;
typedef struct
{
SpvWord local_size_x;
SpvWord local_size_y;
SpvWord local_size_z;
SpvWord geometry_invocations;
SpvWord geometry_output_count;
SpvWord geometry_input;
SpvWord geometry_output;
SpvBool needs_derivatives;
SpvBool has_control_barriers;
} SpvModuleReflectionInfos;
typedef struct
{
SpvWord id;
SpvSize offset;
SpvSize size;
} SpvRuntimeSpecializationEntry;
typedef enum
{
SPV_LOCATION_INPUT = 0,
SPV_LOCATION_OUTPUT = 1
} SpvLocationType;
typedef enum
{
SPV_ENTRY_POINT_COMPLETED = 0,
SPV_ENTRY_POINT_BARRIER = 1
} SpvEntryPointStatus;
typedef enum
{
SPV_PRIMITIVE_BOOL = 0,
SPV_PRIMITIVE_FLOAT = 1,
SPV_PRIMITIVE_SINT = 2,
SPV_PRIMITIVE_UINT = 3
} SpvPrimitiveType;
typedef struct
{
float x;
float y;
float z;
float w;
} SpvVec4f;
typedef struct
{
unsigned int x;
unsigned int y;
unsigned int z;
unsigned int w;
} SpvVec4u;
typedef struct
{
int x;
int y;
int z;
} SpvImageOffset;
typedef struct
{
SpvVec4f dx;
SpvVec4f dy;
} SpvImageDerivatives;
typedef struct
{
void* driver_image;
SpvDim dim;
int x;
int y;
int z;
int lod;
SpvBool has_lod;
} SpvReadImageInfo;
typedef struct
{
void* driver_image;
SpvDim dim;
int x;
int y;
int z;
} SpvWriteImageInfo;
typedef struct
{
void* driver_image;
void* driver_sampler;
SpvDim dim;
int x;
int y;
int z;
float lod;
SpvBool has_lod;
SpvImageOffset offset;
} SpvSampleImageInfo;
typedef struct
{
void* driver_image;
SpvDim dim;
SpvBool arrayed;
int lod;
SpvBool has_lod;
} SpvQueryImageInfo;
typedef struct
{
void* driver_image;
void* driver_sampler;
SpvDim dim;
SpvImageDerivatives derivatives;
} SpvQueryImageLodInfo;
typedef SpvResult (*SpvReadImageFloat4_PFN)(SpvReadImageInfo info, SpvVec4f* dst);
typedef SpvResult (*SpvReadImageInt4_PFN)(SpvReadImageInfo info, SpvVec4u* dst);
typedef SpvResult (*SpvWriteImageFloat4_PFN)(SpvWriteImageInfo info, SpvVec4f src);
typedef SpvResult (*SpvWriteImageInt4_PFN)(SpvWriteImageInfo info, SpvVec4u src);
typedef SpvResult (*SpvSampleImageFloat4_PFN)(SpvSampleImageInfo info, SpvVec4f* dst);
typedef SpvResult (*SpvSampleImageInt4_PFN)(SpvSampleImageInfo info, SpvVec4u* dst);
typedef SpvResult (*SpvSampleImageDref_PFN)(SpvSampleImageInfo info, float dref, float* dst);
typedef SpvResult (*SpvQueryImageSize_PFN)(SpvQueryImageInfo info, SpvVec4u* dst);
typedef SpvResult (*SpvQueryImageLevels_PFN)(void* driver_image, unsigned long* dst);
typedef SpvResult (*SpvQueryImageSamples_PFN)(void* driver_image, unsigned long* dst);
typedef SpvResult (*SpvQueryImageLod_PFN)(SpvQueryImageLodInfo info, SpvVec4f* dst);
typedef struct
{
SpvReadImageFloat4_PFN SpvReadImageFloat4;
SpvReadImageInt4_PFN SpvReadImageInt4;
SpvWriteImageFloat4_PFN SpvWriteImageFloat4;
SpvWriteImageInt4_PFN SpvWriteImageInt4;
SpvSampleImageFloat4_PFN SpvSampleImageFloat4;
SpvSampleImageInt4_PFN SpvSampleImageInt4;
SpvSampleImageDref_PFN SpvSampleImageDref;
SpvQueryImageSize_PFN SpvQueryImageSize;
SpvQueryImageLevels_PFN SpvQueryImageLevels;
SpvQueryImageSamples_PFN SpvQueryImageSamples;
SpvQueryImageLod_PFN SpvQueryImageLod;
} SpvImageAPI;
typedef void* SpvModule;
typedef void* SpvRuntime;
SPV_API SpvResult SpvInitModule(SpvModule* module, const SpvWord* source, SpvSize source_len, SpvModuleOptions options);
SPV_API void SpvDeinitModule(SpvModule module);
SPV_API SpvModuleReflectionInfos SpvModuleGetReflectionInfos(SpvModule module);
SPV_API SpvResult SpvModuleGetBindingResult(SpvModule module, SpvWord set, SpvWord binding, SpvWord* result);
SPV_API SpvResult SpvInitRuntime(SpvRuntime* runtime, SpvModule module, SpvImageAPI image_api);
SPV_API SpvResult SpvInitRuntimeFrom(SpvRuntime* runtime, SpvRuntime other, SpvImageAPI image_api);
SPV_API void SpvDeinitRuntime(SpvRuntime runtime);
SPV_API SpvResult SpvFlushDescriptorSets(SpvRuntime runtime);
SPV_API SpvResult SpvAddSpecializationInfo(SpvRuntime runtime, SpvRuntimeSpecializationEntry entry, const SpvByte* data, SpvSize data_size);
SPV_API SpvResult SpvCopySpecializationConstantsFrom(SpvRuntime runtime, SpvRuntime other);
SPV_API SpvResult SpvSetDerivativeFromMemory(SpvRuntime runtime, SpvWord result, const SpvByte* dx, SpvSize dx_size, const SpvByte* dy, SpvSize dy_size);
SPV_API void SpvClearDerivative(SpvRuntime runtime, SpvWord result);
SPV_API SpvResult SpvCopyDerivative(SpvRuntime runtime, SpvWord dst, SpvWord src);
SPV_API SpvResult SpvPopulatePushConstants(SpvRuntime runtime, const SpvByte* data, SpvSize data_size);
SPV_API SpvResult SpvGetResultByName(SpvRuntime runtime, const char* name, SpvWord* result);
SPV_API SpvResult SpvGetResultLocation(SpvRuntime runtime, SpvWord location, SpvLocationType type, SpvWord* result);
SPV_API SpvResult SpvGetResultByLocation(SpvRuntime runtime, SpvWord location, SpvLocationType type, SpvWord* result);
SPV_API SpvResult SpvGetResultByLocationComponent(SpvRuntime runtime, SpvWord location, SpvWord component, SpvLocationType type, SpvWord* result);
SPV_API SpvResult SpvGetEntryPointByName(SpvRuntime runtime, const char* name, SpvWord* result);
SPV_API SpvResult SpvGetResultMemorySize(SpvRuntime runtime, SpvWord result, SpvSize* size);
SPV_API SpvResult SpvGetInputLocationMemorySize(SpvRuntime runtime, SpvWord location, SpvSize* size);
SPV_API SpvResult SpvGetResultPrimitiveType(SpvRuntime runtime, SpvWord result, SpvPrimitiveType* primitive_type);
SPV_API SpvBool SpvHasResultDecoration(SpvRuntime runtime, SpvWord result, SpvDecoration decoration);
SPV_API SpvResult SpvCallEntryPoint(SpvRuntime runtime, SpvWord entry_point_index);
SPV_API SpvResult SpvBeginEntryPoint(SpvRuntime runtime, SpvWord entry_point_index, SpvEntryPointStatus* status);
SPV_API SpvResult SpvContinueEntryPoint(SpvRuntime runtime, SpvEntryPointStatus* status);
SPV_API void SpvResetInvocation(SpvRuntime runtime);
SPV_API SpvResult SpvReadOutput(SpvRuntime runtime, SpvByte* output, SpvSize output_size, SpvWord result);
SPV_API SpvResult SpvReadBuiltIn(SpvRuntime runtime, SpvByte* output, SpvSize output_size, SpvBuiltIn builtin);
SPV_API SpvResult SpvWriteInput(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvWord result);
SPV_API SpvResult SpvWriteInputLocation(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvWord location);
SPV_API SpvResult SpvWriteBuiltIn(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvBuiltIn builtin);
SPV_API SpvResult SpvWriteDescriptorSet(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvWord set, SpvWord binding, SpvWord descriptor_index);
#ifdef __cplusplus
}
#endif
#endif /* SPIRV_INTERPRETER_H */
+30
View File
@@ -0,0 +1,30 @@
pub const spv = @import("spv");
pub const SpvCBool = c_int;
pub const SpvCWord = c_ulong;
pub const SpvCSize = c_ulong;
pub const Result = enum(c_int) {
Success = 0,
Barrier = 1,
Killed = 2,
DivisionByZero = -1,
InvalidEntryPoint = -2,
InvalidSpirV = -3,
InvalidValueType = -4,
NotFound = -5,
OutOfMemory = -6,
OutOfBounds = -7,
ToDo = -8,
Unreachable = -9,
UnsupportedSpirV = -10,
UnsupportedExtension = -11,
UnsupportedEndianness = -12,
InvalidMagic = -13,
Unknown = -14,
};
comptime {
_ = @import("module.zig");
_ = @import("runtime.zig");
}
+75
View File
@@ -0,0 +1,75 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const spv = ffi.spv;
const Options = extern struct {
use_simd_vectors_specializations: ffi.SpvCBool,
};
const ReflectionInfos = extern struct {
local_size_x: ffi.SpvCWord,
local_size_y: ffi.SpvCWord,
local_size_z: ffi.SpvCWord,
geometry_invocations: ffi.SpvCWord,
geometry_output_count: ffi.SpvCWord,
geometry_input: ffi.SpvCWord,
geometry_output: ffi.SpvCWord,
needs_derivatives: ffi.SpvCBool,
has_control_barriers: ffi.SpvCBool,
};
fn toCResult(err: spv.Module.ModuleError) ffi.Result {
return switch (err) {
spv.Module.ModuleError.InvalidSpirV => ffi.Result.InvalidSpirV,
spv.Module.ModuleError.InvalidMagic => ffi.Result.InvalidMagic,
spv.Module.ModuleError.UnsupportedEndianness => ffi.Result.UnsupportedEndianness,
spv.Module.ModuleError.UnsupportedExtension => ffi.Result.UnsupportedExtension,
spv.Module.ModuleError.OutOfMemory => ffi.Result.OutOfMemory,
};
}
export fn SpvInitModule(module: **spv.Module, source: [*]const ffi.SpvCWord, source_len: ffi.SpvCSize, options: Options) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
module.* = allocator.create(spv.Module) catch return .OutOfMemory;
const cast_source: []const u32 = @as([*]const u32, @ptrCast(source[0..source_len]))[0..source_len];
module.*.* = spv.Module.init(
allocator,
cast_source[0..source_len],
.{
.use_simd_vectors_specializations = if (options.use_simd_vectors_specializations == 0) false else true,
},
) catch |err| {
allocator.destroy(module.*);
return toCResult(err);
};
return .Success;
}
export fn SpvDeinitModule(module: *spv.Module) callconv(.c) void {
const allocator = std.heap.c_allocator;
module.deinit(allocator);
allocator.destroy(module);
}
export fn SpvModuleGetReflectionInfos(module: *spv.Module) callconv(.c) ReflectionInfos {
return .{
.local_size_x = module.reflection_infos.local_size_x,
.local_size_y = module.reflection_infos.local_size_y,
.local_size_z = module.reflection_infos.local_size_z,
.geometry_invocations = module.reflection_infos.geometry_invocations,
.geometry_output_count = module.reflection_infos.geometry_output_count,
.geometry_input = module.reflection_infos.geometry_input,
.geometry_output = module.reflection_infos.geometry_output,
.needs_derivatives = if (module.reflection_infos.needs_derivatives) 1 else 0,
.has_control_barriers = if (module.reflection_infos.has_control_barriers) 1 else 0,
};
}
export fn SpvModuleGetBindingResult(module: *const spv.Module, set: ffi.SpvCWord, binding: ffi.SpvCWord, result: *ffi.SpvCWord) callconv(.c) ffi.Result {
result.* = module.getBindingResult(@intCast(set), @intCast(binding)) orelse return .NotFound;
return .Success;
}
+628
View File
@@ -0,0 +1,628 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const spv = ffi.spv;
const CSpecializationEntry = extern struct {
id: spv.SpvWord,
offset: c_ulong,
size: c_ulong,
};
const LocationType = enum(c_int) {
input = 0,
output = 1,
};
const EntryPointStatus = enum(c_int) {
completed = 0,
barrier = 1,
};
const PrimitiveType = enum(c_int) {
bool = 0,
float = 1,
sint = 2,
uint = 3,
};
const Vec4f = extern struct {
x: f32,
y: f32,
z: f32,
w: f32,
};
const Vec4u = extern struct {
x: c_uint,
y: c_uint,
z: c_uint,
w: c_uint,
};
const ImageOffset = extern struct {
x: c_int,
y: c_int,
z: c_int,
};
const ImageDerivatives = extern struct {
dx: Vec4f,
dy: Vec4f,
};
const ReadImageInfo = extern struct {
driver_image: ?*anyopaque,
dim: spv.spv.SpvDim,
x: c_int,
y: c_int,
z: c_int,
lod: c_int,
has_lod: ffi.SpvCBool,
};
const WriteImageInfo = extern struct {
driver_image: ?*anyopaque,
dim: spv.spv.SpvDim,
x: c_int,
y: c_int,
z: c_int,
};
const SampleImageInfo = extern struct {
driver_image: ?*anyopaque,
driver_sampler: ?*anyopaque,
dim: spv.spv.SpvDim,
x: f32,
y: f32,
z: f32,
lod: f32,
has_lod: ffi.SpvCBool,
offset: ImageOffset,
};
const QueryImageInfo = extern struct {
driver_image: ?*anyopaque,
dim: spv.spv.SpvDim,
arrayed: ffi.SpvCBool,
lod: c_int,
has_lod: ffi.SpvCBool,
};
const QueryImageLodInfo = extern struct {
driver_image: ?*anyopaque,
driver_sampler: ?*anyopaque,
dim: spv.spv.SpvDim,
derivatives: ImageDerivatives,
};
const readImageFloat4_PFN = *const fn (info: ReadImageInfo, dst: *Vec4f) callconv(.c) ffi.Result;
const readImageInt4_PFN = *const fn (info: ReadImageInfo, dst: *Vec4u) callconv(.c) ffi.Result;
const writeImageFloat4_PFN = *const fn (info: WriteImageInfo, src: Vec4f) callconv(.c) ffi.Result;
const writeImageInt4_PFN = *const fn (info: WriteImageInfo, src: Vec4u) callconv(.c) ffi.Result;
const sampleImageFloat4_PFN = *const fn (info: SampleImageInfo, dst: *Vec4f) callconv(.c) ffi.Result;
const sampleImageInt4_PFN = *const fn (info: SampleImageInfo, dst: *Vec4u) callconv(.c) ffi.Result;
const sampleImageDref_PFN = *const fn (info: SampleImageInfo, dref: f32, dst: *f32) callconv(.c) ffi.Result;
const queryImageSize_PFN = *const fn (info: QueryImageInfo, dst: *Vec4u) callconv(.c) ffi.Result;
const queryImageLevels_PFN = *const fn (driver_image: ?*anyopaque, dst: *ffi.SpvCSize) callconv(.c) ffi.Result;
const queryImageSamples_PFN = *const fn (driver_image: ?*anyopaque, dst: *ffi.SpvCSize) callconv(.c) ffi.Result;
const queryImageLod_PFN = *const fn (info: QueryImageLodInfo, dst: *Vec4f) callconv(.c) ffi.Result;
const ImageAPI = extern struct {
readImageFloat4: readImageFloat4_PFN,
readImageInt4: readImageInt4_PFN,
writeImageFloat4: writeImageFloat4_PFN,
writeImageInt4: writeImageInt4_PFN,
sampleImageFloat4: sampleImageFloat4_PFN,
sampleImageInt4: sampleImageInt4_PFN,
sampleImageDref: sampleImageDref_PFN,
queryImageSize: queryImageSize_PFN,
queryImageLevels: queryImageLevels_PFN,
queryImageSamples: queryImageSamples_PFN,
queryImageLod: queryImageLod_PFN,
};
fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result {
return switch (err) {
spv.Runtime.RuntimeError.Barrier => ffi.Result.Barrier,
spv.Runtime.RuntimeError.DivisionByZero => ffi.Result.DivisionByZero,
spv.Runtime.RuntimeError.InvalidEntryPoint => ffi.Result.InvalidEntryPoint,
spv.Runtime.RuntimeError.InvalidSpirV => ffi.Result.InvalidSpirV,
spv.Runtime.RuntimeError.InvalidValueType => ffi.Result.InvalidValueType,
spv.Runtime.RuntimeError.Killed => ffi.Result.Killed,
spv.Runtime.RuntimeError.NotFound => ffi.Result.NotFound,
spv.Runtime.RuntimeError.OutOfBounds => ffi.Result.OutOfBounds,
spv.Runtime.RuntimeError.OutOfMemory => ffi.Result.OutOfMemory,
spv.Runtime.RuntimeError.ToDo => ffi.Result.ToDo,
spv.Runtime.RuntimeError.Unknown => ffi.Result.Unknown,
spv.Runtime.RuntimeError.Unreachable => ffi.Result.Unreachable,
spv.Runtime.RuntimeError.UnsupportedExtension => ffi.Result.UnsupportedExtension,
spv.Runtime.RuntimeError.UnsupportedSpirV => ffi.Result.UnsupportedSpirV,
};
}
fn fromCResult(res: ffi.Result) spv.Runtime.RuntimeError!void {
return switch (res) {
ffi.Result.Barrier => spv.Runtime.RuntimeError.Barrier,
ffi.Result.DivisionByZero => spv.Runtime.RuntimeError.DivisionByZero,
ffi.Result.InvalidEntryPoint => spv.Runtime.RuntimeError.InvalidEntryPoint,
ffi.Result.InvalidSpirV => spv.Runtime.RuntimeError.InvalidSpirV,
ffi.Result.InvalidValueType => spv.Runtime.RuntimeError.InvalidValueType,
ffi.Result.Killed => spv.Runtime.RuntimeError.Killed,
ffi.Result.NotFound => spv.Runtime.RuntimeError.NotFound,
ffi.Result.OutOfBounds => spv.Runtime.RuntimeError.OutOfBounds,
ffi.Result.OutOfMemory => spv.Runtime.RuntimeError.OutOfMemory,
ffi.Result.ToDo => spv.Runtime.RuntimeError.ToDo,
ffi.Result.Unknown => spv.Runtime.RuntimeError.Unknown,
ffi.Result.Unreachable => spv.Runtime.RuntimeError.Unreachable,
ffi.Result.UnsupportedExtension => spv.Runtime.RuntimeError.UnsupportedExtension,
ffi.Result.UnsupportedSpirV => spv.Runtime.RuntimeError.UnsupportedSpirV,
else => {},
};
}
fn toCEntryPointStatus(status: spv.Runtime.EntryPointStatus) EntryPointStatus {
return switch (status) {
.completed => .completed,
.barrier => .barrier,
};
}
/// Hacky wrapper
const ImageAPIBridge = struct {
threadlocal var current_image_api: ?*const ImageAPI = null;
fn getImageAPI() spv.Runtime.RuntimeError!*const ImageAPI {
return current_image_api orelse spv.Runtime.RuntimeError.Unknown;
}
fn toCVec4f(value: spv.Runtime.Vec4(f32)) Vec4f {
return .{ .x = value.x, .y = value.y, .z = value.z, .w = value.w };
}
fn toCVec4u(value: spv.Runtime.Vec4(u32)) Vec4u {
return .{ .x = value.x, .y = value.y, .z = value.z, .w = value.w };
}
fn readImageInfo(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) ReadImageInfo {
return .{
.driver_image = driver_image,
.dim = dim,
.x = @intCast(x),
.y = @intCast(y),
.z = @intCast(z),
.lod = @intCast(lod orelse 0),
.has_lod = if (lod == null) 0 else 1,
};
}
fn writeImageInfo(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32) WriteImageInfo {
return .{
.driver_image = driver_image,
.dim = dim,
.x = @intCast(x),
.y = @intCast(y),
.z = @intCast(z),
};
}
fn sampleImageInfo(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SampleImageInfo {
return .{
.driver_image = driver_image,
.driver_sampler = driver_sampler,
.dim = dim,
.x = x,
.y = y,
.z = z,
.lod = lod orelse 0.0,
.has_lod = if (lod == null) 0 else 1,
.offset = toCImageOffset(offset),
};
}
fn readImageFloat4(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
const image_api = try getImageAPI();
var dst: Vec4f = undefined;
const result = image_api.readImageFloat4(readImageInfo(driver_image, dim, x, y, z, lod), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn readImageInt4(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
const image_api = try getImageAPI();
var dst: Vec4u = undefined;
const result = image_api.readImageInt4(readImageInfo(driver_image, dim, x, y, z, lod), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn writeImageFloat4(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) spv.Runtime.RuntimeError!void {
const image_api = try getImageAPI();
const result = image_api.writeImageFloat4(writeImageInfo(driver_image, dim, x, y, z), toCVec4f(pixel));
try fromCResult(result);
}
fn writeImageInt4(driver_image: *anyopaque, dim: spv.spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) spv.Runtime.RuntimeError!void {
const image_api = try getImageAPI();
const result = image_api.writeImageInt4(writeImageInfo(driver_image, dim, x, y, z), toCVec4u(pixel));
try fromCResult(result);
}
fn toCImageOffset(offset: spv.Runtime.ImageOffset) ImageOffset {
return .{
.x = @intCast(offset.x),
.y = @intCast(offset.y),
.z = @intCast(offset.z),
};
}
fn sampleImageFloat4(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
const image_api = try getImageAPI();
var dst: Vec4f = undefined;
const result = image_api.sampleImageFloat4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn sampleImageInt4(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
const image_api = try getImageAPI();
var dst: Vec4u = undefined;
const result = image_api.sampleImageInt4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 {
const image_api = try getImageAPI();
var dst: f32 = undefined;
const result = image_api.sampleImageDref(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), dref, &dst);
try fromCResult(result);
return dst;
}
fn queryImageSize(driver_image: *anyopaque, dim: spv.spv.SpvDim, arrayed: bool, lod: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
const image_api = try getImageAPI();
var dst: Vec4u = undefined;
const result = image_api.queryImageSize(.{
.driver_image = driver_image,
.dim = dim,
.arrayed = if (arrayed) 1 else 0,
.lod = @intCast(lod orelse 0),
.has_lod = if (lod == null) 0 else 1,
}, &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn queryImageLevels(driver_image: *anyopaque) spv.Runtime.RuntimeError!u32 {
const image_api = try getImageAPI();
var dst: ffi.SpvCSize = undefined;
const result = image_api.queryImageLevels(driver_image, &dst);
try fromCResult(result);
return @intCast(dst);
}
fn queryImageSamples(driver_image: *anyopaque) spv.Runtime.RuntimeError!u32 {
const image_api = try getImageAPI();
var dst: ffi.SpvCSize = undefined;
const result = image_api.queryImageSamples(driver_image, &dst);
try fromCResult(result);
return @intCast(dst);
}
fn queryImageLod(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, derivatives: spv.Runtime.ImageDerivatives) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
const image_api = try getImageAPI();
var dst: Vec4f = undefined;
const result = image_api.queryImageLod(.{
.driver_image = driver_image,
.driver_sampler = driver_sampler,
.dim = dim,
.derivatives = .{
.dx = toCVec4f(derivatives.dx),
.dy = toCVec4f(derivatives.dy),
},
}, &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
};
const RuntimeWrapper = struct {
rt: spv.Runtime,
image_api: ImageAPI,
};
export fn SpvInitRuntime(rt: **RuntimeWrapper, module: *spv.Module, image_api: ImageAPI) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.* = allocator.create(RuntimeWrapper) catch return .OutOfMemory;
rt.*.image_api = image_api;
rt.*.rt = spv.Runtime.init(
allocator,
module,
.{
.readImageFloat4 = ImageAPIBridge.readImageFloat4,
.readImageInt4 = ImageAPIBridge.readImageInt4,
.writeImageFloat4 = ImageAPIBridge.writeImageFloat4,
.writeImageInt4 = ImageAPIBridge.writeImageInt4,
.sampleImageFloat4 = ImageAPIBridge.sampleImageFloat4,
.sampleImageInt4 = ImageAPIBridge.sampleImageInt4,
.sampleImageDref = ImageAPIBridge.sampleImageDref,
.queryImageSize = ImageAPIBridge.queryImageSize,
.queryImageLevels = ImageAPIBridge.queryImageLevels,
.queryImageSamples = ImageAPIBridge.queryImageSamples,
.queryImageLod = ImageAPIBridge.queryImageLod,
},
) catch |err| {
allocator.destroy(rt.*);
return toCResult(err);
};
return .Success;
}
export fn SpvInitRuntimeFrom(rt: **RuntimeWrapper, other: *RuntimeWrapper, image_api: ImageAPI) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.* = allocator.create(RuntimeWrapper) catch return .OutOfMemory;
rt.*.image_api = image_api;
rt.*.rt = spv.Runtime.initFrom(
allocator,
&other.rt,
.{
.readImageFloat4 = ImageAPIBridge.readImageFloat4,
.readImageInt4 = ImageAPIBridge.readImageInt4,
.writeImageFloat4 = ImageAPIBridge.writeImageFloat4,
.writeImageInt4 = ImageAPIBridge.writeImageInt4,
.sampleImageFloat4 = ImageAPIBridge.sampleImageFloat4,
.sampleImageInt4 = ImageAPIBridge.sampleImageInt4,
.sampleImageDref = ImageAPIBridge.sampleImageDref,
.queryImageSize = ImageAPIBridge.queryImageSize,
.queryImageLevels = ImageAPIBridge.queryImageLevels,
.queryImageSamples = ImageAPIBridge.queryImageSamples,
.queryImageLod = ImageAPIBridge.queryImageLod,
},
) catch |err| {
allocator.destroy(rt.*);
return toCResult(err);
};
return .Success;
}
export fn SpvDeinitRuntime(rt: *RuntimeWrapper) callconv(.c) void {
const allocator = std.heap.c_allocator;
rt.rt.deinit(allocator);
allocator.destroy(rt);
}
export fn SpvFlushDescriptorSets(rt: *RuntimeWrapper) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.flushDescriptorSets(allocator) catch |err| return toCResult(err);
return .Success;
}
export fn SpvAddSpecializationInfo(rt: *RuntimeWrapper, entry: CSpecializationEntry, data: [*]const u8, data_size: c_ulong) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.addSpecializationInfo(
allocator,
.{
.id = entry.id,
.offset = @intCast(entry.offset),
.size = @intCast(entry.size),
},
data[0..data_size],
) catch |err| return toCResult(err);
return .Success;
}
export fn SpvCopySpecializationConstantsFrom(rt: *RuntimeWrapper, other: *const RuntimeWrapper) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.copySpecializationConstantsFrom(allocator, &other.rt) catch |err| return toCResult(err);
return .Success;
}
export fn SpvSetDerivativeFromMemory(rt: *RuntimeWrapper, result: spv.SpvWord, dx: [*]const u8, dx_size: c_ulong, dy: [*]const u8, dy_size: c_ulong) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.setDerivativeFromMemory(allocator, result, dx[0..dx_size], dy[0..dy_size]) catch |err| return toCResult(err);
return .Success;
}
export fn SpvClearDerivative(rt: *RuntimeWrapper, result: spv.SpvWord) callconv(.c) void {
const allocator = std.heap.c_allocator;
rt.rt.clearDerivative(allocator, result);
}
export fn SpvCopyDerivative(rt: *RuntimeWrapper, dst: spv.SpvWord, src: spv.SpvWord) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.copyDerivative(allocator, dst, src) catch |err| return toCResult(err);
return .Success;
}
export fn SpvPopulatePushConstants(rt: *RuntimeWrapper, data: [*]const u8, data_size: c_ulong) callconv(.c) ffi.Result {
rt.rt.populatePushConstants(data[0..data_size]) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetEntryPointByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getEntryPointByName(std.mem.span(name)) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultByLocation(rt: *RuntimeWrapper, location: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getResultByLocation(location, switch (kind) {
.input => .input,
.output => .output,
}) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultLocation(rt: *RuntimeWrapper, location: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result {
return SpvGetResultByLocation(rt, location, kind, result);
}
export fn SpvGetResultByLocationComponent(rt: *RuntimeWrapper, location: spv.SpvWord, component: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getResultByLocationComponent(location, component, switch (kind) {
.input => .input,
.output => .output,
}) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getResultByName(std.mem.span(name)) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultMemorySize(rt: *RuntimeWrapper, result: spv.SpvWord, size: *c_ulong) callconv(.c) ffi.Result {
size.* = rt.rt.getResultMemorySize(result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetInputLocationMemorySize(rt: *RuntimeWrapper, location: spv.SpvWord, size: *c_ulong) callconv(.c) ffi.Result {
size.* = rt.rt.getInputLocationMemorySize(location) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultPrimitiveType(rt: *RuntimeWrapper, result: spv.SpvWord, primitive_type: *PrimitiveType) callconv(.c) ffi.Result {
primitive_type.* = switch (rt.rt.getResultPrimitiveType(result) catch |err| return toCResult(err)) {
.Bool => .bool,
.Float => .float,
.SInt => .sint,
.UInt => .uint,
};
return .Success;
}
export fn SpvHasResultDecoration(rt: *RuntimeWrapper, result: spv.SpvWord, decoration: spv.spv.SpvDecoration) callconv(.c) c_int {
return if (rt.rt.hasResultDecoration(result, decoration)) 1 else 0;
}
export fn SpvCallEntryPoint(rt: *RuntimeWrapper, entry_point: spv.SpvWord) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
// Ultra hacky
const previous_image_api = ImageAPIBridge.current_image_api;
ImageAPIBridge.current_image_api = &rt.image_api;
defer ImageAPIBridge.current_image_api = previous_image_api;
rt.rt.callEntryPoint(allocator, entry_point) catch |err| return toCResult(err);
return .Success;
}
export fn SpvBeginEntryPoint(rt: *RuntimeWrapper, entry_point: spv.SpvWord, status: *EntryPointStatus) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
const previous_image_api = ImageAPIBridge.current_image_api;
ImageAPIBridge.current_image_api = &rt.image_api;
defer ImageAPIBridge.current_image_api = previous_image_api;
status.* = toCEntryPointStatus(rt.rt.beginEntryPoint(allocator, entry_point) catch |err| return toCResult(err));
return .Success;
}
export fn SpvContinueEntryPoint(rt: *RuntimeWrapper, status: *EntryPointStatus) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
const previous_image_api = ImageAPIBridge.current_image_api;
ImageAPIBridge.current_image_api = &rt.image_api;
defer ImageAPIBridge.current_image_api = previous_image_api;
status.* = toCEntryPointStatus(rt.rt.continueEntryPoint(allocator) catch |err| return toCResult(err));
return .Success;
}
export fn SpvResetInvocation(rt: *RuntimeWrapper) callconv(.c) void {
const allocator = std.heap.c_allocator;
rt.rt.resetInvocation(allocator);
}
export fn SpvReadOutput(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.readOutput(output[0..output_size], result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvReadBuiltIn(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result {
rt.rt.readBuiltIn(output[0..output_size], builtin) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteInput(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.writeInput(input[0..input_size], result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteInputLocation(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, location: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.writeInputLocation(input[0..input_size], location) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteBuiltIn(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result {
rt.rt.writeBuiltIn(input[0..input_size], builtin) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteDescriptorSet(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, set: spv.SpvWord, binding: spv.SpvWord, descriptor_index: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.writeDescriptorSet(input[0..input_size], set, binding, descriptor_index) catch |err| return toCResult(err);
return .Success;
}
+3 -2
View File
@@ -26,6 +26,8 @@ pub fn main() !void {
var ssbo: SSBO = .{};
try rt.writeDescriptorSet(std.mem.asBytes(&ssbo), 0, 0, 0);
for (0..16) |i| {
for (0..16) |x| {
const global_invocation_indices = [3]i32{
@@ -35,12 +37,11 @@ pub fn main() !void {
};
try rt.writeBuiltIn(std.mem.asBytes(&global_invocation_indices), .GlobalInvocationId);
try rt.writeDescriptorSet(allocator, std.mem.asBytes(&ssbo), 0, 0);
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
spv.Runtime.RuntimeError.OutOfBounds => continue,
else => return err,
};
try rt.readDescriptorSet(std.mem.asBytes(&ssbo), 0, 0);
try rt.flushDescriptorSets(allocator);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ pub const GLSLstd450Version: u32 = 100;
pub const GLSLstd450Revision: u32 = 3;
pub const GLSLOp = enum(u32) {
Bad = 0,
Bad = 0, // Don't use
Round = 1,
RoundEven = 2,
Trunc = 3,
+1065 -154
View File
File diff suppressed because it is too large Load Diff
View File
+205 -94
View File
@@ -9,17 +9,15 @@ const SpvByte = spv.SpvByte;
const SpvWord = spv.SpvWord;
const SpvBool = spv.SpvBool;
const SpvBinding = spv.SpvBinding;
const Result = @import("Result.zig");
const Runtime = @import("Runtime.zig");
const Value = @import("Value.zig").Value;
const WordIterator = @import("WordIterator.zig");
const Value = Result.Value;
const Self = @This();
pub const ModuleOptions = struct {
/// Also affects matrices
use_simd_vectors_specializations: bool = true,
};
@@ -30,6 +28,12 @@ const SpvEntryPoint = struct {
globals: []SpvWord,
};
const BindingEntry = struct {
set: SpvWord,
binding: SpvWord,
result: SpvWord,
};
pub const ModuleError = error{
InvalidSpirV,
InvalidMagic,
@@ -38,6 +42,20 @@ pub const ModuleError = error{
OutOfMemory,
};
pub const ReflectionInfos = struct {
local_size_x: SpvWord,
local_size_y: SpvWord,
local_size_z: SpvWord,
geometry_invocations: SpvWord,
geometry_output_count: SpvWord,
geometry_input: SpvWord,
geometry_output: SpvWord,
needs_derivatives: bool,
has_control_barriers: bool,
};
options: ModuleOptions,
it: WordIterator,
@@ -62,20 +80,12 @@ results: []Result,
entry_points: std.ArrayList(SpvEntryPoint),
capabilities: std.EnumSet(spv.SpvCapability),
local_size_x: SpvWord,
local_size_y: SpvWord,
local_size_z: SpvWord,
geometry_invocations: SpvWord,
geometry_output_count: SpvWord,
geometry_input: SpvWord,
geometry_output: SpvWord,
input_locations: [lib.SPIRV_MAX_INPUT_LOCATIONS]SpvWord,
output_locations: [lib.SPIRV_MAX_OUTPUT_LOCATIONS]SpvWord,
bindings: [lib.SPIRV_MAX_SET][lib.SPIRV_MAX_SET_BINDINGS]SpvWord,
input_locations: [lib.SPIRV_MAX_INPUT_LOCATIONS][4]SpvWord,
output_locations: [lib.SPIRV_MAX_OUTPUT_LOCATIONS][4]SpvWord,
bindings: std.ArrayList(BindingEntry),
builtins: std.EnumMap(spv.SpvBuiltIn, SpvWord),
push_constants: []Value,
reflection_infos: ReflectionInfos,
pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: ModuleOptions) ModuleError!Self {
var self: Self = std.mem.zeroInit(Self, .{
@@ -83,12 +93,11 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: Modu
.code = allocator.dupe(SpvWord, source) catch return ModuleError.OutOfMemory,
.extensions = std.ArrayList([]const u8).empty,
.entry_points = std.ArrayList(SpvEntryPoint).empty,
.bindings = std.ArrayList(BindingEntry).empty,
.capabilities = std.EnumSet(spv.SpvCapability).initEmpty(),
.local_size_x = 1,
.local_size_y = 1,
.local_size_z = 1,
});
errdefer allocator.free(self.code);
errdefer self.bindings.deinit(allocator);
op.initRuntimeDispatcher();
@@ -126,53 +135,7 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: Modu
_ = self.it.skip(); // Skip schema
try self.pass(allocator); // Setup pass
try self.populateMaps();
if (std.process.hasEnvVarConstant("SPIRV_INTERPRETER_DEBUG_LOGS")) {
var capability_set_names: std.ArrayList([]const u8) = .empty;
defer capability_set_names.deinit(allocator);
var it = self.capabilities.iterator();
while (it.next()) |cap| {
capability_set_names.append(allocator, @tagName(cap)) catch return ModuleError.OutOfMemory;
}
const capabilities = std.mem.join(allocator, ", ", capability_set_names.items) catch return ModuleError.OutOfMemory;
defer allocator.free(capabilities);
var entry_points_names = std.ArrayList([]const u8).initCapacity(allocator, self.entry_points.items.len) catch return ModuleError.OutOfMemory;
defer entry_points_names.deinit(allocator);
for (self.entry_points.items) |entry_point| {
entry_points_names.appendAssumeCapacity(entry_point.name);
}
const entry_points = std.mem.join(allocator, ", ", entry_points_names.items) catch return ModuleError.OutOfMemory;
defer allocator.free(entry_points);
std.log.scoped(.SPIRV_Interpreter).debug(
\\Loaded shader module with infos:
\\ SPIR-V version: {d}.{d}
\\ Generator: {s} (ID {d}), encoded version 0x{X}
\\ Capabilities: [{s}]
\\ Entry points: [{s}]
, .{
self.version_major,
self.version_minor,
spv.vendorName(self.generator_id),
self.generator_id,
self.generator_version,
capabilities,
entry_points,
});
}
//@import("pretty").print(allocator, self.results, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return ModuleError.OutOfMemory;
try self.applyDecorations(allocator);
return self;
}
@@ -189,8 +152,7 @@ fn checkEndiannessFromSpvMagic(magic: SpvWord) bool {
}
fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
var rt = Runtime.init(allocator, self) catch return ModuleError.OutOfMemory;
defer rt.deinit(allocator);
var rt = Runtime.init(allocator, self, undefined) catch return ModuleError.OutOfMemory;
while (rt.it.nextOrNull()) |opcode_data| {
const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1;
@@ -205,51 +167,199 @@ fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
_ = it_tmp.skipN(word_count);
rt.it = it_tmp;
}
for (self.results, rt.results) |*result, new_result| {
result.deinit(allocator);
result.* = new_result.dupe(allocator) catch return ModuleError.OutOfMemory;
}
rt.deinit(allocator);
}
fn populateMaps(self: *Self) ModuleError!void {
fn resolveConstantWord(self: *const Self, id: SpvWord) ?SpvWord {
if (id >= self.results.len) return null;
const variant = self.results[id].variant orelse return null;
return switch (variant) {
.Constant => |c| switch (c.value) {
.Int => |i| i.value.uint32,
else => null,
},
else => null,
};
}
fn findAccessChainToMember(self: *const Self, base_id: SpvWord, member_index: SpvWord) ?SpvWord {
for (self.results, 0..) |*result, id| {
const variant = result.variant orelse continue;
switch (variant) {
.AccessChain => |a| {
if (a.base != base_id or a.indexes.len == 0) continue;
const first_index = self.resolveConstantWord(a.indexes[0]) orelse continue;
if (first_index == member_index) return @intCast(id);
},
else => {},
}
}
return null;
}
fn resultComponent(self: *const Self, id: SpvWord) SpvWord {
for (self.results[id].decorations.items) |decoration| {
if (decoration.rtype == .Component)
return decoration.literal_1;
}
return 0;
}
fn applyInterfaceDecoration(self: *Self, storage_class: spv.SpvStorageClass, decoration: Result.Decoration, id: SpvWord) ModuleError!void {
switch (storage_class) {
.Input => switch (decoration.rtype) {
.BuiltIn => self.builtins.put(
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
id,
),
.Location => self.input_locations[decoration.literal_1][self.resultComponent(id)] = id,
else => {},
},
.Output => switch (decoration.rtype) {
.BuiltIn => self.builtins.put(
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
id,
),
.Location => self.output_locations[decoration.literal_1][self.resultComponent(id)] = id,
else => {},
},
else => {},
}
}
fn applyStructMemberInterfaceDecorations(self: *Self, storage_class: spv.SpvStorageClass, type_word: SpvWord, id: SpvWord) ModuleError!void {
switch (storage_class) {
.Input, .Output => {},
else => return,
}
const type_result = &self.results[type_word];
const target_type_word = if (type_result.variant) |variant| switch (variant) {
.Type => |t| switch (t) {
.Pointer => |ptr| ptr.target,
else => type_word,
},
else => type_word,
} else type_word;
const target_result = &self.results[target_type_word];
if (target_result.variant) |variant| {
switch (variant) {
.Type => |t| switch (t) {
.Structure => {
for (target_result.decorations.items) |decoration| {
switch (decoration.rtype) {
.BuiltIn, .Location => {
const member_id = self.findAccessChainToMember(id, decoration.index) orelse continue;
try self.applyInterfaceDecoration(storage_class, decoration, member_id);
},
else => {},
}
}
},
else => {},
},
else => {},
}
}
}
fn applyDecorations(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
for (self.results, 0..) |result, id| {
if (result.variant == null or std.meta.activeTag(result.variant.?) != .Variable)
if (result.variant == null)
continue;
var set: ?usize = null;
var binding: ?usize = null;
var set: ?SpvWord = null;
var binding: ?SpvWord = null;
for (result.decorations.items) |decoration| {
switch (result.variant.?.Variable.storage_class) {
.Input => {
switch (decoration.rtype) {
.BuiltIn => self.builtins.put(
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
@intCast(id),
),
.Location => self.input_locations[decoration.literal_1] = @intCast(id),
if (result.variant) |*variant| switch (variant.*) {
.Variable => |v| {
try self.applyInterfaceDecoration(v.storage_class, decoration, @intCast(id));
switch (v.storage_class) {
.StorageBuffer,
.Uniform,
.UniformConstant,
=> {
switch (decoration.rtype) {
.Binding => binding = decoration.literal_1,
.DescriptorSet => set = decoration.literal_1,
else => {},
}
},
else => {},
}
},
.Output => {
if (decoration.rtype == .Location)
self.output_locations[decoration.literal_1] = @intCast(id);
},
.StorageBuffer,
.Uniform,
.UniformConstant,
=> {
switch (decoration.rtype) {
.Binding => binding = decoration.literal_1,
.DescriptorSet => set = decoration.literal_1,
.Type => |t| {
switch (t) {
.Structure => |*s| {
switch (decoration.rtype) {
.Offset => s.members_offsets[decoration.index] = decoration.literal_1,
.MatrixStride => s.members_matrix_strides[decoration.index] = decoration.literal_1,
else => {},
}
},
else => {},
}
},
else => {},
}
};
}
if (result.variant) |*variant| switch (variant.*) {
.Variable => |*v| {
try self.applyStructMemberInterfaceDecorations(v.storage_class, v.type_word, @intCast(id));
switch (v.storage_class) {
.StorageBuffer,
.Uniform,
.PushConstant,
=> if (v.value == .Structure) {
if (self.results[v.type_word].variant) |type_variant| switch (type_variant) {
.Type => |type_data| switch (type_data) {
.Structure => |s| {
@memcpy(@constCast(v.value.Structure.offsets), s.members_offsets);
@memcpy(@constCast(v.value.Structure.matrix_strides), s.members_matrix_strides);
},
else => {},
},
else => {},
};
},
else => {},
}
},
else => {},
};
if (set != null and binding != null) {
self.bindings[set.?][binding.?] = @intCast(id);
self.bindings.append(allocator, .{
.set = set.?,
.binding = binding.?,
.result = @intCast(id),
}) catch return ModuleError.OutOfMemory;
}
}
}
pub fn getBindingResult(self: *const Self, set: SpvWord, binding: SpvWord) ?SpvWord {
for (self.bindings.items) |entry| {
if (entry.set == set and entry.binding == binding) {
return entry.result;
}
}
return null;
}
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(self.code);
for (self.entry_points.items) |entry| {
@@ -262,6 +372,7 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(ext);
}
self.extensions.deinit(allocator);
self.bindings.deinit(allocator);
for (self.results) |*result| {
result.deinit(allocator);
+91 -280
View File
@@ -1,6 +1,8 @@
const std = @import("std");
const spv = @import("spv.zig");
const op = @import("opcodes.zig");
const lib = @import("lib.zig");
const Value = @import("Value.zig").Value;
const Runtime = @import("Runtime.zig");
const RuntimeError = Runtime.RuntimeError;
@@ -10,17 +12,17 @@ const SpvByte = spv.SpvByte;
const SpvWord = spv.SpvWord;
const SpvBool = spv.SpvBool;
pub const Vec4f32 = @Vector(4, f32);
pub const Vec3f32 = @Vector(3, f32);
pub const Vec2f32 = @Vector(2, f32);
const Vec4f32 = lib.Vec4f32;
const Vec3f32 = lib.Vec3f32;
const Vec2f32 = lib.Vec2f32;
pub const Vec4i32 = @Vector(4, i32);
pub const Vec3i32 = @Vector(3, i32);
pub const Vec2i32 = @Vector(2, i32);
const Vec4i32 = lib.Vec4i32;
const Vec3i32 = lib.Vec3i32;
const Vec2i32 = lib.Vec2i32;
pub const Vec4u32 = @Vector(4, u32);
pub const Vec3u32 = @Vector(3, u32);
pub const Vec2u32 = @Vector(2, u32);
const Vec4u32 = lib.Vec4u32;
const Vec3u32 = lib.Vec3u32;
const Vec2u32 = lib.Vec2u32;
pub const Variant = enum {
String,
@@ -70,198 +72,13 @@ const ImageInfo = struct {
access: spv.SpvAccessQualifier,
};
const Decoration = struct {
pub const Decoration = struct {
rtype: spv.SpvDecoration,
literal_1: SpvWord,
literal_2: ?SpvWord,
index: SpvWord,
};
pub const Value = union(Type) {
Void: struct {},
Bool: bool,
Int: struct {
bit_count: usize,
value: extern union {
sint8: i8,
sint16: i16,
sint32: i32,
sint64: i64,
uint8: u8,
uint16: u16,
uint32: u32,
uint64: u64,
},
},
Float: struct {
bit_count: usize,
value: extern union {
float16: f16,
float32: f32,
float64: f64,
},
},
Vector: []Value,
Vector4f32: Vec4f32,
Vector3f32: Vec3f32,
Vector2f32: Vec2f32,
Vector4i32: Vec4i32,
Vector3i32: Vec3i32,
Vector2i32: Vec2i32,
Vector4u32: Vec4u32,
Vector3u32: Vec3u32,
Vector2u32: Vec2u32,
Matrix: []Value,
Array: []Value,
RuntimeArray: ?[]Value,
Structure: []Value,
Function: noreturn,
Image: struct {},
Sampler: struct {},
SampledImage: struct {},
Pointer: union(enum) {
common: *Value,
f32_ptr: *f32,
i32_ptr: *i32, //< For vector specializations
u32_ptr: *u32,
},
pub inline fn getCompositeDataOrNull(self: *const Value) ?[]Value {
return switch (self.*) {
.Vector, .Matrix, .Array, .Structure => |v| v,
.RuntimeArray => |v| v,
else => null,
};
}
fn init(allocator: std.mem.Allocator, results: []const Self, target: SpvWord) RuntimeError!Value {
const resolved = results[target].resolveType(results);
const member_count = resolved.getMemberCounts();
return switch (resolved.variant.?) {
.Type => |t| switch (t) {
.Bool => .{ .Bool = false },
.Int => |i| .{ .Int = .{
.bit_count = i.bit_length,
.value = .{ .uint64 = 0 },
} },
.Float => |f| .{ .Float = .{
.bit_count = f.bit_length,
.value = .{ .float64 = 0 },
} },
.Vector => |v| blk: {
var self: Value = .{ .Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Vector) |*value| {
value.* = try Value.init(allocator, results, v.components_type_word);
}
break :blk self;
},
.Vector4f32 => .{ .Vector4f32 = Vec4f32{ 0.0, 0.0, 0.0, 0.0 } },
.Vector3f32 => .{ .Vector3f32 = Vec3f32{ 0.0, 0.0, 0.0 } },
.Vector2f32 => .{ .Vector2f32 = Vec2f32{ 0.0, 0.0 } },
.Vector4i32 => .{ .Vector4i32 = Vec4i32{ 0, 0, 0, 0 } },
.Vector3i32 => .{ .Vector3i32 = Vec3i32{ 0, 0, 0 } },
.Vector2i32 => .{ .Vector2i32 = Vec2i32{ 0, 0 } },
.Vector4u32 => .{ .Vector4u32 = Vec4u32{ 0, 0, 0, 0 } },
.Vector3u32 => .{ .Vector3u32 = Vec3u32{ 0, 0, 0 } },
.Vector2u32 => .{ .Vector2u32 = Vec2u32{ 0, 0 } },
.Matrix => |m| blk: {
var self: Value = .{ .Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Matrix) |*value| {
value.* = try Value.init(allocator, results, m.column_type_word);
}
break :blk self;
},
.Array => |a| blk: {
var self: Value = .{ .Array = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Array) |*value| {
value.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk self;
},
.Structure => |s| blk: {
var self: Value = .{ .Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Structure, s.members_type_word) |*value, member_type_word| {
value.* = try Value.init(allocator, results, member_type_word);
}
break :blk self;
},
.RuntimeArray => .{ .RuntimeArray = null },
else => unreachable,
},
else => unreachable,
};
}
/// Performs a deep copy
pub fn dupe(self: *const Value, allocator: std.mem.Allocator) RuntimeError!Value {
return switch (self.*) {
.Vector => |v| .{
.Vector = blk: {
const values = allocator.dupe(Value, v) catch return RuntimeError.OutOfMemory;
for (values, v) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Matrix => |m| .{
.Matrix = blk: {
const values = allocator.dupe(Value, m) catch return RuntimeError.OutOfMemory;
for (values, m) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Array => |a| .{
.Array = blk: {
const values = allocator.dupe(Value, a) catch return RuntimeError.OutOfMemory;
for (values, a) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.RuntimeArray => |opt_a| .{
.RuntimeArray = blk: {
if (opt_a) |a| {
const values = allocator.dupe(Value, a) catch return RuntimeError.OutOfMemory;
for (values, a) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
} else {
break :blk null;
}
},
},
.Structure => |s| .{
.Structure = blk: {
const values = allocator.dupe(Value, s) catch return RuntimeError.OutOfMemory;
for (values, s) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
else => self.*,
};
}
fn deinit(self: *Value, allocator: std.mem.Allocator) void {
switch (self.*) {
.Vector, .Matrix, .Array, .Structure => |values| {
for (values) |*value| value.deinit(allocator);
allocator.free(values);
},
.RuntimeArray => |opt_values| if (opt_values) |values| {
for (values) |*value| value.deinit(allocator);
allocator.free(values);
},
else => {},
}
}
};
pub const TypeData = union(Type) {
Void: struct {},
Bool: struct {},
@@ -295,13 +112,17 @@ pub const TypeData = union(Type) {
components_type_word: SpvWord,
components_type: Type,
member_count: SpvWord,
stride: SpvWord,
},
RuntimeArray: struct {
components_type_word: SpvWord,
components_type: Type,
stride: SpvWord,
},
Structure: struct {
members_type_word: []const SpvWord,
members_offsets: []?SpvWord,
members_matrix_strides: []?SpvWord,
member_names: std.ArrayList([]const u8),
},
Function: struct {
@@ -309,9 +130,19 @@ pub const TypeData = union(Type) {
return_type: SpvWord,
params: []const SpvWord,
},
Image: struct {},
Image: struct {
dim: spv.SpvDim,
depth: SpvByte,
arrayed: SpvByte,
ms: SpvByte,
sampled: SpvByte,
format: spv.SpvImageFormat,
access: ?spv.SpvAccessQualifier,
},
Sampler: struct {},
SampledImage: struct {},
SampledImage: struct {
image_type: SpvWord,
},
Pointer: struct {
storage_class: spv.SpvStorageClass,
target: SpvWord,
@@ -322,13 +153,19 @@ pub const TypeData = union(Type) {
.Bool => 1,
.Int => |i| @divExact(i.bit_length, 8),
.Float => |f| @divExact(f.bit_length, 8),
.Vector => |v| results[v.components_type_word].variant.?.Type.getSize(results),
.Array => |a| results[a.components_type_word].variant.?.Type.getSize(results),
.Matrix => |m| results[m.column_type_word].variant.?.Type.getSize(results),
.RuntimeArray => |a| results[a.components_type_word].variant.?.Type.getSize(results),
.Vector => |v| results[v.components_type_word].variant.?.Type.getSize(results) * v.member_count,
.Array => |a| a.stride,
.Matrix => |m| results[m.column_type_word].variant.?.Type.getSize(results) * m.member_count,
.RuntimeArray => |a| a.stride,
.Structure => |s| blk: {
var total: usize = 0;
for (s.members_type_word) |type_word| {
for (s.members_type_word, 0..) |type_word, i| {
if (i + 1 < s.members_offsets.len) {
if (s.members_offsets[i + 1]) |offset| {
total = offset;
continue;
}
}
total += results[type_word].variant.?.Type.getSize(results);
}
break :blk total;
@@ -367,6 +204,8 @@ pub const VariantData = union(Variant) {
},
AccessChain: struct {
target: SpvWord,
base: SpvWord,
indexes: []SpvWord,
value: Value,
},
FunctionParameter: struct {
@@ -406,12 +245,18 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
for (data.member_names.items) |name| {
allocator.free(name);
}
allocator.free(data.members_offsets);
allocator.free(data.members_matrix_strides);
data.member_names.deinit(allocator);
},
else => {},
},
.Constant => |*c| c.value.deinit(allocator),
.Variable => |*v| v.value.deinit(allocator),
.AccessChain => |*a| {
allocator.free(a.indexes);
a.value.deinit(allocator);
},
.Function => |f| allocator.free(f.params),
else => {},
}
@@ -459,11 +304,13 @@ pub inline fn getConstValue(self: *Self) RuntimeError!*const Value {
}
pub inline fn getVariant(self: *Self) RuntimeError!*VariantData {
return &(self.variant orelse return RuntimeError.InvalidSpirV);
if (self.variant) |*variant| return variant;
return RuntimeError.InvalidSpirV;
}
pub inline fn getConstVariant(self: *const Self) RuntimeError!*const VariantData {
return &(self.variant orelse return RuntimeError.InvalidSpirV);
if (self.variant) |variant| return &variant;
return RuntimeError.InvalidSpirV;
}
/// Performs a deep copy
@@ -482,6 +329,8 @@ pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
.Type = .{
.Structure = .{
.members_type_word = allocator.dupe(SpvWord, s.members_type_word) catch return RuntimeError.OutOfMemory,
.members_offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory,
.members_matrix_strides = allocator.dupe(?SpvWord, s.members_matrix_strides) catch return RuntimeError.OutOfMemory,
.member_names = blk2: {
const member_names = s.member_names.clone(allocator) catch return RuntimeError.OutOfMemory;
for (member_names.items, s.member_names.items) |*new_name, name| {
@@ -526,6 +375,14 @@ pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
.params = allocator.dupe(SpvWord, f.params) catch return RuntimeError.OutOfMemory,
},
},
.AccessChain => |a| break :blk .{
.AccessChain = .{
.target = a.target,
.base = a.base,
.indexes = allocator.dupe(SpvWord, a.indexes) catch return RuntimeError.OutOfMemory,
.value = try a.value.dupe(allocator),
},
},
else => break :blk variant,
}
}
@@ -540,6 +397,7 @@ pub fn resolveLaneBitWidth(target_type: TypeData, rt: *const Runtime) RuntimeErr
.Float => |f| f.bit_length,
.Int => |i| i.bit_length,
.Vector => |v| continue :sw (try rt.results[v.components_type_word].getVariant()).Type,
.Matrix => |m| continue :sw (try rt.results[m.column_type_word].getVariant()).Type,
.Vector4f32,
.Vector3f32,
.Vector2f32,
@@ -554,10 +412,23 @@ pub fn resolveLaneBitWidth(target_type: TypeData, rt: *const Runtime) RuntimeErr
};
}
pub fn resolveLaneCount(target_type: TypeData) RuntimeError!SpvWord {
return switch (target_type) {
.Bool, .Float, .Int => 1,
.Vector => |v| v.member_count,
.Matrix => |m| m.member_count,
.Vector4f32, .Vector4i32, .Vector4u32 => 4,
.Vector3f32, .Vector3i32, .Vector3u32 => 3,
.Vector2f32, .Vector2i32, .Vector2u32 => 2,
else => return RuntimeError.InvalidSpirV,
};
}
pub fn resolveSign(target_type: TypeData, rt: *const Runtime) RuntimeError!enum { signed, unsigned } {
return sw: switch (target_type) {
.Int => |i| if (i.is_signed) .signed else .unsigned,
.Vector => |v| continue :sw (try rt.results[v.components_type_word].getVariant()).Type,
.Matrix => |m| continue :sw (try rt.results[m.column_type_word].getVariant()).Type,
.Vector4i32 => .signed,
.Vector3i32 => .signed,
.Vector2i32 => .signed,
@@ -568,17 +439,21 @@ pub fn resolveSign(target_type: TypeData, rt: *const Runtime) RuntimeError!enum
};
}
pub fn resolveType(self: *const Self, results: []const Self) *const Self {
pub inline fn resolveType(self: *const Self, results: []const Self) *const Self {
return if (self.resolveTypeWordOrNull()) |word| &results[word] else self;
}
pub fn resolveTypeWordOrNull(self: *const Self) ?SpvWord {
return if (self.variant) |variant|
switch (variant) {
.Type => |t| switch (t) {
.Pointer => |ptr| &results[ptr.target],
else => self,
.Pointer => |ptr| ptr.target,
else => null,
},
else => self,
else => null,
}
else
self;
null;
}
pub fn getMemberCounts(self: *const Self) usize {
@@ -592,7 +467,6 @@ pub fn getMemberCounts(self: *const Self) usize {
.Vector2f32, .Vector2i32, .Vector2u32 => return 2,
.Matrix => |m| return m.member_count,
.Array => |a| return a.member_count,
.SampledImage => return 2,
.Structure => |s| return s.members_type_word.len,
.Function => |f| return f.params.len,
else => {},
@@ -603,76 +477,13 @@ pub fn getMemberCounts(self: *const Self) usize {
return 0;
}
pub fn initValue(allocator: std.mem.Allocator, member_count: usize, results: []const Self, resolved: *const Self) RuntimeError!Value {
return switch (resolved.variant.?) {
.Type => |t| switch (t) {
.Void => .{ .Void = .{} },
.Bool => .{ .Bool = false },
.Int => |i| .{ .Int = .{
.bit_count = i.bit_length,
.value = .{ .uint64 = 0 },
} },
.Float => |f| .{ .Float = .{
.bit_count = f.bit_length,
.value = .{ .float64 = 0 },
} },
.Vector => |v| blk: {
const value: Value = .{ .Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Vector);
for (value.Vector) |*val| {
val.* = try Value.init(allocator, results, v.components_type_word);
}
break :blk value;
},
.Vector4f32 => .{ .Vector4f32 = Vec4f32{ 0.0, 0.0, 0.0, 0.0 } },
.Vector3f32 => .{ .Vector3f32 = Vec3f32{ 0.0, 0.0, 0.0 } },
.Vector2f32 => .{ .Vector2f32 = Vec2f32{ 0.0, 0.0 } },
.Vector4i32 => .{ .Vector4i32 = Vec4i32{ 0, 0, 0, 0 } },
.Vector3i32 => .{ .Vector3i32 = Vec3i32{ 0, 0, 0 } },
.Vector2i32 => .{ .Vector2i32 = Vec2i32{ 0, 0 } },
.Vector4u32 => .{ .Vector4u32 = Vec4u32{ 0, 0, 0, 0 } },
.Vector3u32 => .{ .Vector3u32 = Vec3u32{ 0, 0, 0 } },
.Vector2u32 => .{ .Vector2u32 = Vec2u32{ 0, 0 } },
.Matrix => |m| blk: {
const value: Value = .{ .Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Matrix);
for (value.Matrix) |*v| {
v.* = try Value.init(allocator, results, m.column_type_word);
}
break :blk value;
},
.Array => |a| blk: {
const value: Value = .{ .Array = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Array);
for (value.Array) |*val| {
val.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk value;
},
.RuntimeArray => |a| blk: {
if (member_count == 0) {
break :blk Value{ .RuntimeArray = null };
}
const value: Value = .{ .RuntimeArray = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.RuntimeArray.?);
for (value.RuntimeArray.?) |*val| {
val.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk value;
},
.Structure => |s| blk: {
const value: Value = .{ .Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Structure);
for (value.Structure, s.members_type_word) |*v, member_type_word| {
v.* = try Value.init(allocator, results, member_type_word);
}
break :blk value;
},
.Image => RuntimeError.ToDo,
.Sampler => RuntimeError.ToDo,
.SampledImage => RuntimeError.ToDo,
else => RuntimeError.InvalidSpirV,
pub inline fn flushPtr(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
if (self.variant) |*variant| switch (variant.*) {
.Variable => |*v| try v.value.flushPtr(allocator),
.AccessChain => |*a| {
if (!std.mem.allEqual(u8, std.mem.asBytes(&a.value), 0xaa))
try a.value.flushPtr(allocator);
},
else => RuntimeError.InvalidSpirV,
else => {},
};
}
+522 -278
View File
@@ -4,6 +4,7 @@ const std = @import("std");
const spv = @import("spv.zig");
const op = @import("opcodes.zig");
const lib = @import("lib.zig");
const pretty = @import("pretty");
const SpvVoid = spv.SpvVoid;
const SpvByte = spv.SpvByte;
@@ -11,12 +12,15 @@ const SpvWord = spv.SpvWord;
const SpvBool = spv.SpvBool;
const Module = @import("Module.zig");
const PrimitiveType = @import("Value.zig").PrimitiveType;
const Result = @import("Result.zig");
const Value = @import("Value.zig").Value;
const WordIterator = @import("WordIterator.zig");
const Self = @This();
pub const RuntimeError = error{
Barrier,
DivisionByZero,
InvalidEntryPoint,
InvalidSpirV,
@@ -29,12 +33,77 @@ pub const RuntimeError = error{
Unreachable,
UnsupportedSpirV,
UnsupportedExtension,
Unknown,
};
pub const EntryPointStatus = enum {
completed,
barrier,
};
pub const SpecializationEntry = struct {
id: SpvWord,
offset: usize,
size: usize,
};
pub const Derivative = struct {
dx: Value,
dy: Value,
pub fn dupe(self: *const @This(), allocator: std.mem.Allocator) RuntimeError!@This() {
return .{
.dx = try self.dx.dupe(allocator),
.dy = try self.dy.dupe(allocator),
};
}
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
self.dx.deinit(allocator);
self.dy.deinit(allocator);
}
};
pub const Function = struct {
source_location: usize,
result: *Result,
ret: *Result,
current_label: ?SpvWord,
previous_label: ?SpvWord,
};
pub fn Vec4(comptime T: type) type {
return struct {
x: T,
y: T,
z: T,
w: T,
};
}
pub const ImageOffset = struct {
x: i32 = 0,
y: i32 = 0,
z: i32 = 0,
};
pub const ImageDerivatives = struct {
dx: Vec4(f32),
dy: Vec4(f32),
};
pub const ImageAPI = struct {
readImageFloat4: *const fn (driver_image: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) RuntimeError!Vec4(f32),
readImageInt4: *const fn (driver_image: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) RuntimeError!Vec4(u32),
writeImageFloat4: *const fn (driver_image: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: Vec4(f32)) RuntimeError!void,
writeImageInt4: *const fn (driver_image: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: Vec4(u32)) RuntimeError!void,
sampleImageFloat4: *const fn (driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: ImageOffset) RuntimeError!Vec4(f32),
sampleImageInt4: *const fn (driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: ImageOffset) RuntimeError!Vec4(u32),
sampleImageDref: *const fn (driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: ImageOffset) RuntimeError!f32,
queryImageSize: *const fn (driver_image: *anyopaque, dim: spv.SpvDim, arrayed: bool, lod: ?i32) RuntimeError!Vec4(u32),
queryImageLevels: *const fn (driver_image: *anyopaque) RuntimeError!u32,
queryImageSamples: *const fn (driver_image: *anyopaque) RuntimeError!u32,
queryImageLod: *const fn (driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, derivatives: ImageDerivatives) RuntimeError!Vec4(f32),
};
mod: *Module,
@@ -47,7 +116,15 @@ current_parameter_index: SpvWord,
current_function: ?*Result,
function_stack: std.ArrayList(Function),
pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self {
current_label: ?SpvWord,
previous_label: ?SpvWord,
specialization_constants: std.AutoHashMapUnmanaged(u32, []const u8),
derivatives: std.AutoHashMapUnmanaged(SpvWord, Derivative),
image_api: ImageAPI,
pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI) RuntimeError!Self {
return .{
.mod = module,
.it = module.it,
@@ -55,37 +132,172 @@ pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self {
const results = allocator.dupe(Result, module.results) catch return RuntimeError.OutOfMemory;
for (results, module.results) |*new_result, result| {
new_result.* = result.dupe(allocator) catch return RuntimeError.OutOfMemory;
if (new_result.variant) |*variant| {
switch (variant.*) {
.AccessChain => |*access_chain| {
allocator.free(access_chain.indexes);
access_chain.value.deinit(allocator);
new_result.variant = null;
},
else => {},
}
}
}
break :blk results;
},
.current_parameter_index = 0,
.current_function = null,
.function_stack = .empty,
.current_label = null,
.previous_label = null,
.specialization_constants = .empty,
.derivatives = .empty,
.image_api = image_api,
};
}
pub fn initFrom(allocator: std.mem.Allocator, other: *const Self, image_api: ImageAPI) RuntimeError!Self {
const results = allocator.alloc(Result, other.results.len) catch return RuntimeError.OutOfMemory;
var initialized: usize = 0;
errdefer {
for (results[0..initialized]) |*result| {
result.deinit(allocator);
}
allocator.free(results);
}
for (results, other.results) |*new_result, result| {
new_result.* = result.dupe(allocator) catch return RuntimeError.OutOfMemory;
initialized += 1;
}
var self: Self = .{
.mod = other.mod,
.it = other.mod.it,
.results = results,
.current_parameter_index = 0,
.current_function = null,
.function_stack = .empty,
.current_label = null,
.previous_label = null,
.specialization_constants = .empty,
.derivatives = .empty,
.image_api = image_api,
};
errdefer self.deinit(allocator);
try self.copySpecializationConstantsFrom(allocator, other);
return self;
}
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
for (self.results) |*result| {
result.deinit(allocator);
}
allocator.free(self.results);
self.function_stack.deinit(allocator);
var it = self.specialization_constants.iterator();
while (it.next()) |entry| {
allocator.free(entry.value_ptr.*);
}
self.specialization_constants.deinit(allocator);
var derivatives = self.derivatives.iterator();
while (derivatives.next()) |entry| {
entry.value_ptr.deinit(allocator);
}
self.derivatives.deinit(allocator);
}
pub fn getEntryPointByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord {
pub fn addSpecializationInfo(self: *Self, allocator: std.mem.Allocator, entry: SpecializationEntry, data: []const u8) RuntimeError!void {
const slice = allocator.dupe(u8, data[entry.offset .. entry.offset + entry.size]) catch return RuntimeError.OutOfMemory;
self.specialization_constants.put(allocator, entry.id, slice) catch return RuntimeError.OutOfMemory;
}
pub fn copySpecializationConstantsFrom(self: *Self, allocator: std.mem.Allocator, other: *const Self) RuntimeError!void {
var it = other.specialization_constants.iterator();
while (it.next()) |entry| {
const slice = allocator.dupe(u8, entry.value_ptr.*) catch return RuntimeError.OutOfMemory;
self.specialization_constants.put(allocator, entry.key_ptr.*, slice) catch {
allocator.free(slice);
return RuntimeError.OutOfMemory;
};
}
}
pub fn setDerivative(self: *Self, allocator: std.mem.Allocator, result: SpvWord, dx: *const Value, dy: *const Value) RuntimeError!void {
const derivative: Derivative = .{
.dx = try dx.dupe(allocator),
.dy = try dy.dupe(allocator),
};
errdefer {
var tmp = derivative;
tmp.deinit(allocator);
}
const gop = self.derivatives.getOrPut(allocator, result) catch return RuntimeError.OutOfMemory;
if (gop.found_existing) {
gop.value_ptr.deinit(allocator);
}
gop.value_ptr.* = derivative;
}
pub fn setDerivativeFromMemory(self: *Self, allocator: std.mem.Allocator, result: SpvWord, dx: []const u8, dy: []const u8) RuntimeError!void {
const target_type = try self.getResultTargetTypeWord(result);
var dx_value = try Value.init(allocator, self.results, target_type, false);
defer dx_value.deinit(allocator);
_ = try dx_value.write(dx);
var dy_value = try Value.init(allocator, self.results, target_type, false);
defer dy_value.deinit(allocator);
_ = try dy_value.write(dy);
try self.setDerivative(allocator, result, &dx_value, &dy_value);
}
fn getResultTargetTypeWord(self: *const Self, result: SpvWord) RuntimeError!SpvWord {
return switch ((try self.results[result].getConstVariant()).*) {
.Variable => |v| v.type_word,
.Constant => |c| c.type_word,
.FunctionParameter => |p| p.type_word,
.AccessChain => |a| a.target,
else => return RuntimeError.InvalidSpirV,
};
}
pub fn clearDerivative(self: *Self, allocator: std.mem.Allocator, result: SpvWord) void {
if (self.derivatives.fetchRemove(result)) |kv| {
var derivative = kv.value;
derivative.deinit(allocator);
}
}
pub fn copyDerivative(self: *Self, allocator: std.mem.Allocator, dst: SpvWord, src: SpvWord) RuntimeError!void {
if (self.derivatives.get(src)) |derivative| {
try self.setDerivative(allocator, dst, &derivative.dx, &derivative.dy);
} else {
self.clearDerivative(allocator, dst);
}
}
pub fn getEntryPointByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.mod.entry_points.items, 0..) |entry_point, i| {
if (blk: {
// Not using std.mem.eql as entry point names may have longer size than their content
for (0..@min(name.len, entry_point.name.len)) |j| {
if (name[j] != entry_point.name[j]) break :blk false;
if (name[j] != entry_point.name[j])
break :blk false;
}
if (entry_point.name.len != name.len and entry_point.name[name.len] != 0)
break :blk false;
break :blk true;
}) return @intCast(i);
}
return error.NotFound;
return RuntimeError.NotFound;
}
pub fn getResultByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord {
pub fn getResultByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.results, 0..) |result, i| {
if (result.name) |result_name| {
if (blk: {
@@ -97,14 +309,63 @@ pub fn getResultByName(self: *const Self, name: []const u8) error{NotFound}!SpvW
}) return @intCast(i);
}
}
return error.NotFound;
return RuntimeError.NotFound;
}
pub const LocationKind = enum { input, output };
pub inline fn getResultByLocation(self: *const Self, location: SpvWord, kind: LocationKind) RuntimeError!SpvWord {
return self.getResultByLocationComponent(location, 0, kind);
}
pub fn getResultByLocationComponent(self: *const Self, location: SpvWord, component: SpvWord, kind: LocationKind) RuntimeError!SpvWord {
switch (kind) {
.input => if (location < self.mod.input_locations.len and component < 4 and self.mod.input_locations[location][component] != 0) {
return self.mod.input_locations[location][component];
},
.output => if (location < self.mod.output_locations.len and component < 4 and self.mod.output_locations[location][component] != 0) {
return self.mod.output_locations[location][component];
},
}
return RuntimeError.NotFound;
}
pub fn getResultPrimitiveType(self: *const Self, result: SpvWord) RuntimeError!PrimitiveType {
if (result >= self.results.len)
return RuntimeError.OutOfBounds;
return (try self.results[result].getConstValue()).resolvePrimitiveType();
}
pub fn dumpResultsTable(self: *Self, allocator: std.mem.Allocator, writer: *std.Io.Writer) RuntimeError!void {
const dump = pretty.dump(allocator, self.results, .{
.tab_size = 4,
.max_depth = 0,
.struct_max_len = 0,
.array_max_len = 0,
}) catch return RuntimeError.OutOfMemory;
defer allocator.free(dump);
writer.print("{s}", .{dump}) catch return RuntimeError.Unknown;
writer.flush() catch return RuntimeError.Unknown;
}
/// Calls an entry point, `entry_point_index` being the index of the entry point ordered by declaration in the bytecode
pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_index: SpvWord) RuntimeError!void {
self.reset();
pub inline fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_index: SpvWord) RuntimeError!void {
_ = try self.beginEntryPoint(allocator, entry_point_index);
}
if (entry_point_index > self.mod.entry_points.items.len) return RuntimeError.InvalidEntryPoint;
pub fn beginEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_index: SpvWord) RuntimeError!EntryPointStatus {
self.reset();
if (entry_point_index >= self.mod.entry_points.items.len)
return RuntimeError.InvalidEntryPoint;
// Spec constants pass
try self.pass(allocator, .initMany(&.{
.SpecConstantTrue,
.SpecConstantFalse,
.SpecConstantComposite,
.SpecConstant,
.SpecConstantOp,
}));
{
const entry_point_desc = &self.mod.entry_points.items[entry_point_index];
@@ -112,11 +373,14 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
if (entry_point_result.variant) |variant| {
switch (variant) {
.Function => |f| {
if (!self.it.jumpToSourceLocation(f.source_location)) return RuntimeError.InvalidEntryPoint;
if (!self.it.jumpToSourceLocation(f.source_location))
return RuntimeError.InvalidEntryPoint;
self.function_stack.append(allocator, .{
.source_location = f.source_location,
.result = entry_point_result,
.ret = &self.results[f.return_type],
.current_label = null,
.previous_label = null,
}) catch return RuntimeError.OutOfMemory;
},
else => return RuntimeError.InvalidEntryPoint,
@@ -126,11 +390,34 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
}
}
// Execution pass
return self.continueEntryPoint(allocator);
}
pub fn continueEntryPoint(self: *Self, allocator: std.mem.Allocator) RuntimeError!EntryPointStatus {
self.pass(allocator, null) catch |err| switch (err) {
RuntimeError.Barrier => return .barrier,
else => return err,
};
return .completed;
}
fn pass(self: *Self, allocator: std.mem.Allocator, op_set: ?std.EnumSet(spv.SpvOp)) RuntimeError!void {
self.it.did_jump = false; // To reset function jump
while (self.it.nextOrNull()) |opcode_data| {
const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1;
const word_count_with_header = (opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift;
if (word_count_with_header == 0) return RuntimeError.InvalidSpirV;
const word_count = word_count_with_header - 1;
const opcode = (opcode_data & spv.SpvOpCodeMask);
if (op_set) |set| {
@branchHint(.unlikely);
if (!set.contains(@enumFromInt(opcode))) {
_ = self.it.skipN(word_count);
continue;
}
}
var it_tmp = self.it; // Save because operations may iter on this iterator
if (op.runtime_dispatcher[opcode]) |pfn| {
try pfn(allocator, word_count, self);
@@ -142,304 +429,261 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
self.it.did_jump = false;
}
}
//@import("pretty").print(allocator, self.results, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return RuntimeError.OutOfMemory;
}
pub fn readDescriptorSet(self: *const Self, output: []u8, set: SpvWord, binding: SpvWord) RuntimeError!void {
if (set < lib.SPIRV_MAX_SET and binding < lib.SPIRV_MAX_SET_BINDINGS) {
_ = try self.readValue(output, &self.results[self.mod.bindings[set][binding]].variant.?.Variable.value);
} else {
return RuntimeError.NotFound;
pub fn populatePushConstants(self: *Self, blob: []const u8) RuntimeError!void {
for (self.results) |*result| {
if (result.variant == null or std.meta.activeTag(result.variant.?) != .Variable)
continue;
const variable = &result.variant.?.Variable;
if (variable.storage_class != .PushConstant)
continue;
_ = try variable.value.write(blob);
}
}
pub fn writeDescriptorSet(self: *const Self, allocator: std.mem.Allocator, input: []const u8, set: SpvWord, binding: SpvWord) RuntimeError!void {
if (set < lib.SPIRV_MAX_SET and binding < lib.SPIRV_MAX_SET_BINDINGS) {
const variable = &self.results[self.mod.bindings[set][binding]].variant.?.Variable;
pub fn writeDescriptorSet(self: *const Self, input: []const u8, set: SpvWord, binding: SpvWord, descriptor_index: SpvWord) RuntimeError!void {
const result = self.mod.getBindingResult(set, binding) orelse return RuntimeError.NotFound;
const value = &(self.results[result].variant orelse return).Variable.value;
switch (value.*) {
.Array => |arr| {
if (descriptor_index >= arr.values.len)
return RuntimeError.NotFound;
_ = try arr.values[descriptor_index].write(input);
},
else => {
if (descriptor_index != 0)
return RuntimeError.NotFound;
_ = try value.write(input);
},
}
}
const helper = struct {
fn init(allocator2: std.mem.Allocator, len: usize, value: *Result.Value, type_word: SpvWord, results: []Result) RuntimeError!void {
const resolved = results[type_word].resolveType(results);
fn readResultValue(self: *const Self, output: []u8, result: SpvWord) RuntimeError!void {
const variant = self.results[result].variant orelse return RuntimeError.InvalidSpirV;
switch (variant) {
.Variable => |v| _ = try v.value.read(output),
.AccessChain => |a| switch (a.value) {
.Pointer => |ptr| switch (ptr.ptr) {
.common => |value_ptr| _ = try value_ptr.read(output),
.f32_ptr => |value_ptr| {
if (output.len < @sizeOf(f32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, output[0..@sizeOf(f32)], std.mem.asBytes(value_ptr));
},
.i32_ptr => |value_ptr| {
if (output.len < @sizeOf(i32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, output[0..@sizeOf(i32)], std.mem.asBytes(value_ptr));
},
.u32_ptr => |value_ptr| {
if (output.len < @sizeOf(u32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, output[0..@sizeOf(u32)], std.mem.asBytes(value_ptr));
},
},
else => _ = try a.value.read(output),
},
else => return RuntimeError.InvalidSpirV,
}
}
switch (value.*) {
.RuntimeArray => |a| if (a == null) {
const elem_size = resolved.variant.?.Type.getSize(results);
value.* = try Result.initValue(allocator2, std.math.divCeil(usize, len, elem_size) catch unreachable, results, resolved);
fn writeResultValue(self: *const Self, input: []const u8, result: SpvWord) RuntimeError!void {
if (self.results[result].variant) |*variant| {
switch (variant.*) {
.Variable => |*v| _ = try v.value.write(input),
.AccessChain => |*a| switch (a.value) {
.Pointer => |ptr| switch (ptr.ptr) {
.common => |value_ptr| _ = try value_ptr.write(input),
.f32_ptr => |value_ptr| {
if (input.len < @sizeOf(f32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(f32)]);
},
.Structure => |*s| for (s.*, 0..) |*elem, i| {
try @This().init(allocator2, len, elem, resolved.variant.?.Type.Structure.members_type_word[i], results);
.i32_ptr => |value_ptr| {
if (input.len < @sizeOf(i32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(i32)]);
},
else => {},
.u32_ptr => |value_ptr| {
if (input.len < @sizeOf(u32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(u32)]);
},
},
else => _ = try a.value.write(input),
},
else => return RuntimeError.InvalidSpirV,
}
} else {
return RuntimeError.InvalidSpirV;
}
}
const InputLocationTarget = struct {
result: SpvWord,
matrix_column: ?usize = null,
};
fn resolveInputLocationTarget(self: *const Self, location: SpvWord) RuntimeError!InputLocationTarget {
if (location < self.mod.input_locations.len and self.mod.input_locations[location][0] != 0) {
const result = self.mod.input_locations[location][0];
const value = try self.results[result].getConstValue();
switch (value.*) {
.Matrix => return .{ .result = result, .matrix_column = 0 },
else => return .{ .result = result },
}
}
var base_location = location;
while (base_location > 0) {
base_location -= 1;
const result = if (base_location < self.mod.input_locations.len)
self.mod.input_locations[base_location][0]
else
0;
if (result == 0) continue;
const location_offset: usize = @intCast(location - base_location);
const value = try self.results[result].getConstValue();
switch (value.*) {
.Matrix => |columns| {
if (location_offset < columns.len) {
return .{
.result = result,
.matrix_column = location_offset,
};
}
}
};
try helper.init(allocator, input.len, &variable.value, variable.type_word, self.results);
//@import("pretty").print(allocator, variable, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return RuntimeError.OutOfMemory;
_ = try self.writeValue(input, &variable.value);
} else {
return RuntimeError.NotFound;
},
else => {},
}
}
return RuntimeError.NotFound;
}
fn getInputLocationTargetValue(self: *const Self, target: InputLocationTarget) RuntimeError!*Value {
const value = switch ((try self.results[target.result].getVariant()).*) {
.Variable => |*v| &v.value,
.AccessChain => |*a| &a.value,
else => return RuntimeError.InvalidSpirV,
};
if (target.matrix_column) |column| {
switch (value.*) {
.Matrix => |columns| {
if (column >= columns.len) return RuntimeError.OutOfBounds;
return &columns[column];
},
else => return RuntimeError.InvalidValueType,
}
}
return value;
}
pub fn readOutput(self: *const Self, output: []u8, result: SpvWord) RuntimeError!void {
if (std.mem.indexOfScalar(SpvWord, &self.mod.output_locations, result)) |_| {
_ = try self.readValue(output, &self.results[result].variant.?.Variable.value);
for (&self.mod.output_locations) |*location| {
if (std.mem.indexOfScalar(SpvWord, location, result)) |_| {
try self.readResultValue(output, result);
return;
}
}
return RuntimeError.NotFound;
}
pub fn readBuiltIn(self: *const Self, output: []u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| {
try self.readResultValue(output, result);
} else {
return RuntimeError.NotFound;
}
}
pub fn writeInput(self: *const Self, input: []const u8, result: SpvWord) RuntimeError!void {
if (std.mem.indexOfScalar(SpvWord, &self.mod.input_locations, result)) |_| {
_ = try self.writeValue(input, &self.results[result].variant.?.Variable.value);
} else {
return RuntimeError.NotFound;
for (&self.mod.input_locations) |*location| {
if (std.mem.indexOfScalar(SpvWord, location, result)) |_| {
try self.writeResultValue(input, result);
if (self.results[result].variant) |*variant| switch (variant.*) {
.Variable => |*v| v.value.clearExternalData(),
.AccessChain => |*a| a.value.clearExternalData(),
else => {},
};
return;
}
}
return RuntimeError.NotFound;
}
pub fn getInputLocationMemorySize(self: *const Self, location: SpvWord) RuntimeError!usize {
const target = try self.resolveInputLocationTarget(location);
return (try self.getInputLocationTargetValue(target)).getPlainMemorySize();
}
pub fn writeInputLocation(self: *const Self, input: []const u8, location: SpvWord) RuntimeError!void {
const target = try self.resolveInputLocationTarget(location);
const value = try self.getInputLocationTargetValue(target);
_ = try value.write(input);
value.clearExternalData();
}
pub fn writeBuiltIn(self: *const Self, input: []const u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| {
_ = try self.writeValue(input, &self.results[result].variant.?.Variable.value);
try self.writeResultValue(input, result);
} else {
return RuntimeError.NotFound;
}
}
pub fn flushDescriptorSets(self: *const Self, allocator: std.mem.Allocator) RuntimeError!void {
for (self.results) |*result| {
try result.flushPtr(allocator);
}
}
pub fn getResultMemorySize(self: *const Self, result: SpvWord) RuntimeError!usize {
const value = try self.results[result].getConstValue();
return value.getPlainMemorySize();
}
pub fn hasResultDecoration(self: *const Self, result: SpvWord, decoration: spv.SpvDecoration) bool {
for (self.results[result].decorations.items) |result_decoration| {
if (result_decoration.rtype == decoration)
return true;
}
return false;
}
pub fn resetInvocation(self: *Self, allocator: std.mem.Allocator) void {
var derivatives = self.derivatives.iterator();
while (derivatives.next()) |entry| {
entry.value_ptr.deinit(allocator);
}
self.derivatives.clearRetainingCapacity();
for (self.results) |*result| {
if (result.variant) |*variant| {
switch (variant.*) {
.AccessChain => |*access_chain| {
if (std.mem.allEqual(u8, std.mem.asBytes(&access_chain.value), 0xaa)) {
result.variant = null;
continue;
}
access_chain.value.deinit(allocator);
allocator.free(access_chain.indexes);
result.variant = null;
},
.FunctionParameter => |*parameter| {
parameter.value_ptr = null;
},
else => {},
}
}
}
self.reset();
}
fn reset(self: *Self) void {
self.it = self.mod.it;
self.function_stack.clearRetainingCapacity();
self.current_parameter_index = 0;
self.current_function = null;
}
fn readValue(self: *const Self, output: []u8, value: *const Result.Value) RuntimeError!usize {
switch (value.*) {
.Bool => |b| {
output[0] = if (b == true) 1 else 0;
return 1;
},
.Int => |i| {
switch (i.bit_count) {
8 => output[0] = @bitCast(i.value.uint8),
16 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint16)),
32 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint32)),
64 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |f| {
switch (f.bit_count) {
16 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float16)),
32 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float32)),
64 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3f32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2f32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4i32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3i32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2i32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4u32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3u32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2u32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector,
.Matrix,
.Array,
.Structure,
=> |values| {
var offset: usize = 0;
for (values) |v| {
offset += try self.readValue(output[offset..], &v);
}
return offset;
},
.RuntimeArray => |opt_values| if (opt_values) |values| {
var offset: usize = 0;
for (values) |v| {
offset += try self.readValue(output[offset..], &v);
}
return offset;
},
else => return RuntimeError.InvalidValueType,
}
return 0;
}
fn writeValue(self: *const Self, input: []const u8, value: *Result.Value) RuntimeError!usize {
switch (value.*) {
.Bool => |*b| {
b.* = if (input[0] != 0) true else false;
return 1;
},
.Int => |*i| {
switch (i.bit_count) {
8 => i.value.uint8 = @bitCast(input[0]),
16 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint16), input[0..2]),
32 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint32), input[0..4]),
64 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |*f| {
switch (f.bit_count) {
16 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float16), input[0..2]),
32 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float32), input[0..4]),
64 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3f32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2f32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4i32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3i32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2i32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4u32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3u32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2u32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector,
.Matrix,
.Array,
.Structure,
=> |*values| {
var offset: usize = 0;
for (values.*) |*v| {
offset += try self.writeValue(input[offset..], v);
}
return offset;
},
.RuntimeArray => |opt_values| if (opt_values) |*values| {
var offset: usize = 0;
for (values.*) |*v| {
offset += try self.writeValue(input[offset..], v);
}
return offset;
},
else => return RuntimeError.InvalidValueType,
}
return 0;
self.current_label = null;
self.previous_label = null;
}
+1131
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -10,12 +10,14 @@ const Self = @This();
buffer: []const SpvWord,
index: usize,
did_jump: bool,
next_force_skip: ?usize,
pub fn init(buffer: []const SpvWord) Self {
return .{
.buffer = buffer,
.index = 0,
.did_jump = false,
.next_force_skip = null,
};
}
@@ -25,15 +27,38 @@ pub inline fn nextOrNull(self: *Self) ?SpvWord {
return word;
}
/// self.index + index will be automatically skipped
pub inline fn forceSkipIndex(self: *Self, index: SpvWord) void {
self.next_force_skip = self.index + index;
}
pub inline fn nextAsOrNull(self: *Self, comptime E: type) ?E {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
self.next_force_skip = null;
}
}
return if (self.nextOrNull()) |word| std.enums.fromInt(E, word) else null;
}
pub inline fn next(self: *Self) RuntimeError!SpvWord {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
self.next_force_skip = null;
}
}
return self.nextOrNull() orelse return RuntimeError.InvalidSpirV;
}
pub inline fn nextAs(self: *Self, comptime E: type) RuntimeError!E {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
self.next_force_skip = null;
}
}
return self.nextAsOrNull(E) orelse return RuntimeError.InvalidSpirV;
}
@@ -59,6 +84,7 @@ pub inline fn skipN(self: *Self, count: usize) bool {
pub inline fn skipToEnd(self: *Self) void {
self.index = self.buffer.len;
self.did_jump = true;
}
pub inline fn emitSourceLocation(self: *const Self) usize {
@@ -71,3 +97,10 @@ pub inline fn jumpToSourceLocation(self: *Self, source_location: usize) bool {
self.did_jump = true;
return true;
}
/// Like jumpToSourceLocation without toggling self.did_jump
pub inline fn goToSourceLocation(self: *Self, source_location: usize) bool {
if (source_location > self.buffer.len) return false;
self.index = source_location;
return true;
}
+16 -3
View File
@@ -21,7 +21,7 @@
//!
//! try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
//! var output: [4]f32 = undefined;
//! try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color"));
//! try rt.readOutput(std.mem.asBytes(output[0..output.len]), try rt.getResultByName("color"));
//! std.log.info("Output: Vec4{any}", .{output});
//! }
//! std.log.info("Successfully executed", .{});
@@ -30,18 +30,31 @@
const std = @import("std");
pub const Image = @import("Image.zig");
pub const Module = @import("Module.zig");
pub const Runtime = @import("Runtime.zig");
const opcodes = @import("opcodes.zig");
const spv = @import("spv.zig");
pub const spv = @import("spv.zig");
pub const SpvVoid = spv.SpvVoid;
pub const SpvByte = spv.SpvByte;
pub const SpvWord = spv.SpvWord;
pub const SpvBool = spv.SpvBool;
pub const SpvDim = spv.SpvDim;
pub const Vec4f32 = @Vector(4, f32);
pub const Vec3f32 = @Vector(3, f32);
pub const Vec2f32 = @Vector(2, f32);
pub const Vec4i32 = @Vector(4, i32);
pub const Vec3i32 = @Vector(3, i32);
pub const Vec2i32 = @Vector(2, i32);
pub const Vec4u32 = @Vector(4, u32);
pub const Vec3u32 = @Vector(3, u32);
pub const Vec2u32 = @Vector(2, u32);
pub const GLSL_std_450 = @import("GLSL_std_450/opcodes.zig");
/// Maximum number of input locations per module
+4373 -701
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -263,9 +263,9 @@ pub const SpvStorageClass = enum(u32) {
};
pub const SpvDim = enum(u32) {
_1D = 0,
_2D = 1,
_3D = 2,
@"1D" = 0,
@"2D" = 1,
@"3D" = 2,
Cube = 3,
Rect = 4,
Buffer = 5,
+525
View File
@@ -0,0 +1,525 @@
const std = @import("std");
const spv = @import("spv");
const root = @import("root.zig");
const compileNzsl = root.compileNzsl;
const ImageState = struct {
expected_sampler: *anyopaque,
sample_calls: usize = 0,
dref_calls: usize = 0,
last_x: f32 = 0,
last_y: f32 = 0,
last_z: f32 = 0,
last_dref: f32 = 0,
last_lod: ?f32 = null,
last_offset: spv.Runtime.ImageOffset = .{},
};
fn readImageFloat4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn readImageInt4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn writeImageFloat4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: spv.Runtime.Vec4(f32)) spv.Runtime.RuntimeError!void {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn writeImageInt4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: spv.Runtime.Vec4(u32)) spv.Runtime.RuntimeError!void {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn sampleImageFloat4(driver_image: *anyopaque, driver_sampler: *anyopaque, _: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
const state: *ImageState = @ptrCast(@alignCast(driver_image));
if (state.expected_sampler != driver_sampler) return spv.Runtime.RuntimeError.InvalidSpirV;
state.sample_calls += 1;
state.last_x = x;
state.last_y = y;
state.last_z = z;
state.last_lod = lod;
state.last_offset = offset;
return .{ .x = x, .y = y, .z = 9.0, .w = 1.0 };
}
fn sampleImageInt4(_: *anyopaque, _: *anyopaque, _: spv.spv.SpvDim, _: f32, _: f32, _: f32, _: ?f32, _: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, _: spv.spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 {
const state: *ImageState = @ptrCast(@alignCast(driver_image));
if (state.expected_sampler != driver_sampler) return spv.Runtime.RuntimeError.InvalidSpirV;
state.dref_calls += 1;
state.last_x = x;
state.last_y = y;
state.last_z = z;
state.last_dref = dref;
state.last_lod = lod;
state.last_offset = offset;
return dref + x + y;
}
fn queryImageSize(_: *anyopaque, _: spv.spv.SpvDim, _: bool, _: ?i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn queryImageLevels(_: *anyopaque) spv.Runtime.RuntimeError!u32 {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn queryImageSamples(_: *anyopaque) spv.Runtime.RuntimeError!u32 {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
fn queryImageLod(_: *anyopaque, _: *anyopaque, _: spv.spv.SpvDim, _: spv.Runtime.ImageDerivatives) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
return spv.Runtime.RuntimeError.UnsupportedSpirV;
}
const image_api: spv.Runtime.ImageAPI = .{
.readImageFloat4 = readImageFloat4,
.readImageInt4 = readImageInt4,
.writeImageFloat4 = writeImageFloat4,
.writeImageInt4 = writeImageInt4,
.sampleImageFloat4 = sampleImageFloat4,
.sampleImageInt4 = sampleImageInt4,
.sampleImageDref = sampleImageDref,
.queryImageSize = queryImageSize,
.queryImageLevels = queryImageLevels,
.queryImageSamples = queryImageSamples,
.queryImageLod = queryImageLod,
};
fn initModule(allocator: std.mem.Allocator, shader: []const u8) !struct { code: []const u32, module: spv.Module } {
const code = try compileNzsl(allocator, shader);
errdefer allocator.free(code);
const module = try spv.Module.init(allocator, code, .{
.use_simd_vectors_specializations = false,
});
return .{ .code = code, .module = module };
}
test "Runtime API lifecycle" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragIn
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main(input: FragIn) -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = input.color;
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
const input_result = try rt.getResultByLocationComponent(0, 0, .input);
const output_result = try rt.getResultByName("color");
const output_location_result = try rt.getResultByLocationComponent(0, 0, .output);
try std.testing.expectEqual(input_result, try rt.getResultByLocation(0, .input));
try std.testing.expectEqual(output_location_result, try rt.getResultByLocation(0, .output));
try std.testing.expectEqual(@as(usize, 16), try rt.getInputLocationMemorySize(0));
try std.testing.expectEqual(@as(usize, 16), try rt.getResultMemorySize(output_result));
try std.testing.expectEqual(@as(usize, 16), try rt.getResultMemorySize(output_location_result));
try std.testing.expectEqual(.Float, try rt.getResultPrimitiveType(output_result));
try std.testing.expect(rt.hasResultDecoration(output_result, .Location));
const input = [_]f32{ 10.0, 20.0, 30.0, 40.0 };
try rt.writeInputLocation(std.mem.asBytes(&input), 0);
const entry = try rt.getEntryPointByName("main");
try std.testing.expectEqual(.completed, try rt.beginEntryPoint(allocator, entry));
var output: [4]f32 = undefined;
try rt.readOutput(std.mem.asBytes(&output), output_location_result);
try std.testing.expectEqualSlices(f32, &input, &output);
rt.resetInvocation(allocator);
}
test "Module binding writes" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ [layout(std430)]
\\ struct SSBO
\\ {
\\ value: u32
\\ }
\\
\\ external
\\ {
\\ [set(2), binding(3)] ssbo: storage[SSBO],
\\ }
\\
\\ [entry(compute)]
\\ [workgroup(1, 1, 1)]
\\ fn main()
\\ {
\\ ssbo.value = ssbo.value + 7;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
const binding_result = compiled.module.getBindingResult(2, 3) orelse return error.TestExpectedEqual;
try std.testing.expectEqual(@as(?spv.SpvWord, null), compiled.module.getBindingResult(0, 0));
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
var storage: u32 = 35;
try rt.writeDescriptorSet(std.mem.asBytes(&storage), 2, 3, 0);
try std.testing.expectEqual(error.NotFound, rt.writeDescriptorSet(std.mem.asBytes(&storage), 2, 4, 0));
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
try rt.flushDescriptorSets(allocator);
_ = binding_result;
try std.testing.expectEqual(@as(u32, 42), storage);
}
test "Push constants" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct Data
\\ {
\\ color: vec4[f32]
\\ }
\\
\\ external
\\ {
\\ data: push_constant[Data]
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = data.color;
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
const push_constants = [_]f32{ 0.125, 0.25, 0.5, 1.0 };
try rt.populatePushConstants(std.mem.asBytes(&push_constants));
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByLocation(0, .output));
try std.testing.expectEqualSlices(f32, &push_constants, &output);
}
test "Built-in inputs and outputs" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct VertIn
\\ {
\\ [builtin(vertex_index)] vertex_index: i32
\\ }
\\
\\ struct VertOut
\\ {
\\ [builtin(position)] position: vec4[f32]
\\ }
\\
\\ [entry(vert)]
\\ fn main(input: VertIn) -> VertOut
\\ {
\\ let value = f32(input.vertex_index);
\\ let output: VertOut;
\\ output.position = vec4[f32](value, value + 1.0, value + 2.0, 1.0);
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
const vertex_index: i32 = 7;
try rt.writeBuiltIn(std.mem.asBytes(&vertex_index), .VertexIndex);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var position: [4]f32 = undefined;
try rt.readBuiltIn(std.mem.asBytes(&position), .Position);
try std.testing.expectEqualSlices(f32, &.{ 7.0, 8.0, 9.0, 1.0 }, &position);
}
test "Integer output metadata" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] value: u32
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.value = 0xA5A5_A5A5;
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
const output_result = try rt.getResultByLocation(0, .output);
try std.testing.expectEqual(.UInt, try rt.getResultPrimitiveType(output_result));
try std.testing.expectEqual(@as(usize, 4), try rt.getResultMemorySize(output_result));
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: u32 = 0;
try rt.readOutput(std.mem.asBytes(&output), output_result);
try std.testing.expectEqual(@as(u32, 0xA5A5_A5A5), output);
}
test "Runtime API error paths" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = vec4[f32](1.0, 2.0, 3.0, 4.0);
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
try std.testing.expectEqual(error.NotFound, rt.getEntryPointByName("missing"));
try std.testing.expectEqual(error.NotFound, rt.getResultByName("missing"));
try std.testing.expectEqual(error.NotFound, rt.getResultByLocation(31, .input));
try std.testing.expectEqual(error.NotFound, rt.getInputLocationMemorySize(31));
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var too_small: [3]u8 = undefined;
try std.testing.expectEqual(error.OutOfBounds, rt.readOutput(&too_small, try rt.getResultByLocation(0, .output)));
}
test "Derivative memory buffers" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragIn
\\ {
\\ [location(0)] normal: vec3[f32]
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main(input: FragIn) -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = vec4[f32](input.normal.x, input.normal.y, input.normal.z, 1.0);
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
const input_result = try rt.getResultByLocation(0, .input);
const dx = [_]f32{ -1.0, 2.0, -3.0 };
const dy = [_]f32{ 4.0, -5.0, 6.0 };
const short_dx = [_]f32{ -1.0, 2.0 };
try std.testing.expectEqual(error.OutOfBounds, rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&short_dx), std.mem.asBytes(&dy)));
const output_result = try rt.getResultByName("color");
try rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&dx), std.mem.asBytes(&dy));
try rt.copyDerivative(allocator, output_result, input_result);
rt.clearDerivative(allocator, input_result);
rt.clearDerivative(allocator, output_result);
const input = [_]f32{ 1.0, 2.0, 3.0 };
try rt.writeInput(std.mem.asBytes(&input), input_result);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByLocation(0, .output));
try std.testing.expectEqualSlices(f32, &.{ 1.0, 2.0, 3.0, 1.0 }, &output);
try rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&dx), std.mem.asBytes(&dy));
try rt.copyDerivative(allocator, output_result, input_result);
rt.clearDerivative(allocator, output_result);
}
test "Image sampling callback" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ external
\\ {
\\ [set(0), binding(0)] tex: sampler2D[f32],
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = tex.Sample(vec2[f32](0.25, 0.75));
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var sampler: u8 = 0;
var image_state: ImageState = .{ .expected_sampler = &sampler };
var descriptor = [_]usize{
@intFromPtr(&image_state),
@intFromPtr(&sampler),
};
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
try rt.writeDescriptorSet(std.mem.asBytes(&descriptor), 0, 0, 0);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByName("color"));
try std.testing.expectEqualSlices(f32, &.{ 0.25, 0.75, 9.0, 1.0 }, &output);
try std.testing.expectEqual(@as(usize, 1), image_state.sample_calls);
try std.testing.expectEqual(@as(?f32, null), image_state.last_lod);
try std.testing.expectEqual(spv.Runtime.ImageOffset{}, image_state.last_offset);
}
test "Depth sampling dref callback" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ external
\\ {
\\ [set(0), binding(0)] tex: depth_sampler2D[f32],
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let value = tex.SampleDepthComp(vec2[f32](0.25, 0.75), 0.5);
\\ let output: FragOut;
\\ output.color = vec4[f32](value, 0.0, 0.0, 1.0);
\\ return output;
\\ }
;
var compiled = try initModule(allocator, shader);
defer allocator.free(compiled.code);
defer compiled.module.deinit(allocator);
var sampler: u8 = 0;
var image_state: ImageState = .{ .expected_sampler = &sampler };
var descriptor = [_]usize{
@intFromPtr(&image_state),
@intFromPtr(&sampler),
};
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
defer rt.deinit(allocator);
try rt.writeDescriptorSet(std.mem.asBytes(&descriptor), 0, 0, 0);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByName("color"));
try std.testing.expectEqualSlices(f32, &.{ 1.5, 0.0, 0.0, 1.0 }, &output);
try std.testing.expectEqual(@as(usize, 1), image_state.dref_calls);
try std.testing.expectEqual(@as(f32, 0.5), image_state.last_dref);
}
+45 -1
View File
@@ -26,5 +26,49 @@ test "Simple array" {
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ 4, 3, 2, 1 });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 4, 3, 2, 1 }),
},
});
}
test "Array fold" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let values = array[f32](1.0, 2.0, 3.0, 4.0);
\\ let sum = 0.0;
\\ let weighted = 0.0;
\\ for i in u32(0) -> values.Size()
\\ {
\\ sum += values[i];
\\ weighted += values[i] * f32(i + 1);
\\ }
\\
\\ let output: FragOut;
\\ output.color = vec4[f32](sum, weighted, values[2], f32(values.Size()));
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 10.0, 30.0, 3.0, 4.0 }),
},
});
}
+6 -1
View File
@@ -25,5 +25,10 @@ test "Simple fragment shader" {
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ 4, 3, 2, 1 });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 4, 3, 2, 1 }),
},
});
}
+52 -3
View File
@@ -73,7 +73,12 @@ test "Bitwise primitives" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
@@ -96,7 +101,7 @@ test "Bitwise vectors" {
inline for (types) |T| {
const op1: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
var op2: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
for (0..L) |i| op2.val[i] = @mod(op2.val[i], @bitSizeOf(T));
inline for (0..L) |i| op2.val[i] = @mod(op2.val[i], @bitSizeOf(T));
const expected = switch (op.key) {
.BitwiseAnd => op1.val & op2.val,
.BitwiseOr => op1.val | op2.val,
@@ -142,8 +147,52 @@ test "Bitwise vectors" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, L, code, "color", &@as([L]T, expected));
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
}
test "Bit mix" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[u32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let a: u32 = 0xF0F0_F0F0;
\\ let b: u32 = 0x0F0F_00FF;
\\ let c = ((a & b) << 4) | ((a ^ b) >> 8);
\\ let d = (c & 0xFFFF) ^ 0x55AA;
\\ let output: FragOut;
\\ output.color = vec4[u32](a & b, a | b, c, d);
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]u32{
0x0000_00F0,
0xFFFF_F0FF,
((0xF0F0_F0F0 & 0x0F0F_00FF) << 4) | ((0xF0F0_F0F0 ^ 0x0F0F_00FF) >> 8),
((((0xF0F0_F0F0 & 0x0F0F_00FF) << 4) | ((0xF0F0_F0F0 ^ 0x0F0F_00FF) >> 8)) & 0xFFFF) ^ 0x55AA,
}),
},
});
}
+56 -1
View File
@@ -93,8 +93,63 @@ test "Simple branching" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
}
test "Nested if" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[i32]
\\ }
\\
\\ fn classify(value: i32) -> i32
\\ {
\\ if (value < 0)
\\ {
\\ if ((value % 2) == 0)
\\ return -2;
\\ else
\\ return -1;
\\ }
\\ else if (value == 0)
\\ return 0;
\\ else
\\ {
\\ if ((value % 2) == 0)
\\ return 2;
\\ else
\\ return 1;
\\ }
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = vec4[i32](classify(-4), classify(-3), classify(0), classify(5));
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]i32{ -2, -1, 0, 1 }),
},
});
}
+44 -2
View File
@@ -55,7 +55,12 @@ test "Primitives casts" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T[1], 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T[1]{ expected, expected, expected, expected }),
},
});
}
}
@@ -103,6 +108,43 @@ test "Primitives bitcasts" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T[1], 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T[1]{ expected, expected, expected, expected }),
},
});
}
}
test "Cast chain" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[i32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let v = vec4[f32](1.25, 2.75, -3.25, 4.5);
\\ let a = vec4[i32](v);
\\ let output: FragOut;
\\ output.color = vec4[i32](a.x, a.y, a.z, a.w);
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]i32{ 1, 2, -3, 4 }),
},
});
}
+99 -2
View File
@@ -44,7 +44,12 @@ test "Simple function calls" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ n, n, n, n });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ n, n, n, n }),
},
});
}
}
@@ -95,6 +100,98 @@ test "Nested function calls" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ n, n, n, n });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ n, n, n, n }),
},
});
}
}
test "Function params" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ fn affine(value: f32, scale: f32, bias: f32) -> f32
\\ {
\\ return value * scale + bias;
\\ }
\\
\\ fn combine(a: vec2[f32], b: vec2[f32]) -> vec4[f32]
\\ {
\\ let left = affine(a.x, b.x, b.y);
\\ let right = affine(a.y, b.y, b.x);
\\ return vec4[f32](left, right, left + right, left - right);
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let output: FragOut;
\\ output.color = combine(vec2[f32](2.0, 3.0), vec2[f32](4.0, 5.0));
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 13.0, 19.0, 32.0, -6.0 }),
},
});
}
test "Struct logic" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct Pair
\\ {
\\ a: f32,
\\ b: f32
\\ }
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ fn eval(pair: Pair) -> vec2[f32]
\\ {
\\ return vec2[f32](pair.a + pair.b, pair.a * pair.b);
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let pair: Pair;
\\ pair.a = 3.0;
\\ pair.b = 4.0;
\\ let v = eval(pair);
\\ let output: FragOut;
\\ output.color = vec4[f32](v.x, v.y, v.y - v.x, v.x + v.y);
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 7.0, 12.0, 5.0, 19.0 }),
},
});
}
+9 -1
View File
@@ -45,7 +45,15 @@ test "Inputs" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutputWithInput(T, L, code, "color", &@as([L]T, input.val), "pos", &@as([L]T, input.val));
try case.expect(.{
.source = code,
.inputs = &.{
std.mem.asBytes(&@as([L]T, input.val)),
},
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, input.val)),
},
});
}
}
}
+50 -1
View File
@@ -47,5 +47,54 @@ test "Simple while loop" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ expected, expected, expected, expected }),
},
});
}
test "For filter" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[u32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let even_sum: u32 = 0;
\\ let odd_sum: u32 = 0;
\\ let product: u32 = 1;
\\ for i in u32(1) -> u32(8)
\\ {
\\ if ((i % u32(2)) == u32(0))
\\ {
\\ even_sum += i;
\\ product *= i;
\\ }
\\ else
\\ odd_sum += i;
\\ }
\\
\\ let output: FragOut;
\\ output.color = vec4[u32](even_sum, odd_sum, product, even_sum + odd_sum);
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]u32{ 12, 16, 48, 28 }),
},
});
}
+257 -2
View File
@@ -1,5 +1,6 @@
const std = @import("std");
const root = @import("root.zig");
const zm = @import("zmath");
const compileNzsl = root.compileNzsl;
const case = root.case;
@@ -72,7 +73,12 @@ test "Maths primitives" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
@@ -139,8 +145,257 @@ test "Maths vectors" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, L, code, "color", &@as([L]T, expected));
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
}
// Tests all mathematical operation on vec2/3/4 with scalars with all NZSL supported primitive types
test "Maths vectors with scalars" {
const allocator = std.testing.allocator;
const types = [_]type{ f32, f64, i32, u32 };
var operations = std.EnumMap(Operations, u8).init(.{
.Mul = '*',
.Div = '/',
.Mod = '%',
});
var it = operations.iterator();
while (it.next()) |op| {
inline for (2..5) |L| {
inline for (types) |T| {
const base_color: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
const ratio = case.random(T);
const splat_ratio = @as(@Vector(L, T), @splat(ratio));
const expected = switch (op.key) {
.Mul => if (@typeInfo(T) == .int) @mulWithOverflow(base_color.val, splat_ratio)[0] else base_color.val * splat_ratio,
.Div => if (@typeInfo(T) == .int) @divTrunc(base_color.val, splat_ratio) else base_color.val / splat_ratio,
.Mod => @mod(base_color.val, splat_ratio),
else => unreachable,
};
const shader = try std.fmt.allocPrint(
allocator,
\\ [nzsl_version("1.1")]
\\ [feature(float64)]
\\ module;
\\
\\ struct FragOut
\\ {{
\\ [location(0)] color: vec{d}[{s}]
\\ }}
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {{
\\ let output: FragOut;
\\ output.color = vec{d}[{s}]({f}) {c} {d};
\\ return output;
\\ }}
,
.{
L,
@typeName(T),
L,
@typeName(T),
base_color,
op.value.*,
ratio,
},
);
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
}
// Tests all mathematical operation on mat3/4 with all NZSL supported primitive types
test "Maths matrices" {
const allocator = std.testing.allocator;
const types = [_]type{ f32, f64 };
var operations = std.EnumMap(Operations, u8).init(.{
.Add = '+',
.Sub = '-',
.Mul = '*',
});
var it = operations.iterator();
while (it.next()) |op| {
inline for (3..5) |L| {
inline for (types) |T| {
const base: case.Mat(L, T) = .{ .val = case.random([L][L]T) };
const ratio: case.Mat(L, T) = .{ .val = case.random([L][L]T) };
var expected: case.Mat(L, T) = undefined;
for (expected.val[0..], base.val[0..], ratio.val[0..]) |*ec, bc, rc| {
for (ec[0..], bc[0..], rc[0..]) |*e, b, r| {
e.* = switch (op.key) {
.Add => b + r,
.Sub => b - r,
.Mul => 0,
else => unreachable,
};
}
}
if (op.key == .Mul) {
for (0..L) |column_index| {
for (0..L) |row_index| {
for (0..L) |inner_index| {
expected.val[column_index][row_index] += base.val[inner_index][row_index] * ratio.val[column_index][inner_index];
}
}
}
}
const shader = try std.fmt.allocPrint(
allocator,
\\ [nzsl_version("1.1")]
\\ [feature(float64)]
\\ module;
\\
\\ struct FragOut
\\ {{
\\ [location(0)] value: mat{d}[{s}]
\\ }}
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {{
\\ let output: FragOut;
\\ output.value = mat{d}[{s}]({f}) {c} mat{d}[{s}]({f});
\\ return output;
\\ }}
,
.{
L,
@typeName(T),
L,
@typeName(T),
base,
op.value.*,
L,
@typeName(T),
ratio,
},
);
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&expected),
},
});
}
}
}
}
// Tests all mathematical operation on mat3/4 with all NZSL supported vectors
test "Maths matrices with vectors" {
const allocator = std.testing.allocator;
const types = [_]type{ f32, f64 };
inline for (3..5) |L| {
inline for (types) |T| {
const base: case.Mat(L, T) = .{ .val = case.random([L][L]T) };
const ratio: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
var expected: @Vector(L, T) = undefined;
expected = @splat(0);
inline for (0..L) |row_index| {
inline for (0..L) |column_index| {
expected[row_index] += base.val[column_index][row_index] * ratio.val[column_index];
}
}
const shader = try std.fmt.allocPrint(
allocator,
\\ [nzsl_version("1.1")]
\\ [feature(float64)]
\\ module;
\\
\\ struct FragOut
\\ {{
\\ [location(0)] value: vec{d}[{s}]
\\ }}
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {{
\\ let output: FragOut;
\\ output.value = mat{d}[{s}]({f}) * vec{d}[{s}]({f});
\\ return output;
\\ }}
,
.{
L,
@typeName(T),
L,
@typeName(T),
base,
L,
@typeName(T),
ratio,
},
);
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
test "Swizzle" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[f32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let v = vec4[f32](1.0, 2.0, 3.0, 4.0);
\\ let a = v.yx;
\\ let b = v.wz;
\\ let output: FragOut;
\\ output.color = vec4[f32](a.x, a.y, b.x, b.y);
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 2.0, 1.0, 4.0, 3.0 }),
},
});
}
+61 -37
View File
@@ -20,65 +20,65 @@ pub fn compileNzsl(allocator: std.mem.Allocator, source: []const u8) ![]const u3
}
pub const case = struct {
pub fn expectOutput(comptime T: type, comptime len: usize, source: []const u32, output_name: []const u8, expected: []const T) !void {
const allocator = std.testing.allocator;
pub const Config = struct {
source: []const u32,
inputs: []const []const u8 = &.{},
expected_outputs: []const []const u8 = &.{},
descriptor_sets: []const []const []u8 = &.{},
expected_descriptor_sets: []const []const []const u8 = &.{},
};
const module_options = [_]spv.Module.ModuleOptions{
.{
.use_simd_vectors_specializations = true,
},
.{
.use_simd_vectors_specializations = false,
},
};
for (module_options) |opt| {
var module = try spv.Module.init(allocator, source, opt);
defer module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &module);
defer rt.deinit(allocator);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [len]T = undefined;
try rt.readOutput(std.mem.sliceAsBytes(output[0..]), try rt.getResultByName(output_name));
try std.testing.expectEqualSlices(T, expected, &output);
}
}
pub fn expectOutputWithInput(comptime T: type, comptime len: usize, source: []const u32, output_name: []const u8, expected: []const T, input_name: []const u8, input: []const T) !void {
pub fn expect(config: Config) !void {
const allocator = std.testing.allocator;
// To test with all important module options
const module_options = [_]spv.Module.ModuleOptions{
.{
.use_simd_vectors_specializations = true,
.use_simd_vectors_specializations = false,
},
.{
.use_simd_vectors_specializations = false,
.use_simd_vectors_specializations = true,
},
};
for (module_options) |opt| {
var module = try spv.Module.init(allocator, source, opt);
var module = try spv.Module.init(allocator, config.source, opt);
defer module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &module);
var rt = try spv.Runtime.init(allocator, &module, undefined);
defer rt.deinit(allocator);
try rt.writeInput(std.mem.sliceAsBytes(input[0..len]), try rt.getResultByName(input_name));
for (config.inputs, 0..) |input, n| {
try rt.writeInput(input[0..], module.input_locations[n][0]);
}
for (config.descriptor_sets, 0..) |descriptor_set, set_index| {
for (descriptor_set, 0..) |descriptor_binding, binding_index| {
try rt.writeDescriptorSet(descriptor_binding, @intCast(set_index), @intCast(binding_index), 0);
}
}
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [len]T = undefined;
try rt.readOutput(std.mem.sliceAsBytes(output[0..]), try rt.getResultByName(output_name));
try rt.flushDescriptorSets(allocator);
try std.testing.expectEqualSlices(T, expected, &output);
for (config.expected_outputs, 0..) |expected, n| {
const output = try allocator.alloc(u8, expected.len);
defer allocator.free(output);
try rt.readOutput(output[0..], module.output_locations[n][0]);
try std.testing.expectEqualSlices(u8, expected, output);
}
for (config.expected_descriptor_sets, config.descriptor_sets) |expected_descriptor_set, descriptor_set| {
for (expected_descriptor_set, descriptor_set) |expected_descriptor_binding, descriptor_binding| {
try std.testing.expectEqualSlices(u8, expected_descriptor_binding, descriptor_binding);
}
}
}
}
pub fn random(comptime T: type) T {
var prng: std.Random.DefaultPrng = .init(@intCast(std.time.microTimestamp()));
var prng: std.Random.DefaultPrng = .init(@intCast(std.Io.Timestamp.now(std.testing.io, .real).toNanoseconds()));
const rand = prng.random();
return switch (@typeInfo(T)) {
@@ -86,11 +86,18 @@ pub const case = struct {
.float => rand.float(T),
.vector => |v| blk: {
var vec: @Vector(v.len, v.child) = undefined;
for (0..v.len) |i| {
inline for (0..v.len) |i| {
vec[i] = random(v.child);
}
break :blk vec;
},
.array => |a| blk: {
var arr: [a.len]a.child = undefined;
inline for (0..a.len) |i| {
arr[i] = random(a.child);
}
break :blk arr;
},
inline else => unreachable,
};
}
@@ -107,9 +114,25 @@ pub const case = struct {
}
};
}
pub fn Mat(comptime len: usize, comptime T: type) type {
return struct {
const Self = @This();
val: [len][len]T,
pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
inline for (0..len) |i| {
inline for (0..len) |j| {
try w.print("{d}", .{self.val[i][j]});
if (i < len - 1 or j < len - 1) try w.writeAll(", ");
}
}
}
};
}
};
test {
std.testing.refAllDecls(@import("api.zig"));
std.testing.refAllDecls(@import("arrays.zig"));
std.testing.refAllDecls(@import("basics.zig"));
std.testing.refAllDecls(@import("bitwise.zig"));
@@ -119,4 +142,5 @@ test {
std.testing.refAllDecls(@import("inputs.zig"));
std.testing.refAllDecls(@import("loops.zig"));
std.testing.refAllDecls(@import("maths.zig"));
std.testing.refAllDecls(@import("ssbo.zig"));
}
+108
View File
@@ -0,0 +1,108 @@
const std = @import("std");
const root = @import("root.zig");
const compileNzsl = root.compileNzsl;
const case = root.case;
test "SSBO read" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ [layout(std430)]
\\ struct SSBO
\\ {
\\ data: dyn_array[u32]
\\ }
\\
\\ external
\\ {
\\ [set(0), binding(0)] ssbo: storage[SSBO],
\\ }
\\
\\ [entry(compute)]
\\ [workgroup(16, 1, 1)]
\\ fn main()
\\ {
\\ for i in 0 -> 256
\\ {
\\ ssbo.data[i] = u32(i);
\\ }
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
var ssbo = [_]u32{0} ** 256;
var expected = [_]u32{0} ** 256;
for (expected[0..], 0..) |*val, i| {
val.* = @intCast(i);
}
try case.expect(.{
.source = code,
.descriptor_sets = &.{
// Set 0
&.{
// Binding 0
std.mem.asBytes(&ssbo),
},
},
.expected_descriptor_sets = &.{
// Set 0
&.{
// Binding 0
std.mem.asBytes(&expected),
},
},
});
}
test "SSBO write" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ [layout(std430)]
\\ struct SSBO
\\ {
\\ data: dyn_array[u32]
\\ }
\\
\\ external
\\ {
\\ [set(0), binding(0)] ssbo: storage[SSBO],
\\ }
\\
\\ [entry(compute)]
\\ [workgroup(1, 1, 1)]
\\ fn main()
\\ {
\\ for i in u32(2) -> u32(8)
\\ {
\\ ssbo.data[i] = ssbo.data[i - u32(1)] + ssbo.data[i - u32(2)];
\\ }
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
var ssbo = [_]u32{ 1, 1, 0, 0, 0, 0, 0, 0 };
const expected = [_]u32{ 1, 1, 2, 3, 5, 8, 13, 21 };
try case.expect(.{
.source = code,
.descriptor_sets = &.{
&.{
std.mem.asBytes(&ssbo),
},
},
.expected_descriptor_sets = &.{
&.{
std.mem.asBytes(&expected),
},
},
});
}
+21 -76
View File
@@ -16,11 +16,8 @@ pub fn main() !void {
const allocator = fba.allocator();
const env = Env.init(allocator);
defer env.deinit(allocator);
var slowest = SlowTracker.init(allocator, 5);
defer slowest.deinit();
defer slowest.deinit(allocator);
var pass: usize = 0;
var fail: usize = 0;
@@ -46,13 +43,6 @@ pub fn main() !void {
var status = Status.pass;
slowest.startTiming();
const is_unnamed_test = isUnnamed(t);
if (env.filter) |f| {
if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) {
continue;
}
}
const friendly_name = blk: {
const name = t.name;
var it = std.mem.splitScalar(u8, name, '.');
@@ -70,7 +60,7 @@ pub fn main() !void {
const result = t.func();
current_test = null;
const ns_taken = slowest.endTiming(friendly_name);
const ns_taken = slowest.endTiming(allocator, friendly_name);
if (std.testing.allocator_instance.deinit() == .leak) {
leak += 1;
@@ -89,20 +79,13 @@ pub fn main() !void {
fail += 1;
Printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER });
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
if (env.fail_first) {
break;
std.debug.dumpErrorReturnTrace(trace);
}
},
}
if (env.verbose) {
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
Printer.status(status, "\x1b[35m[{d: >10.2} ms]\x1b[0m {s: <30}", .{ ms, friendly_name });
} else {
Printer.status(status, ".", .{});
}
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
Printer.status(status, "\x1b[35m[{d: >10.2} ms]\x1b[0m {s: <30}", .{ ms, friendly_name });
}
for (builtin.test_functions) |t| {
@@ -126,7 +109,7 @@ pub fn main() !void {
Printer.fmt("\n", .{});
try slowest.display();
Printer.fmt("\n", .{});
std.posix.exit(if (fail == 0) 0 else 1);
std.process.exit(if (fail == 0) 0 else 1);
}
const Printer = struct {
@@ -155,42 +138,41 @@ const SlowTracker = struct {
const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming);
max: usize,
slowest: SlowestQueue,
timer: std.time.Timer,
timer: std.Io.Timestamp,
fn init(allocator: Allocator, count: u32) SlowTracker {
const timer = std.time.Timer.start() catch @panic("failed to start timer");
var slowest = SlowestQueue.init(allocator, {});
slowest.ensureTotalCapacity(count) catch @panic("OOM");
var slowest = SlowestQueue.empty;
slowest.ensureTotalCapacity(allocator, count) catch @panic("OOM");
return .{
.max = count,
.timer = timer,
.timer = std.Io.Timestamp.now(std.testing.io, .real),
.slowest = slowest,
};
}
const TestInfo = struct {
ns: u64,
ns: i96,
name: []const u8,
};
fn deinit(self: SlowTracker) void {
self.slowest.deinit();
fn deinit(self: *SlowTracker, allocator: std.mem.Allocator) void {
self.slowest.deinit(allocator);
}
fn startTiming(self: *SlowTracker) void {
self.timer.reset();
self.timer = std.Io.Timestamp.now(std.testing.io, .real);
}
fn endTiming(self: *SlowTracker, test_name: []const u8) u64 {
var timer = self.timer;
const ns = timer.lap();
fn endTiming(self: *SlowTracker, allocator: std.mem.Allocator, test_name: []const u8) i96 {
const duration = self.timer.untilNow(std.testing.io, .real);
const ns = duration.toNanoseconds();
var slowest = &self.slowest;
if (slowest.count() < self.max) {
// Capacity is fixed to the # of slow tests we want to track
// If we've tracked fewer tests than this capacity, than always add
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
slowest.push(allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
@@ -205,8 +187,8 @@ const SlowTracker = struct {
}
// the previous fastest of our slow tests, has been pushed off.
_ = slowest.removeMin();
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
_ = slowest.popMin();
slowest.push(allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
@@ -214,7 +196,7 @@ const SlowTracker = struct {
var slowest = self.slowest;
const count = slowest.count();
Printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" });
while (slowest.removeMinOrNull()) |info| {
while (slowest.popMin()) |info| {
const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0;
Printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name });
}
@@ -226,43 +208,6 @@ const SlowTracker = struct {
}
};
const Env = struct {
verbose: bool,
fail_first: bool,
filter: ?[]const u8,
fn init(allocator: Allocator) Env {
return .{
.verbose = readEnvBool(allocator, "TEST_VERBOSE", true),
.fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false),
.filter = readEnv(allocator, "TEST_FILTER"),
};
}
fn deinit(self: Env, allocator: Allocator) void {
if (self.filter) |f| {
allocator.free(f);
}
}
fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 {
const v = std.process.getEnvVarOwned(allocator, key) catch |err| {
if (err == error.EnvironmentVariableNotFound) {
return null;
}
std.log.warn("failed to get env var {s} due to err {}", .{ key, err });
return null;
};
return v;
}
fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool {
const value = readEnv(allocator, key) orelse return deflt;
defer allocator.free(value);
return std.ascii.eqlIgnoreCase(value, "true");
}
};
pub const panic = std.debug.FullPanic(struct {
pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn {
if (current_test) |ct| {
+222
View File
@@ -0,0 +1,222 @@
#include <stdio.h>
#include <SpirvInterpreter.h>
#define CHECK_RESULT(expr) do { \
SpvResult check_result = (expr); \
if (check_result != SPV_RESULT_SUCCESS) \
{ \
fprintf(stderr, "%s failed with %d\n", #expr, check_result); \
return -1; \
} \
} while (0)
static SpvResult ReadImageFloat4(SpvReadImageInfo info, SpvVec4f* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult ReadImageInt4(SpvReadImageInfo info, SpvVec4u* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult WriteImageFloat4(SpvWriteImageInfo info, SpvVec4f src)
{
(void)info;
(void)src;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult WriteImageInt4(SpvWriteImageInfo info, SpvVec4u src)
{
(void)info;
(void)src;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult SampleImageFloat4(SpvSampleImageInfo info, SpvVec4f* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult SampleImageInt4(SpvSampleImageInfo info, SpvVec4u* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult SampleImageDref(SpvSampleImageInfo info, float dref, float* dst)
{
(void)info;
(void)dref;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult QueryImageSize(SpvQueryImageInfo info, SpvVec4u* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult QueryImageLevels(void* driver_image, unsigned long* dst)
{
(void)driver_image;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult QueryImageSamples(void* driver_image, unsigned long* dst)
{
(void)driver_image;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static SpvResult QueryImageLod(SpvQueryImageLodInfo info, SpvVec4f* dst)
{
(void)info;
(void)dst;
return SPV_RESULT_UNSUPPORTED_SPIRV;
}
static const unsigned char shader_source[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x82, 0x10, 0x27, 0x00, 0x17, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x10, 0x00, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x04, 0x00, 0x05, 0x00, 0x56, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x15, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40,
0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x08, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00,
0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x15, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
};
int main(void)
{
SpvModule module;
SpvModuleOptions options;
options.use_simd_vectors_specializations = 1;
if(SpvInitModule(&module, (SpvWord*)shader_source, sizeof(shader_source) / 4, options) != SPV_RESULT_SUCCESS)
{
fprintf(stderr, "Module init failed\n");
return -1;
}
SpvWord binding_result = 0;
if (SpvModuleGetBindingResult(module, 0, 0, &binding_result) != SPV_RESULT_NOT_FOUND)
{
fprintf(stderr, "Unexpected binding lookup result\n");
return -1;
}
SpvImageAPI image_api = {
.SpvReadImageFloat4 = ReadImageFloat4,
.SpvReadImageInt4 = ReadImageInt4,
.SpvWriteImageFloat4 = WriteImageFloat4,
.SpvWriteImageInt4 = WriteImageInt4,
.SpvSampleImageFloat4 = SampleImageFloat4,
.SpvSampleImageInt4 = SampleImageInt4,
.SpvSampleImageDref = SampleImageDref,
.SpvQueryImageSize = QueryImageSize,
.SpvQueryImageLevels = QueryImageLevels,
.SpvQueryImageSamples = QueryImageSamples,
.SpvQueryImageLod = QueryImageLod
};
SpvRuntime runtime;
if(SpvInitRuntime(&runtime, module, image_api) != SPV_RESULT_SUCCESS)
{
fprintf(stderr, "Runtime init failed\n");
return -1;
}
SpvRuntime second_runtime;
if(SpvInitRuntime(&second_runtime, module, image_api) != SPV_RESULT_SUCCESS)
{
fprintf(stderr, "Second runtime init failed\n");
SpvDeinitRuntime(runtime);
return -1;
}
unsigned int spec_value = 64;
SpvRuntimeSpecializationEntry spec_entry = {
.id = 0,
.offset = 0,
.size = sizeof(spec_value)
};
CHECK_RESULT(SpvAddSpecializationInfo(runtime, spec_entry, (const SpvByte*)&spec_value, sizeof(spec_value)));
CHECK_RESULT(SpvCopySpecializationConstantsFrom(second_runtime, runtime));
SpvWord main_entry_index;
CHECK_RESULT(SpvGetEntryPointByName(runtime, "main", &main_entry_index));
CHECK_RESULT(SpvCallEntryPoint(runtime, main_entry_index));
float output[4];
SpvWord output_result;
CHECK_RESULT(SpvGetResultByName(runtime, "color", &output_result));
CHECK_RESULT(SpvReadOutput(runtime, (SpvByte*)output, sizeof(output), output_result));
SpvSize output_size = 0;
SpvPrimitiveType primitive_type;
CHECK_RESULT(SpvGetResultMemorySize(runtime, output_result, &output_size));
CHECK_RESULT(SpvGetResultPrimitiveType(runtime, output_result, &primitive_type));
if (output_size != sizeof(output) || primitive_type != SPV_PRIMITIVE_FLOAT || primitive_type == SPV_PRIMITIVE_SINT || primitive_type == SPV_PRIMITIVE_UINT)
{
fprintf(stderr, "Unexpected output metadata\n");
SpvDeinitRuntime(second_runtime);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return -1;
}
float dx[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
float dy[4] = { 5.0f, 6.0f, 7.0f, 8.0f };
CHECK_RESULT(SpvSetDerivativeFromMemory(runtime, output_result, (const SpvByte*)dx, sizeof(dx), (const SpvByte*)dy, sizeof(dy)));
CHECK_RESULT(SpvCopyDerivative(runtime, output_result, output_result));
SpvClearDerivative(runtime, output_result);
printf("Output: Vec4[%f, %f, %f, %f]\n", output[0], output[1], output[2], output[3]);
SpvDeinitRuntime(second_runtime);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return 0;
}