Compare commits

...

35 Commits

Author SHA1 Message Date
kbz_8 7da8eec788 fixing CI
Test / build_and_test (push) Successful in 22s
Build / build (push) Successful in 31s
2026-06-02 22:21:58 +02:00
kbz_8 e68ee5217d fixing CI
Test / build_and_test (push) Successful in 20s
Build / build (push) Successful in 26s
2026-06-02 22:19:48 +02:00
kbz_8 191db8a8e4 fixing CI
Test / build_and_test (push) Successful in 19s
Build / build (push) Successful in 29s
2026-06-02 22:14:58 +02:00
kbz_8 657bb38810 fixing CI
Test / build_and_test (push) Successful in 22s
Build / build (push) Successful in 31s
2026-06-02 22:12:31 +02:00
kbz_8 8cd40c40f0 fixing CI
Test / build_and_test (push) Successful in 20s
Build / build (push) Successful in 26s
2026-06-02 22:09:41 +02:00
kbz_8 042662a928 fixing CI
Test / build_and_test (push) Successful in 19s
Build / build (push) Successful in 30s
2026-06-02 22:01:34 +02:00
kbz_8 d4e835b6af fixing CI
Test / build_and_test (push) Successful in 23s
Build / build (push) Failing after 35s
2026-06-02 21:54:16 +02:00
kbz_8 a7aad1c2e8 fixing CI
Test / build_and_test (push) Successful in 19s
Build / build (push) Successful in 28s
2026-06-02 21:46:37 +02:00
kbz_8 76be771215 fixing CI
Test / build_and_test (push) Successful in 18s
Build / build (push) Failing after 26s
2026-06-02 21:41:50 +02:00
kbz_8 4b5aaf3d6f try new deployment
Test / build_and_test (push) Successful in 1m21s
Build / build (push) Failing after 1m42s
2026-06-02 21:36:58 +02:00
kbz_8 6dc82d4a68 fixing all image operation tests
Test / build_and_test (push) Successful in 26s
Build / build (push) Successful in 1m3s
2026-06-01 22:13:43 +02:00
kbz_8 19c8d84e05 improving draw line
Test / build_and_test (push) Successful in 30s
Build / build (push) Successful in 1m3s
2026-05-30 13:10:34 +02:00
kbz_8 13d53abbdf improving fragment outputs management
Build / build (push) Successful in 2m15s
Test / build_and_test (push) Successful in 1m31s
2026-05-30 01:45:15 +02:00
kbz_8 0ab6581fe9 new identity
Test / build_and_test (push) Successful in 45s
Build / build (push) Failing after 2h4m30s
2026-05-29 16:42:06 +02:00
kbz_8 ba874a77b2 yes
Test / build_and_test (push) Successful in 38s
Build / build (push) Successful in 53s
2026-05-29 00:45:45 +02:00
kbz_8 b26c4ff9e6 yets
Test / build_and_test (push) Successful in 26s
Build / build (push) Successful in 58s
2026-05-29 00:45:07 +02:00
kbz_8 fc2407d10d adding attachments resolving to renderpass commands
Test / build_and_test (push) Successful in 26s
Build / build (push) Successful in 56s
2026-05-28 01:05:23 +02:00
kbz_8 fe391bc678 adding proper subpass management and image resolve
Test / build_and_test (push) Successful in 29s
Build / build (push) Successful in 1m5s
2026-05-27 23:31:05 +02:00
kbz_8 6edb856d06 fixing sneaky bug in blitter clear
Test / build_and_test (push) Successful in 1m4s
Build / build (push) Successful in 1m42s
2026-05-27 00:47:12 +02:00
kbz_8 ae2bdd03a0 improving image clear
Test / build_and_test (push) Successful in 24s
Build / build (push) Successful in 56s
2026-05-26 01:25:05 +02:00
kbz_8 5a91956939 fixing lots of copy tests issues
Test / build_and_test (push) Successful in 33s
Build / build (push) Successful in 1m0s
2026-05-24 22:04:10 +02:00
kbz_8 bd2774b7f9 fixing image layers and mipmaps
Test / build_and_test (push) Successful in 26s
Build / build (push) Successful in 59s
2026-05-24 13:22:16 +02:00
kbz_8 fe6b0b3b23 fixing all format blits, adding srgb convertion
Test / build_and_test (push) Successful in 33s
Build / build (push) Successful in 1m13s
2026-05-23 23:19:22 +02:00
kbz_8 afc47bf29f improving blitter formats management
Test / build_and_test (push) Successful in 30s
Build / build (push) Successful in 2m26s
2026-05-23 01:06:25 +02:00
kbz_8 d4d0f70472 updating spirv interpreter
Test / build_and_test (push) Successful in 36s
Build / build (push) Successful in 1m5s
2026-05-22 10:44:35 +02:00
kbz_8 bb482465fe adding formats management
Test / build_and_test (push) Successful in 41s
Build / build (push) Successful in 1m7s
2026-05-21 18:52:43 +02:00
kbz_8 800c867cdd base texture sampling
Test / build_and_test (push) Successful in 46s
Build / build (push) Successful in 1m15s
2026-05-20 18:30:36 +02:00
kbz_8 4d344e83d3 fixing tests
Build / build (push) Successful in 1m25s
Test / build_and_test (push) Successful in 32s
2026-05-18 23:52:59 +02:00
kbz_8 9e4221affb improving descriptor sets push to shaders
Test / build_and_test (push) Successful in 35s
Build / build (push) Successful in 1m16s
2026-05-18 19:54:09 +02:00
kbz_8 3c9a864240 fixing crashes
Test / build_and_test (push) Successful in 49s
Build / build (push) Successful in 1m28s
2026-05-17 01:58:20 +02:00
kbz_8 134925a16e adding descriptor sets injections to graphics stages
Test / build_and_test (push) Successful in 45s
Build / build (push) Successful in 1m18s
2026-05-15 00:58:58 +02:00
kbz_8 d460f22a45 improving rasterization performances
Test / build_and_test (push) Successful in 33s
Build / build (push) Successful in 1m2s
2026-05-14 21:44:53 +02:00
kbz_8 1eb367ac17 adding drawcall stats
Build / build (push) Successful in 58s
Test / build_and_test (push) Successful in 33s
2026-05-14 14:27:27 +02:00
kbz_8 c2dad9fe6d adding base depth buffers
Test / build_and_test (push) Successful in 28s
Build / build (push) Successful in 1m8s
2026-05-14 01:35:26 +02:00
kbz_8 124ea12d2e fixing slow memory leak
Test / build_and_test (push) Successful in 28s
Build / build (push) Successful in 1m4s
2026-05-14 00:23:46 +02:00
49 changed files with 3380 additions and 1150 deletions
+15 -14
View File
@@ -6,21 +6,17 @@ on:
pull_request:
branches: [ "master" ]
permissions:
contents: read
deployments: write
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, 'ci skip') }}
steps:
- uses: actions/checkout@v4
- uses: https://codeberg.org/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 soft
@@ -28,10 +24,15 @@ jobs:
- name: Generating docs
run: zig build docs
- 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=vulkan-driver-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
-77
View File
@@ -1,77 +0,0 @@
name: CTS
on:
workflow_dispatch:
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: https://codeberg.org/mlugg/setup-zig@v2
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Install system dependencies
run: |
apt update
apt install -y libgl1 libwayland-egl1 libwayland-cursor0 clang libwayland-server0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: x86_64-unknown-linux-gnu,wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-on-failure: "false"
- name: Install cargo tools
uses: cargo-bins/cargo-binstall@main
- name: Install Dioxus
shell: bash
run: |
source $HOME/.cargo/env
cargo binstall dioxus-cli --no-confirm --force --version 0.7.9
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install deqp-runner
run: cargo install deqp-runner
- name: Verify installations
shell: bash
run: |
source $HOME/.cargo/env
echo "Verifying tool installations..."
which dx && dx --version || echo "dioxus-cli not found"
which deqp-runner && deqp-runner --version || echo "deqp-runner not found"
- name: Run Vulkan CTS
run: zig build cts-soft --release=fast -- -j4
continue-on-error: true
- name: Verify tests
run: ls cts | grep "results.csv";
- name: Cloning CTS viewer
run: git clone https://git.kbz8.me/kbz_8/VulkanCTSViewer.git
- name: Build CTS viewer
env:
URL: https://vulkan-driver-cts-report.kbz8.me/
run: |
cd VulkanCTSViewer
zip ./assets/results.zip ../cts/results.csv
dx build --verbose --platform web --release
- name: Publish to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy VulkanCTSViewer/target/dx/vulkan-cts-analyzer/release/web/public --project-name=vulkan-driver-cts-report
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
+64
View File
@@ -0,0 +1,64 @@
name: CTS Soft
on:
workflow_dispatch:
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: https://codeberg.org/mlugg/setup-zig@v2
- name: Install system dependencies
run: |
apt update
apt install -y libgl1 libwayland-egl1 libwayland-cursor0 clang libwayland-server0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-on-failure: "false"
- name: Install deqp-runner
run: cargo install deqp-runner
- name: Verify installations
shell: bash
run: |
source $HOME/.cargo/env
echo "Verifying tool installations..."
which deqp-runner && deqp-runner --version || echo "deqp-runner not found"
- name: Run Vulkan CTS
run: zig build cts-soft --release=fast -- -j4
continue-on-error: true
- name: Verify tests
run: ls cts | grep "results.csv";
- name: Archiving results
run: |
mkdir -p assets/
zip ./assets/results.zip ./cts/results.csv
- name: Deploying CTS results
uses: milanmk/actions-file-deployer@master
with:
remote-protocol: sftp
remote-host: ${{ secrets.SFTP_HOST_CTS_SOFT }}
remote-user: ${{ secrets.SFTP_USER_CTS_SOFT }}
remote-password: ${{ secrets.SFTP_PASSWORD_CTS_SOFT }}
remote-port: 6969
local-path: "./assets"
remote-path: "/www/assets"
sync: full
+1
View File
@@ -9,6 +9,7 @@ scripts/__pycache__/
.gdb_history
*.json
*.png
!logo.png
*.bin
*.qpa
*.xml
+23 -20
View File
@@ -1,10 +1,10 @@
# Stroll Vulkan ICD <a href="https://git.kbz8.me/kbz_8/VulkanDriver/actions?workflows=Build.yml"><img src="https://git.kbz8.me/kbz_8/VulkanDriver/actions/workflows/Build.yml/badge.svg"></a> <a href="https://git.kbz8.me/kbz_8/VulkanDriver/actions?workflows=Test.yml"><img src="https://git.kbz8.me/kbz_8/VulkanDriver/actions/workflows/Test.yml/badge.svg"></a>
# Ape Vulkan ICD <a href="https://git.kbz8.me/kbz_8/VulkanDriver/actions?workflows=Build.yml"><img src="https://git.kbz8.me/kbz_8/VulkanDriver/actions/workflows/Build.yml/badge.svg"></a> <a href="https://git.kbz8.me/kbz_8/VulkanDriver/actions?workflows=Test.yml"><img src="https://git.kbz8.me/kbz_8/VulkanDriver/actions/workflows/Test.yml/badge.svg"></a>
<img align="right" src="https://matthew.kerwin.net.au/blog_files/kappa"/>
<img align="right" width="250px" src="./logo.png"/>
A driver as slow as Lance Stroll.
For I feel as an ape, smiting sticks together in the vain hope of forging a driver.
Here lies the source code of a rather calamitous attempt at the Vulkan specification, shaped into an Installable Client Driver for a software-based renderer, all written in Zig.
Here lies the source code of a rather calamitous attempt at the Vulkan specification, shaped into an Installable Client Driver all written in Zig.
It was forged for my own learning and amusement alone. Pray, do not wield it in any earnest project, lest thy hopes and frame rates both find themselves entombed.
@@ -14,11 +14,16 @@ To understand Vulkan - not as a humble API mere mortals call upon, but as a laby
It does not seek to produce a performant or production-worthy driver. \
*The gods are merciful, but not that merciful.*
## Build
## Soft [software implementation]
Soft be a software implementation of the Vulkan specification, abiding within this driver's own codebase.\
It maketh use of a bespoke [SPIR-V interpreter](https://git.kbz8.me/kbz_8/SPIRV-Interpreter) and renderer, by whose workings its labours are carried forth.
### Build
If thou art truly determined:
```
zig build
zig build soft
```
Then ensure thy Vulkan loader is pointed toward the ICD manifest.
@@ -26,8 +31,7 @@ The precise ritual varies by system - consult the tomes of your operating system
Use at your own risk. If thy machine shudders, weeps, or attempts to flee - know that it was warned.
## Vulkan 1.0 specification
#### Vulkan 1.0 specification
<details>
<summary>
The present standing of thy Vulkan 1.0 specification's implementation
@@ -55,7 +59,7 @@ vkCmdBindVertexBuffers | ✅ Implemented
vkCmdBlitImage | ✅ Implemented
vkCmdClearAttachments | ✅ Implemented
vkCmdClearColorImage | ✅ Implemented
vkCmdClearDepthStencilImage | ⚙️ WIP
vkCmdClearDepthStencilImage | ✅ Implemented
vkCmdCopyBuffer | ✅ Implemented
vkCmdCopyBufferToImage | ✅ Implemented
vkCmdCopyImage | ✅ Implemented
@@ -71,18 +75,18 @@ vkCmdEndQuery | ⚙️ WIP
vkCmdEndRenderPass | ✅ Implemented
vkCmdExecuteCommands | ✅ Implemented
vkCmdFillBuffer | ✅ Implemented
vkCmdNextSubpass | ⚙️ WIP
vkCmdNextSubpass | ✅ Implemented
vkCmdPipelineBarrier | ✅ Implemented
vkCmdPushConstants | ⚙️ WIP
vkCmdPushConstants | ✅ Implemented
vkCmdResetEvent | ✅ Implemented
vkCmdResetQueryPool | ⚙️ WIP
vkCmdResolveImage | ⚙️ WIP
vkCmdResolveImage | ✅ Implemented
vkCmdSetBlendConstants | ⚙️ WIP
vkCmdSetDepthBias | ⚙️ WIP
vkCmdSetDepthBounds | ⚙️ WIP
vkCmdSetEvent | ✅ Implemented
vkCmdSetLineWidth | ⚙️ WIP
vkCmdSetScissor | ⚙️ WIP
vkCmdSetScissor | ✅ Implemented
vkCmdSetStencilCompareMask | ⚙️ WIP
vkCmdSetStencilReference | ⚙️ WIP
vkCmdSetStencilWriteMask | ⚙️ WIP
@@ -91,7 +95,7 @@ vkCmdUpdateBuffer | ⚙️ WIP
vkCmdWaitEvents | ✅ Implemented
vkCmdWriteTimestamp | ⚙️ WIP
vkCreateBuffer | ✅ Implemented
vkCreateBufferView | ⚙️ WIP
vkCreateBufferView | ✅ Implemented
vkCreateCommandPool | ✅ Implemented
vkCreateComputePipelines | ✅ Implemented
vkCreateDescriptorPool | ✅ Implemented
@@ -108,17 +112,16 @@ vkCreatePipelineCache | ⚙️ WIP
vkCreatePipelineLayout | ✅ Implemented
vkCreateQueryPool | ⚙️ WIP
vkCreateRenderPass | ✅ Implemented
vkCreateSampler | ⚙️ WIP
vkCreateSampler | ✅ Implemented
vkCreateSemaphore | ⚙️ WIP
vkCreateShaderModule | ✅ Implemented
vkCreateSwapchainKHR | ⚙️ WIP
vkCreateSwapchainKHR | ✅ Implemented
vkCreateWaylandSurfaceKHR | ✅ Implemented
vkCreateWin32SurfaceKHR | ⚙️ WIP
vkCreateXcbSurfaceKHR | ⚙️ WIP
vkCreateXlibSurfaceKHR | ⚙️ WIP
vkDestroyBuffer | ✅ Implemented
vkDestroyBufferView | ⚙️ WIP
vkDestroyBufferView | ✅ Implemented
vkDestroyCommandPool | ✅ Implemented
vkDestroyDescriptorPool | ✅ Implemented
vkDestroyDescriptorSetLayout | ✅ Implemented
@@ -157,7 +160,7 @@ vkGetDeviceQueue | ✅ Implemented
vkGetEventStatus | ✅ Implemented
vkGetFenceStatus | ✅ Implemented
vkGetImageMemoryRequirements | ✅ Implemented
vkGetImageSparseMemoryRequirements | ⚙️ WIP
vkGetImageSparseMemoryRequirements | ❎ Unsupported
vkGetImageSubresourceLayout | ✅ Implemented
vkGetInstanceProcAddr | ✅ Implemented
vkGetPhysicalDeviceFeatures | ✅ Implemented
@@ -166,7 +169,7 @@ vkGetPhysicalDeviceImageFormatProperties | ✅ Implemented
vkGetPhysicalDeviceMemoryProperties | ✅ Implemented
vkGetPhysicalDeviceProperties | ✅ Implemented
vkGetPhysicalDeviceQueueFamilyProperties | ✅ Implemented
vkGetPhysicalDeviceSparseImageFormatProperties | ⚙️ WIP
vkGetPhysicalDeviceSparseImageFormatProperties | ❎ Unsupported
vkGetPhysicalDeviceSurfaceCapabilitiesKHR | ✅ Implemented
vkGetPhysicalDeviceSurfaceFormatsKHR | ✅ Implemented
vkGetPhysicalDeviceSurfacePresentModesKHR | ✅ Implemented
@@ -182,7 +185,7 @@ vkGetSwapchainImagesKHR | ✅ Implemented
vkInvalidateMappedMemoryRanges | ✅ Implemented
vkMapMemory | ✅ Implemented
vkMergePipelineCaches | ⚙️ WIP
vkQueueBindSparse | ⚙️ WIP
vkQueueBindSparse | ❎ Unsupported
vkQueuePresentKHR | ✅ Implemented
vkQueueSubmit | ✅ Implemented
vkQueueWaitIdle | ✅ Implemented
+5 -17
View File
@@ -108,7 +108,7 @@ pub fn build(b: *std.Build) !void {
}
const icd_file = b.addWriteFile(
b.getInstallPath(.lib, b.fmt("vk_stroll_{s}.json", .{impl.name})),
b.getInstallPath(.lib, b.fmt("vk_ape_{s}.json", .{impl.name})),
b.fmt(
\\{{
\\ "file_format_version": "1.0.1",
@@ -176,14 +176,11 @@ pub fn build(b: *std.Build) !void {
fn customSoft(
b: *std.Build,
lib: *Step.Compile,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
_: std.Build.ResolvedTarget,
_: std.builtin.OptimizeMode,
options: *Step.Options,
use_llvm: bool,
) !void {
const cpuinfo = b.lazyDependency("cpuinfo", .{}) orelse return error.UnresolvedDependency;
lib.root_module.linkLibrary(cpuinfo.artifact("cpuinfo"));
const spv = b.lazyDependency("SPIRV_Interpreter", .{
.@"no-example" = true,
.@"no-test" = true,
@@ -191,23 +188,13 @@ fn customSoft(
}) orelse return error.UnresolvedDependency;
lib.root_module.addImport("spv", spv.module("spv"));
const c_includes = b.addTranslateC(.{
.root_source_file = b.path("src/soft/c_includes.h"),
.target = target,
.optimize = optimize,
.link_libc = false,
});
c_includes.addIncludePath(cpuinfo.path("include"));
lib.root_module.addImport("soft_c", c_includes.createModule());
const single_threaded_option = b.option(bool, "single-threaded", "Single threaded runtime mode") orelse false;
const debug_allocator_option = b.option(bool, "debug-allocator", "Debug device allocator") orelse false;
const shaders_simd_option = b.option(bool, "shader-simd", "Shaders SIMD acceleration") orelse true;
const single_threaded_compute_option = b.option(bool, "single-threaded-compute", "Single threaded compute shaders execution") orelse true;
const compute_dump_early_results_table_option = b.option(u32, "compute-dump-early-results-table", "Dump compute shaders results table before invocation");
const compute_dump_final_results_table_option = b.option(u32, "compute-dump-final-results-table", "Dump compute shaders results table after invocation");
const approxiamte_rgb_option = b.option(bool, "approximates-rgb", "Approximate sRGB <-> RGB conversions") orelse true;
options.addOption(bool, "single_threaded", single_threaded_option);
options.addOption(bool, "debug_allocator", debug_allocator_option);
@@ -215,6 +202,7 @@ fn customSoft(
options.addOption(bool, "single_threaded_compute", single_threaded_compute_option);
options.addOption(?u32, "compute_dump_early_results_table", compute_dump_early_results_table_option);
options.addOption(?u32, "compute_dump_final_results_table", compute_dump_final_results_table_option);
options.addOption(bool, "approximates_rgb", approxiamte_rgb_option);
}
fn addCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *const ImplementationDesc, impl_lib: *Step.Compile, comptime mode: RunningMode) !*Step {
+2 -7
View File
@@ -25,14 +25,9 @@
.url = "git+https://git.kbz8.me/kbz_8/Vulkan-CTS-bin.git#a5f787d80f14f136e3cb3e1185c35e298846c1d7",
.hash = "N-V-__8AAMpOQxkHCKTw9i-NwmmQ3ks1ndFDXcVLlic4KjK3",
},
.cpuinfo = .{
.url = "git+https://github.com/Kbz-8/cpuinfo.git#c9bea4f6c166a495ee0ce117821f9627d4aed118",
.hash = "cpuinfo-0.0.1-RLgIQYrTMgGqfQMOd1nAa2EuglXOh5gR9bNzwMzQTemt",
.lazy = true,
},
.SPIRV_Interpreter = .{
.url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#c0825d53158cd5a5fc38f12d155d1158efc9b371",
.hash = "SPIRV_Interpreter-0.0.1-ajmpn0RFBQBe3oaZ5-aVNJQ7FMancJXlmCNt7mYUP5WP",
.url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#9c355fe126d0142ac6d1fae48633993864c90d61",
.hash = "SPIRV_Interpreter-0.0.1-ajmpn867BQA-J2PA4fGQGnb3WcNtqd_3_W7-goaEocC5",
.lazy = true,
},
//.SPIRV_Interpreter = .{
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

+4 -4
View File
@@ -60,7 +60,7 @@ pub fn fillBuffer(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize, data:
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
var bytes = if (size == vk.WHOLE_SIZE) memory.size - offset else size;
const map = try self.mapAsSliceWithOffset(u32, offset, bytes);
const map = try self.mapAsSliceWithOffset(u32, offset, @divFloor(bytes, @sizeOf(u32)) * @sizeOf(u32));
var i: usize = 0;
while (bytes >= 4) : ({
@@ -97,18 +97,18 @@ pub inline fn mapAsSliceWithAddedOffset(self: *const Self, comptime T: type, off
pub fn mapAsWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, @sizeOf(T)))))[0..@sizeOf(T)];
const map = try memory.map(offset, @sizeOf(T));
return @alignCast(std.mem.bytesAsValue(T, map));
}
pub fn mapToWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, @sizeOf(T)))))[0..@sizeOf(T)];
const map = try memory.map(offset, @sizeOf(T));
return std.mem.bytesToValue(T, map);
}
pub fn mapAsSliceWithOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, size))))[0..size];
const map = try memory.map(offset, size);
return @alignCast(std.mem.bytesAsSlice(T, map));
}
+117 -26
View File
@@ -53,6 +53,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
.blitImage = blitImage,
.clearAttachment = clearAttachment,
.clearColorImage = clearColorImage,
.clearDepthStencilImage = clearDepthStencilImage,
.copyBuffer = copyBuffer,
.copyBufferToImage = copyBufferToImage,
.copyImage = copyImage,
@@ -67,10 +68,12 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
.endRenderPass = endRenderPass,
.executeCommands = executeCommands,
.fillBuffer = fillBuffer,
.nextSubpass = nextSubpass,
.pipelineBarrier = pipelineBarrier,
.pushConstants = pushConstants,
.reset = reset,
.resetEvent = resetEvent,
.resolveImage = resolveImage,
.setEvent = setEvent,
.setScissor = setScissor,
.setViewport = setViewport,
@@ -99,9 +102,11 @@ pub fn execute(self: *Self, device: *ExecutionDevice) void {
for (self.commands.items) |command| {
command.vtable.execute(@ptrCast(command.ptr), device) catch |err| {
base.errors.errorLoggerContext(err, "the software execution device");
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return; // Should we return or continue ? Maybe device lost ?
};
}
@@ -141,6 +146,7 @@ pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, fra
const impl: *Impl = @ptrCast(@alignCast(context));
device.renderer.render_pass = impl.render_pass;
device.renderer.framebuffer = impl.framebuffer;
device.renderer.subpass_index = 0;
for (impl.render_pass.interface.attachments, impl.framebuffer.interface.attachments, 0..) |desc, attachment, index| {
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", attachment.image));
@@ -162,7 +168,7 @@ pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, fra
if (clear_mask.color_bit) {
try blitter.clear(
(impl.clear_values orelse return VkError.Unknown)[index],
try image.getClearFormat(),
try SoftImage.getClearFormatFor(attachment.format),
image,
attachment.format,
attachment.subresource_range,
@@ -375,21 +381,45 @@ pub fn clearAttachment(interface: *Interface, attachment: vk.ClearAttachment, re
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
if (device.renderer.framebuffer) |framebuffer| {
const image_view = framebuffer.interface.attachments[impl.attachment.color_attachment];
const framebuffer = device.renderer.framebuffer orelse return;
const render_pass = device.renderer.render_pass orelse return;
const subpass = render_pass.interface.subpasses[device.renderer.subpass_index];
const image_view = blk: {
if (impl.attachment.aspect_mask.toInt() == (vk.ImageAspectFlags{ .color_bit = true }).toInt()) {
const fb_attachment_index = (subpass.color_attachments orelse return)[impl.attachment.color_attachment].attachment;
if (fb_attachment_index != vk.ATTACHMENT_UNUSED)
break :blk framebuffer.interface.attachments[impl.attachment.color_attachment];
} else if (impl.attachment.aspect_mask.depth_bit or impl.attachment.aspect_mask.stencil_bit) {
if (render_pass.interface.subpasses[device.renderer.subpass_index].depth_stencil_attachments) |desc| {
if (desc.attachment != vk.ATTACHMENT_UNUSED)
break :blk framebuffer.interface.attachments[desc.attachment];
}
}
return;
};
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.image));
const clear_format = try image.getClearFormat();
const clear_format = try SoftImage.getClearFormatFor(image_view.format);
const range: vk.ImageSubresourceRange = .{
.aspect_mask = impl.attachment.aspect_mask,
.base_mip_level = image_view.subresource_range.base_mip_level,
.level_count = image_view.subresource_range.level_count,
.base_array_layer = impl.rect.base_array_layer + image_view.subresource_range.base_array_layer,
.layer_count = impl.rect.layer_count,
};
try blitter.clear(
impl.attachment.clear_value,
clear_format,
image,
image_view.format,
image_view.subresource_range,
null,
range,
impl.rect.rect,
);
}
}
};
const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory;
@@ -429,6 +459,34 @@ pub fn clearColorImage(interface: *Interface, image: *base.Image, _: vk.ImageLay
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn clearDepthStencilImage(interface: *Interface, image: *base.Image, _: vk.ImageLayout, value: *const vk.ClearDepthStencilValue, range: vk.ImageSubresourceRange) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
const CommandImpl = struct {
const Impl = @This();
image: *SoftImage,
value: vk.ClearDepthStencilValue,
range: vk.ImageSubresourceRange,
pub fn execute(context: *anyopaque, _: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
const clear_format = try impl.image.getClearFormat();
try blitter.clear(.{ .depth_stencil = impl.value }, clear_format, impl.image, impl.image.interface.format, impl.range, null);
}
};
const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(cmd);
cmd.* = .{
.image = @alignCast(@fieldParentPtr("interface", image)),
.value = value.*,
.range = range,
};
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn copyBuffer(interface: *Interface, src: *base.Buffer, dst: *base.Buffer, regions: []const vk.BufferCopy) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
@@ -730,6 +788,11 @@ pub fn endRenderPass(interface: *Interface) VkError!void {
const CommandImpl = struct {
pub fn execute(_: *anyopaque, device: *ExecutionDevice) VkError!void {
const framebuffer = device.renderer.framebuffer orelse return;
const render_pass = device.renderer.render_pass orelse return;
try framebuffer.resolveAttachments(render_pass, device.renderer.subpass_index);
device.renderer.render_pass = null;
device.renderer.framebuffer = null;
}
@@ -790,15 +853,20 @@ pub fn fillBuffer(interface: *Interface, buffer: *base.Buffer, offset: vk.Device
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn pipelineBarrier(interface: *Interface, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, dependency: vk.DependencyFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void {
pub fn nextSubpass(interface: *Interface, _: vk.SubpassContents) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
const CommandImpl = struct {
const Impl = @This();
pub fn execute(_: *anyopaque, _: *ExecutionDevice) VkError!void {
// TODO: implement synchronization for rasterization stages
pub fn execute(_: *anyopaque, device: *ExecutionDevice) VkError!void {
const framebuffer = device.renderer.framebuffer orelse return;
const render_pass = device.renderer.render_pass orelse return;
try framebuffer.resolveAttachments(render_pass, device.renderer.subpass_index);
device.renderer.subpass_index += 1;
}
};
@@ -806,13 +874,10 @@ pub fn pipelineBarrier(interface: *Interface, src_stage: vk.PipelineStageFlags,
errdefer allocator.destroy(cmd);
cmd.* = .{};
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
_ = src_stage;
_ = dst_stage;
_ = dependency;
_ = memory_barriers;
_ = buffer_barriers;
_ = image_barriers;
pub fn pipelineBarrier(_: *Interface, _: vk.PipelineStageFlags, _: vk.PipelineStageFlags, _: vk.DependencyFlags, _: []const vk.MemoryBarrier, _: []const vk.BufferMemoryBarrier, _: []const vk.ImageMemoryBarrier) VkError!void {
// No-op
}
pub fn pushConstants(interface: *Interface, stages: vk.ShaderStageFlags, offset: u32, blob: []const u8) VkError!void {
@@ -829,14 +894,15 @@ pub fn pushConstants(interface: *Interface, stages: vk.ShaderStageFlags, offset:
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
const state = &device.pipeline_states[
if (impl.stages.vertex_bit or impl.stages.fragment_bit)
ExecutionDevice.GRAPHICS_PIPELINE_STATE
else
ExecutionDevice.COMPUTE_PIPELINE_STATE
];
const size = @min(lib.PUSH_CONSTANT_SIZE - impl.offset, impl.blob.len);
// TODO: pipeline layout offset
if (impl.stages.vertex_bit or impl.stages.fragment_bit) {
@memcpy(device.pipeline_states[ExecutionDevice.GRAPHICS_PIPELINE_STATE].push_constant_blob[impl.offset..size], impl.blob[0..size]);
}
if (impl.stages.compute_bit) {
@memcpy(device.pipeline_states[ExecutionDevice.COMPUTE_PIPELINE_STATE].push_constant_blob[impl.offset..size], impl.blob[0..size]);
}
@memcpy(state.push_constant_blob[impl.offset .. impl.offset + size], impl.blob[0..size]);
}
};
@@ -850,12 +916,10 @@ pub fn pushConstants(interface: *Interface, stages: vk.ShaderStageFlags, offset:
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn resetEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineStageFlags) VkError!void {
pub fn resetEvent(interface: *Interface, event: *base.Event, _: vk.PipelineStageFlags) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
_ = stage;
const CommandImpl = struct {
const Impl = @This();
@@ -875,6 +939,33 @@ pub fn resetEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineS
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn resolveImage(interface: *Interface, src: *base.Image, _: vk.ImageLayout, dst: *base.Image, _: vk.ImageLayout, region: vk.ImageResolve) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
const CommandImpl = struct {
const Impl = @This();
src: *const SoftImage,
dst: *SoftImage,
region: vk.ImageResolve,
pub fn execute(context: *anyopaque, _: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
try blitter.resolve(impl.src, impl.dst, impl.region);
}
};
const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(cmd);
cmd.* = .{
.src = @alignCast(@fieldParentPtr("interface", src)),
.dst = @alignCast(@fieldParentPtr("interface", dst)),
.region = region,
};
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn setEvent(interface: *Interface, event: *base.Event, stage: vk.PipelineStageFlags) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
+238 -13
View File
@@ -5,9 +5,12 @@ const base = @import("base");
const VkError = base.VkError;
const Device = base.Device;
const Buffer = base.Buffer;
const BufferView = base.BufferView;
const ImageView = base.ImageView;
const Sampler = base.Sampler;
const SoftBuffer = @import("SoftBuffer.zig");
const SoftBufferView = @import("SoftBufferView.zig");
const SoftImageView = @import("SoftImageView.zig");
const SoftSampler = @import("SoftSampler.zig");
@@ -31,10 +34,15 @@ const DescriptorImage = struct {
object: ?*SoftImageView,
};
const DescriptorTexel = struct {
object: ?*SoftBufferView,
};
const Descriptor = union(enum) {
buffer: []DescriptorBuffer,
texture: []DescriptorTexture,
image: []DescriptorImage,
texel_buffer: []DescriptorTexel,
unsupported: struct {},
};
@@ -61,8 +69,22 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.
var size: usize = layout.bindings.len * @sizeOf(Descriptor);
for (layout.bindings) |binding| {
const struct_size: usize = switch (binding.descriptor_type) {
.storage_buffer, .storage_buffer_dynamic => @sizeOf(DescriptorBuffer),
.storage_image, .input_attachment => @sizeOf(DescriptorImage),
.uniform_buffer,
.storage_buffer,
.storage_buffer_dynamic,
=> @sizeOf(DescriptorBuffer),
.storage_image,
.input_attachment,
=> @sizeOf(DescriptorImage),
.storage_texel_buffer,
.uniform_texel_buffer,
=> @sizeOf(DescriptorTexel),
.combined_image_sampler,
=> @sizeOf(DescriptorTexture),
else => 0,
};
@@ -80,13 +102,62 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.
const descriptors = local_allocator.alloc(Descriptor, layout.bindings.len) catch return VkError.OutOfHostMemory;
for (descriptors, layout.bindings) |*descriptor, binding| {
switch (binding.descriptor_type) {
.storage_buffer, .storage_buffer_dynamic => descriptor.* = .{
.uniform_buffer,
.storage_buffer,
.storage_buffer_dynamic,
=> descriptor.* = blk: {
const desc: Descriptor = .{
.buffer = local_allocator.alloc(DescriptorBuffer, binding.array_size) catch return VkError.OutOfHostMemory,
};
for (desc.buffer[0..]) |*d| {
d.* = .{
.object = null,
.offset = 0,
.size = 0,
};
}
break :blk desc;
},
.storage_image, .input_attachment => descriptor.* = .{
.storage_image,
.input_attachment,
=> descriptor.* = blk: {
const desc: Descriptor = .{
.image = local_allocator.alloc(DescriptorImage, binding.array_size) catch return VkError.OutOfHostMemory,
};
for (desc.image[0..]) |*d| {
d.* = .{ .object = null };
}
break :blk desc;
},
else => {},
.storage_texel_buffer,
.uniform_texel_buffer,
=> descriptor.* = blk: {
const desc: Descriptor = .{
.texel_buffer = local_allocator.alloc(DescriptorTexel, binding.array_size) catch return VkError.OutOfHostMemory,
};
for (desc.texel_buffer[0..]) |*d| {
d.* = .{ .object = null };
}
break :blk desc;
},
.combined_image_sampler,
=> descriptor.* = blk: {
const desc: Descriptor = .{
.texture = local_allocator.alloc(DescriptorTexture, binding.array_size) catch return VkError.OutOfHostMemory,
};
for (desc.texture[0..]) |*d| {
d.* = .{
.sampler = null,
.view = null,
};
}
break :blk desc;
},
else => descriptor.* = .{ .unsupported = .{} },
}
}
@@ -98,23 +169,141 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, layout: *base.
return self;
}
pub fn copy(interface: *Interface, copy_data: vk.CopyDescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
_ = self;
_ = copy_data;
}
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
allocator.free(self.heap);
allocator.destroy(self);
}
fn descriptorLen(descriptor: Descriptor) usize {
return switch (descriptor) {
.buffer => |buffer| buffer.len,
.image => |image| image.len,
.texel_buffer => |texel_buffer| texel_buffer.len,
.texture => |texture| texture.len,
.unsupported => 0,
};
}
fn advanceToAvailableDescriptor(descriptors: []const Descriptor, binding: *usize, array_element: *usize) bool {
while (binding.* < descriptors.len) {
switch (descriptors[binding.*]) {
.unsupported => return true,
else => {},
}
const len = descriptorLen(descriptors[binding.*]);
if (array_element.* < len) return true;
if (array_element.* > len) return false;
binding.* += 1;
array_element.* = 0;
}
return false;
}
fn copyDescriptorRange(dst_desc: *Descriptor, dst_array_element: usize, src_desc: Descriptor, src_array_element: usize, descriptor_count: usize) bool {
switch (dst_desc.*) {
.buffer => |dst_buffer| {
const src_buffer = switch (src_desc) {
.buffer => |buffer| buffer,
else => return false,
};
@memcpy(
dst_buffer[dst_array_element .. dst_array_element + descriptor_count],
src_buffer[src_array_element .. src_array_element + descriptor_count],
);
},
.image => |dst_image| {
const src_image = switch (src_desc) {
.image => |image| image,
else => return false,
};
@memcpy(
dst_image[dst_array_element .. dst_array_element + descriptor_count],
src_image[src_array_element .. src_array_element + descriptor_count],
);
},
.texel_buffer => |dst_texel_buffer| {
const src_texel_buffer = switch (src_desc) {
.texel_buffer => |texel_buffer| texel_buffer,
else => return false,
};
@memcpy(
dst_texel_buffer[dst_array_element .. dst_array_element + descriptor_count],
src_texel_buffer[src_array_element .. src_array_element + descriptor_count],
);
},
.texture => |dst_texture| {
const src_texture = switch (src_desc) {
.texture => |texture| texture,
else => return false,
};
@memcpy(
dst_texture[dst_array_element .. dst_array_element + descriptor_count],
src_texture[src_array_element .. src_array_element + descriptor_count],
);
},
.unsupported => return false,
}
return true;
}
pub fn copy(interface: *Interface, src_interface: *const Interface, data: vk.CopyDescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const src: *const Self = @alignCast(@fieldParentPtr("interface", src_interface));
var dst_binding: usize = @intCast(data.dst_binding);
var src_binding: usize = @intCast(data.src_binding);
var dst_array_element: usize = @intCast(data.dst_array_element);
var src_array_element: usize = @intCast(data.src_array_element);
var descriptor_count: usize = @intCast(data.descriptor_count);
while (descriptor_count > 0) {
if (!advanceToAvailableDescriptor(self.descriptors, &dst_binding, &dst_array_element) or
!advanceToAvailableDescriptor(src.descriptors, &src_binding, &src_array_element))
{
return;
}
const dst_desc = &self.descriptors[dst_binding];
const src_desc = src.descriptors[src_binding];
const dst_len = descriptorLen(dst_desc.*);
const src_len = descriptorLen(src_desc);
if (dst_len == 0 or src_len == 0) {
base.unsupported("descriptor type for copy", .{});
return;
}
const dst_remaining = dst_len - dst_array_element;
const src_remaining = src_len - src_array_element;
const copy_count = @min(descriptor_count, dst_remaining, src_remaining);
if (!copyDescriptorRange(dst_desc, dst_array_element, src_desc, src_array_element, copy_count)) {
base.unsupported("descriptor type for copy", .{});
return;
}
descriptor_count -= copy_count;
dst_array_element += copy_count;
src_array_element += copy_count;
}
}
pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
switch (write_data.descriptor_type) {
.storage_buffer, .storage_buffer_dynamic => {
.uniform_buffer,
.storage_buffer,
.storage_buffer_dynamic,
=> {
for (write_data.p_buffer_info, 0..write_data.descriptor_count) |buffer_info, i| {
const desc = &self.descriptors[write_data.dst_binding].buffer[i];
desc.* = .{
@@ -131,7 +320,10 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v
}
}
},
.storage_image, .input_attachment => {
.storage_image,
.input_attachment,
=> {
for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| {
const desc = &self.descriptors[write_data.dst_binding].image[i];
desc.* = .{ .object = null };
@@ -141,6 +333,39 @@ pub fn write(interface: *Interface, write_data: vk.WriteDescriptorSet) VkError!v
}
}
},
.storage_texel_buffer,
.uniform_texel_buffer,
=> {
for (write_data.p_texel_buffer_view, 0..write_data.descriptor_count) |view, i| {
const desc = &self.descriptors[write_data.dst_binding].texel_buffer[i];
desc.* = .{ .object = null };
if (view != .null_handle) {
const buffer_view = try NonDispatchable(BufferView).fromHandleObject(view);
desc.object = @as(*SoftBufferView, @alignCast(@fieldParentPtr("interface", buffer_view)));
}
}
},
.combined_image_sampler,
=> {
for (write_data.p_image_info, 0..write_data.descriptor_count) |image_info, i| {
const desc = &self.descriptors[write_data.dst_binding].texture[i];
desc.* = .{
.sampler = null,
.view = null,
};
if (image_info.image_view != .null_handle) {
const image_view = try NonDispatchable(ImageView).fromHandleObject(image_info.image_view);
desc.view = @as(*SoftImageView, @alignCast(@fieldParentPtr("interface", image_view)));
}
if (image_info.sampler != .null_handle) {
const sampler = try NonDispatchable(Sampler).fromHandleObject(image_info.sampler);
desc.sampler = @as(*SoftSampler, @alignCast(@fieldParentPtr("interface", sampler)));
}
}
},
else => {
self.descriptors[write_data.dst_binding] = .{ .unsupported = .{} };
base.unsupported("descriptor type {s} for writting", .{@tagName(write_data.descriptor_type)});
+2 -2
View File
@@ -54,12 +54,12 @@ pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.De
_ = size;
}
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque {
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
if (offset >= self.data.len or (size != vk.WHOLE_SIZE and offset + size > self.data.len)) {
return VkError.MemoryMapFailed;
}
return @ptrCast(&self.data[offset]);
return if (size == vk.WHOLE_SIZE) self.data[offset..] else self.data[offset..(offset + size)];
}
pub fn unmap(_: *Interface) void {
+47
View File
@@ -5,6 +5,11 @@ const base = @import("base");
const VkError = base.VkError;
const Device = base.Device;
const blitter = @import("device/blitter.zig");
const SoftImage = @import("SoftImage.zig");
const SoftRenderPass = @import("SoftRenderPass.zig");
const Self = @This();
pub const Interface = base.Framebuffer;
@@ -30,3 +35,45 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
allocator.destroy(self);
}
pub fn resolveAttachments(self: *Self, render_pass: *SoftRenderPass, subpass_index: usize) VkError!void {
const subpass = render_pass.interface.subpasses[subpass_index];
if (subpass.resolve_attachments) |resolve_attachments| {
if (subpass.color_attachments) |color_attachments| {
for (color_attachments[0..], resolve_attachments[0..]) |color, resolve| {
if (resolve.attachment != vk.ATTACHMENT_UNUSED) {
const src_image_view = self.interface.attachments[color.attachment];
const src_image: *SoftImage = @alignCast(@fieldParentPtr("interface", src_image_view.image));
const dst_image_view = self.interface.attachments[resolve.attachment];
const dst_image: *SoftImage = @alignCast(@fieldParentPtr("interface", dst_image_view.image));
try blitter.resolveWithFormats(
src_image,
dst_image,
.{
.src_subresource = .{
.aspect_mask = src_image_view.subresource_range.aspect_mask,
.base_array_layer = src_image_view.subresource_range.base_array_layer,
.layer_count = src_image_view.subresource_range.layer_count,
.mip_level = src_image_view.subresource_range.base_mip_level,
},
.src_offset = .{ .x = 0, .y = 0, .z = 0 },
.dst_subresource = .{
.aspect_mask = dst_image_view.subresource_range.aspect_mask,
.base_array_layer = dst_image_view.subresource_range.base_array_layer,
.layer_count = dst_image_view.subresource_range.layer_count,
.mip_level = dst_image_view.subresource_range.base_mip_level,
},
.dst_offset = .{ .x = 0, .y = 0, .z = 0 },
.extent = src_image.getMipLevelExtent(src_image_view.subresource_range.base_mip_level),
},
src_image_view.format,
dst_image_view.format,
);
}
}
}
}
}
+97 -44
View File
@@ -1,3 +1,55 @@
//! Image layout representation in contiguous memory
//!
//! ```text
//! ┌──────────────────────────────────────────────────┐
//! │ Device memory at image offset │
//! │ ┌──────────────────────────────────────────────┐ │
//! │ │ Aspect 0, e.g. color/depth │ │
//! │ │ │ │
//! │ │ Layer 0 │ │
//! │ │ ┌────────────────────────────────────────┐ │ │
//! │ │ │ Mip 0 │ │ │
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
//! │ │ │ │ z=0 slice │ │ │ │
//! │ │ │ │ row 0: [px][px][px][px] │ │ │ │
//! │ │ │ │ row 1: [px][px][px][px] │ │ │ │
//! │ │ │ │ row 2: [px][px][px][px] │ │ │ │
//! │ │ │ └──────────────────────────────────┘ │ │ │
//! │ │ │ │ │ │
//! │ │ │ Mip 1 │ │ │
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
//! │ │ │ │ row 0: [px][px] │ │ │ │
//! │ │ │ │ row 1: [px][px] │ │ │ │
//! │ │ │ └──────────────────────────────────┘ │ │ │
//! │ │ │ │ │ │
//! │ │ │ Mip 2 │ │ │
//! │ │ │ ┌──────────────────────────────────┐ │ │ │
//! │ │ │ │ row 0: [px] │ │ │ │
//! │ │ │ └──────────────────────────────────┘ │ │ │
//! │ │ └────────────────────────────────────────┘ │ │
//! │ │ │ │
//! │ │ Layer 1 │ │
//! │ │ ┌────────────────────────────────────────┐ │ │
//! │ │ │ Mip 0 │ │ │
//! │ │ │ row 0: [px][px][px][px] │ │ │
//! │ │ │ row 1: [px][px][px][px] │ │ │
//! │ │ │ row 2: [px][px][px][px] │ │ │
//! │ │ │ │ │ │
//! │ │ │ Mip 1 │ │ │
//! │ │ │ row 0: [px][px] │ │ │
//! │ │ │ row 1: [px][px] │ │ │
//! │ │ │ │ │ │
//! │ │ │ Mip 2 │ │ │
//! │ │ │ row 0: [px] │ │ │
//! │ │ └────────────────────────────────────────┘ │ │
//! │ └──────────────────────────────────────────────┘ │
//! │ ┌──────────────────────────────────────────────┐ │
//! │ │ Aspect 1, e.g. stencil │ │
//! │ │ ... │ │
//! │ └──────────────────────────────────────────────┘ │
//! └──────────────────────────────────────────────────┘
//! ```
const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
@@ -50,15 +102,18 @@ pub fn getMemoryRequirements(_: *Interface, requirements: *vk.MemoryRequirements
}
pub fn getClearFormat(self: *Self) VkError!vk.Format {
return if (base.c.vkuFormatIsSINT(@intCast(@intFromEnum(self.interface.format))))
return getClearFormatFor(self.interface.format);
}
pub fn getClearFormatFor(format: vk.Format) VkError!vk.Format {
return if (base.format.isSint(format))
.r32g32b32a32_sint
else if (base.c.vkuFormatIsUINT(@intCast(@intFromEnum(self.interface.format))))
else if (base.format.isUint(format))
.r32g32b32a32_uint
else
.r32g32b32a32_sfloat;
}
/// Based on SwiftShader vk::Image::copyTo
pub fn copyToImage(self: *const Self, dst: *Self, region: vk.ImageCopy) VkError!void {
const combined_depth_stencil_aspect: vk.ImageAspectFlags = .{
.depth_bit = true,
@@ -82,7 +137,6 @@ pub fn copyToImage(self: *const Self, dst: *Self, region: vk.ImageCopy) VkError!
}
}
/// Based on SwiftShader vk::Image::copySingleAspectTo
pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCopy) VkError!void {
if (!(region.src_subresource.aspect_mask == vk.ImageAspectFlags{ .color_bit = true } or
region.src_subresource.aspect_mask == vk.ImageAspectFlags{ .depth_bit = true } or
@@ -104,7 +158,7 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo
const bytes_per_block = base.format.texelSize(src_format);
const src_extent = self.getMipLevelExtent(region.src_subresource.mip_level);
const dst_extent = dst.getMipLevelExtent(region.src_subresource.mip_level);
const dst_extent = dst.getMipLevelExtent(region.dst_subresource.mip_level);
const one_is_3D = (self.interface.image_type == .@"3d") != (dst.interface.image_type == .@"3d");
const both_are_3D = (self.interface.image_type == .@"3d") and (dst.interface.image_type == .@"3d");
@@ -120,24 +174,13 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo
const src_layer_pitch = if (self.interface.image_type == .@"3d") src_depth_pitch_bytes else src_array_pitch;
const dst_layer_pitch = if (dst.interface.image_type == .@"3d") dst_depth_pitch_bytes else dst_array_pitch;
// If one image is 3D, extent.depth must match the layer count. If both images are 2D,
// depth is 1 but the source and destination subresource layer count must match.
const layer_count = if (one_is_3D) region.extent.depth else region.src_subresource.layer_count;
// Copies between 2D and 3D images are treated as layers, so only use depth as the slice count when both images are 3D.
const slice_count = if (both_are_3D) region.extent.depth else self.interface.samples.toInt();
const is_single_slice = (slice_count == 1);
const is_single_row = (region.extent.height == 1) and is_single_slice;
// In order to copy multiple rows using a single memcpy call, we
// have to make sure that we need to copy the entire row and that
// both source and destination rows have the same size in bytes
const is_entire_row = (region.extent.width == src_extent.width) and (region.extent.width == dst_extent.width);
// In order to copy multiple slices using a single memcpy call, we
// have to make sure that we need to copy the entire slice and that
// both source and destination slices have the same size in bytes
const is_entire_slice = is_entire_row and
(region.extent.height == src_extent.height) and
(region.extent.height == dst_extent.height) and
@@ -148,16 +191,14 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo
.mip_level = region.src_subresource.mip_level,
.array_layer = region.src_subresource.base_array_layer,
});
const src_size = try self.interface.getTotalSizeForAspect(region.src_subresource.aspect_mask);
var src_map = try self.mapAsSliceWithAddedOffset(u8, src_texel_offset, src_size);
var src_map = try self.mapAsSliceWithAddedOffset(u8, src_texel_offset, vk.WHOLE_SIZE);
const dst_texel_offset = try dst.getTexelMemoryOffset(region.dst_offset, .{
.aspect_mask = region.dst_subresource.aspect_mask,
.mip_level = region.dst_subresource.mip_level,
.array_layer = region.dst_subresource.base_array_layer,
});
const dst_size = try dst.interface.getTotalSizeForAspect(region.dst_subresource.aspect_mask);
var dst_map = try dst.mapAsSliceWithAddedOffset(u8, dst_texel_offset, dst_size);
var dst_map = try dst.mapAsSliceWithAddedOffset(u8, dst_texel_offset, vk.WHOLE_SIZE);
for (0..layer_count) |_| {
if (is_single_row) {
@@ -203,6 +244,9 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo
src_row_memory = if (src_row_memory.len < src_row_pitch_bytes) break else src_row_memory[src_row_pitch_bytes..];
dst_row_memory = if (dst_row_memory.len < dst_row_pitch_bytes) break else dst_row_memory[dst_row_pitch_bytes..];
}
src_slice_memory = if (src_slice_memory.len < src_depth_pitch_bytes) break else src_slice_memory[src_depth_pitch_bytes..];
dst_slice_memory = if (dst_slice_memory.len < dst_depth_pitch_bytes) break else dst_slice_memory[dst_depth_pitch_bytes..];
}
}
@@ -212,37 +256,38 @@ pub fn copyToImageSingleAspect(self: *const Self, dst: *Self, region: vk.ImageCo
}
pub fn copyToBuffer(self: *const Self, dst: *SoftBuffer, region: vk.BufferImageCopy) VkError!void {
const dst_size = dst.interface.size - region.buffer_offset;
const dst_offset = dst.interface.offset + region.buffer_offset;
const dst_map = try dst.mapAsSliceWithOffset(u8, dst_offset, dst_size);
const dst_map = try dst.mapAsSliceWithOffset(u8, dst_offset, vk.WHOLE_SIZE);
try self.copy(
null,
dst_map,
region.image_subresource,
region.image_offset,
region.image_extent,
region.buffer_row_length,
region.buffer_image_height,
);
}
pub fn copyFromBuffer(self: *const Self, src: *const SoftBuffer, region: vk.BufferImageCopy) VkError!void {
const src_size = src.interface.size - region.buffer_offset;
const src_offset = src.interface.offset + region.buffer_offset;
const src_map = try src.mapAsSliceWithOffset(u8, src_offset, src_size);
const src_map = try src.mapAsSliceWithOffset(u8, src_offset, vk.WHOLE_SIZE);
try self.copy(
src_map,
null,
region.image_subresource,
region.image_offset,
region.image_extent,
region.buffer_row_length,
region.buffer_image_height,
);
}
pub fn copyToMemory(interface: *const Interface, memory: []u8, subresource: vk.ImageSubresourceLayers) VkError!void {
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
try self.copy(null, memory, subresource, .{ .x = 0, .y = 0, .z = 0 }, interface.extent);
try self.copy(null, memory, subresource, .{ .x = 0, .y = 0, .z = 0 }, interface.extent, 0, 0);
}
/// Based on SwiftShader vk::Image::copy
pub fn copy(
self: *const Self,
base_src_memory: ?[]const u8,
@@ -250,6 +295,8 @@ pub fn copy(
image_subresource: vk.ImageSubresourceLayers,
image_offset: vk.Offset3D,
image_extent: vk.Extent3D,
row_length: u32,
image_height: u32,
) VkError!void {
std.debug.assert((base_src_memory == null) != (base_dst_memory == null));
@@ -266,23 +313,25 @@ pub fn copy(
const format = self.interface.formatFromAspect(image_subresource.aspect_mask);
// TODO: handle extent of compressed formats
if (image_extent.width == 0 or image_extent.height == 0 or image_extent.depth == 0) {
return;
}
const extent: vk.Extent2D = .{
.width = if (row_length == 0) image_extent.width else row_length,
.height = if (image_height == 0) image_extent.height else image_height,
};
const bytes_per_block = base.format.texelSize(format);
const memory_row_pitch_bytes = image_extent.width * bytes_per_block;
const memory_slice_pitch_bytes = image_extent.height * memory_row_pitch_bytes;
const memory_row_pitch_bytes = extent.width * bytes_per_block;
const memory_slice_pitch_bytes = extent.height * memory_row_pitch_bytes;
const image_texel_offset = try self.getTexelMemoryOffset(image_offset, .{
.aspect_mask = image_subresource.aspect_mask,
.mip_level = image_subresource.mip_level,
.array_layer = image_subresource.base_array_layer,
});
const image_size = try self.interface.getTotalSizeForAspect(image_subresource.aspect_mask);
const image_map = try self.mapAsSliceWithAddedOffset(u8, image_texel_offset, image_size);
const image_map = try self.mapAsSliceWithAddedOffset(u8, image_texel_offset, vk.WHOLE_SIZE);
var src_memory = if (is_source) base_src_memory orelse return VkError.InvalidDeviceMemoryDrv else image_map;
var dst_memory = if (is_source) image_map else base_dst_memory orelse return VkError.InvalidDeviceMemoryDrv;
@@ -360,7 +409,7 @@ pub fn getTexelMemoryOffset(self: *const Self, offset: vk.Offset3D, subresource:
return try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer) + self.getTexelMemoryOffsetInSubresource(offset, subresource);
}
fn getSubresourceOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, layer: u32) VkError!usize {
pub fn getSubresourceOffset(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, layer: u32) VkError!usize {
var offset = try self.getAspectOffset(aspect_mask);
for (0..mip_level) |mip| {
offset += self.getMultiSampledLevelSize(aspect_mask, @intCast(mip));
@@ -427,8 +476,8 @@ fn getSubresourceLayout(interface: *const Interface, subresource: vk.ImageSubres
.offset = try self.getSubresourceOffset(subresource.aspect_mask, subresource.mip_level, subresource.array_layer),
.size = self.getMultiSampledLevelSize(subresource.aspect_mask, subresource.mip_level),
.row_pitch = self.interface.getRowPitchMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level),
.array_pitch = self.interface.getSliceMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level),
.depth_pitch = self.getLayerSize(subresource.aspect_mask),
.array_pitch = self.getLayerSize(subresource.aspect_mask),
.depth_pitch = self.interface.getSliceMemSizeForMipLevel(subresource.aspect_mask, subresource.mip_level),
};
}
@@ -464,18 +513,22 @@ pub fn getMipLevelExtent(self: *const Self, mip_level: u32) vk.Extent3D {
pub fn getSliceMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
const mip_extent = self.getMipLevelExtent(mip_level);
const format = self.interface.formatFromAspect(aspect_mask);
return base.format.sliceMemSize(format, mip_extent.width, mip_extent.height);
return self.getSliceMemSizeForMipLevelWithFormat(aspect_mask, mip_level, interface.format);
}
pub fn getRowPitchMemSizeForMipLevel(interface: *const Interface, aspect_mask: vk.ImageAspectFlags, mip_level: u32) usize {
const self: *const Self = @alignCast(@fieldParentPtr("interface", interface));
return self.getRowPitchMemSizeForMipLevelWithFormat(aspect_mask, mip_level, interface.format);
}
pub fn getSliceMemSizeForMipLevelWithFormat(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, format: vk.Format) usize {
const mip_extent = self.getMipLevelExtent(mip_level);
const format = self.interface.formatFromAspect(aspect_mask);
return base.format.pitchMemSize(format, mip_extent.width);
return base.format.sliceMemSize(base.format.fromAspect(format, aspect_mask), mip_extent.width, mip_extent.height);
}
pub fn getRowPitchMemSizeForMipLevelWithFormat(self: *const Self, aspect_mask: vk.ImageAspectFlags, mip_level: u32, format: vk.Format) usize {
const mip_extent = self.getMipLevelExtent(mip_level);
return base.format.pitchMemSize(base.format.fromAspect(format, aspect_mask), mip_extent.width);
}
pub inline fn mapAs(self: *const Self, comptime T: type) VkError!*T {
@@ -504,18 +557,18 @@ pub inline fn mapAsSliceWithAddedOffset(self: *const Self, comptime T: type, off
pub fn mapAsWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!*T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, @sizeOf(T)))))[0..@sizeOf(T)];
const map = try memory.map(offset, @sizeOf(T));
return @alignCast(std.mem.bytesAsValue(T, map));
}
pub fn mapToWithOffset(self: *const Self, comptime T: type, offset: usize) VkError!T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, @sizeOf(T)))))[0..@sizeOf(T)];
const map = try memory.map(offset, @sizeOf(T));
return std.mem.bytesToValue(T, map);
}
pub fn mapAsSliceWithOffset(self: *const Self, comptime T: type, offset: usize, size: usize) VkError![]T {
const memory = if (self.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const map = @as([*]u8, @ptrCast(@alignCast(try memory.map(offset, size))))[0..size];
const map = try memory.map(offset, size);
return @alignCast(std.mem.bytesAsSlice(T, map));
}
-2
View File
@@ -2,8 +2,6 @@ const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const lib = @import("lib.zig");
const VkError = base.VkError;
const Device = base.Device;
+107 -48
View File
@@ -1,8 +1,8 @@
const std = @import("std");
const builtin = @import("builtin");
const vk = @import("vulkan");
const base = @import("base");
const lib = @import("lib.zig");
const cpuinfo = lib.c;
const SoftDevice = @import("SoftDevice.zig");
@@ -189,8 +189,6 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S
.shader_float_64 = .true,
.shader_int_64 = .true,
.shader_int_16 = .true,
.texture_compression_etc2 = .false,
.texture_compression_bc = .false,
};
var queue_family_props = [_]vk.QueueFamilyProperties{
@@ -200,35 +198,52 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S
.timestamp_valid_bits = 0,
.min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 },
},
.{
.queue_flags = .{ .graphics_bit = true },
.queue_count = 1,
.timestamp_valid_bits = 0,
.min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 },
},
.{
.queue_flags = .{ .transfer_bit = true },
.queue_count = 1,
.timestamp_valid_bits = 0,
.min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 },
},
// TODO: maybe add a compute specialized queue
};
interface.queue_family_props.appendSlice(allocator, queue_family_props[0..]) catch return VkError.OutOfHostMemory;
if (device_name[0] == 0) {
const name = blk: {
if (cpuinfo.cpuinfo_initialize()) {
const package = cpuinfo.cpuinfo_get_package(0).*;
const non_sentinel_name = package.name[0..(std.mem.len(@as([*:0]const u8, @ptrCast(&package.name))))];
break :blk std.fmt.allocPrint(command_allocator, "{s} ({d} threads)", .{ non_sentinel_name, package.processor_count }) catch return VkError.OutOfHostMemory;
// If arch is x86 we try to get precise CPU name through CPUID
// and fallback to vendor name if not available
if (comptime builtin.cpu.arch.isX86()) {
const max_extended_leaf = cpuid(0x80000000, 0).eax;
if (max_extended_leaf >= 0x80000004) {
var brand: [49]u8 = @splat(0);
for (0..3) |i| {
const regs = cpuid(0x80000002 + @as(u32, @intCast(i)), 0);
const offset = i * 16;
writeU32Le(brand[0..], offset + 0, regs.eax);
writeU32Le(brand[0..], offset + 4, regs.ebx);
writeU32Le(brand[0..], offset + 8, regs.ecx);
writeU32Le(brand[0..], offset + 12, regs.edx);
}
break :blk command_allocator.dupe(u8, "Unkown") catch return VkError.OutOfHostMemory;
const brand_str = std.mem.trim(u8, brand[0..48], " \x00");
break :blk command_allocator.dupe(u8, brand_str) catch return VkError.OutOfHostMemory;
} else {
var vendor: [13]u8 = @splat(0);
const leaf0 = cpuid(0, 0);
writeU32Le(vendor[0..], 0, leaf0.ebx);
writeU32Le(vendor[0..], 4, leaf0.edx);
writeU32Le(vendor[0..], 8, leaf0.ecx);
const vendor_str = std.mem.trim(u8, vendor[0..12], " \x00");
break :blk command_allocator.dupe(u8, vendor_str) catch return VkError.OutOfHostMemory;
}
}
break :blk command_allocator.dupe(u8, lib.PHYSICAL_DEVICE_DEFAULT_NAME) catch return VkError.OutOfHostMemory;
};
defer command_allocator.free(name);
var writer = std.Io.Writer.fixed(device_name[0 .. vk.MAX_PHYSICAL_DEVICE_NAME_SIZE - 1]);
writer.print("{s} [" ++ lib.DRIVER_NAME ++ " StrollDriver]", .{name}) catch return VkError.InitializationFailed;
writer.print("{s} [" ++ lib.DRIVER_NAME ++ " ApeDriver]", .{name}) catch return VkError.InitializationFailed;
}
@memcpy(&interface.props.device_name, &device_name);
@@ -312,32 +327,32 @@ pub fn getFormatProperties(interface: *Interface, format: vk.Format) VkError!vk.
.r32g32b32a32_sfloat,
.b10g11r11_ufloat_pack32,
.e5b9g9r9_ufloat_pack32,
.bc1_rgb_unorm_block,
.bc1_rgb_srgb_block,
.bc1_rgba_unorm_block,
.bc1_rgba_srgb_block,
.bc2_unorm_block,
.bc2_srgb_block,
.bc3_unorm_block,
.bc3_srgb_block,
.bc4_unorm_block,
.bc4_snorm_block,
.bc5_unorm_block,
.bc5_snorm_block,
.bc6h_ufloat_block,
.bc6h_sfloat_block,
.bc7_unorm_block,
.bc7_srgb_block,
.etc2_r8g8b8_unorm_block,
.etc2_r8g8b8_srgb_block,
.etc2_r8g8b8a1_unorm_block,
.etc2_r8g8b8a1_srgb_block,
.etc2_r8g8b8a8_unorm_block,
.etc2_r8g8b8a8_srgb_block,
.eac_r11_unorm_block,
.eac_r11_snorm_block,
.eac_r11g11_unorm_block,
.eac_r11g11_snorm_block,
//.bc1_rgb_unorm_block,
//.bc1_rgb_srgb_block,
//.bc1_rgba_unorm_block,
//.bc1_rgba_srgb_block,
//.bc2_unorm_block,
//.bc2_srgb_block,
//.bc3_unorm_block,
//.bc3_srgb_block,
//.bc4_unorm_block,
//.bc4_snorm_block,
//.bc5_unorm_block,
//.bc5_snorm_block,
//.bc6h_ufloat_block,
//.bc6h_sfloat_block,
//.bc7_unorm_block,
//.bc7_srgb_block,
//.etc2_r8g8b8_unorm_block,
//.etc2_r8g8b8_srgb_block,
//.etc2_r8g8b8a1_unorm_block,
//.etc2_r8g8b8a1_srgb_block,
//.etc2_r8g8b8a8_unorm_block,
//.etc2_r8g8b8a8_srgb_block,
//.eac_r11_unorm_block,
//.eac_r11_snorm_block,
//.eac_r11g11_unorm_block,
//.eac_r11g11_snorm_block,
//.astc_4x_4_unorm_block,
//.astc_5x_4_unorm_block,
//.astc_5x_5_unorm_block,
@@ -856,3 +871,47 @@ fn checkFormatUsage(usage: vk.ImageUsageFlags, features: vk.FormatFeatureFlags)
pub fn getSurfaceSupportKHR(_: *Interface, _: u32, _: *SurfaceKHR) VkError!bool {
return true;
}
const CpuidRegs = packed struct {
eax: u32,
ebx: u32,
ecx: u32,
edx: u32,
};
fn cpuid(leaf_id: u32, subleaf_id: u32) CpuidRegs {
comptime {
switch (builtin.cpu.arch) {
.x86, .x86_64 => {},
else => @compileError("cpuid is only available on x86/x86_64"),
}
}
var eax: u32 = undefined;
var ebx: u32 = undefined;
var ecx: u32 = undefined;
var edx: u32 = undefined;
asm volatile ("cpuid"
: [_] "={eax}" (eax),
[_] "={ebx}" (ebx),
[_] "={ecx}" (ecx),
[_] "={edx}" (edx),
: [_] "{eax}" (leaf_id),
[_] "{ecx}" (subleaf_id),
);
return .{
.eax = eax,
.ebx = ebx,
.ecx = ecx,
.edx = edx,
};
}
fn writeU32Le(dst: []u8, offset: usize, value: u32) void {
dst[offset + 0] = @truncate(value);
dst[offset + 1] = @truncate(value >> 8);
dst[offset + 2] = @truncate(value >> 16);
dst[offset + 3] = @truncate(value >> 24);
}
+330 -42
View File
@@ -3,6 +3,10 @@ const vk = @import("vulkan");
const base = @import("base");
const spv = @import("spv");
const zm = base.zm;
const blitter = @import("device/blitter.zig");
const Device = base.Device;
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
@@ -11,17 +15,25 @@ const NonDispatchable = base.NonDispatchable;
const ShaderModule = base.ShaderModule;
const SoftDevice = @import("SoftDevice.zig");
const SoftBuffer = @import("SoftBuffer.zig");
const SoftBufferView = @import("SoftBufferView.zig");
const SoftImage = @import("SoftImage.zig");
const SoftImageView = @import("SoftImageView.zig");
const SoftInstance = @import("SoftInstance.zig");
const SoftSampler = @import("SoftSampler.zig");
const SoftShaderModule = @import("SoftShaderModule.zig");
const Self = @This();
pub const Interface = base.Pipeline;
const Runtime = struct {
mutex: std.Io.Mutex,
rt: spv.Runtime,
};
const Shader = struct {
module: *SoftShaderModule,
runtimes: []spv.Runtime,
runtimes: []Runtime,
entry: []const u8,
};
@@ -54,9 +66,13 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
const device_allocator = soft_device.device_allocator.allocator();
var runtimes_allocator_arena: std.heap.ArenaAllocator = .init(device_allocator);
errdefer runtimes_allocator_arena.deinit();
const runtimes_allocator = runtimes_allocator_arena.allocator();
self.* = .{
.interface = interface,
.runtimes_allocator = .init(device_allocator),
.stages = std.EnumMap(Stages, Shader).init(.{}),
};
errdefer self.runtimes_allocator.deinit();
const runtimes_allocator = self.runtimes_allocator.allocator();
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
const runtimes_count = switch (instance.threaded.async_limit) {
@@ -68,19 +84,16 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
},
};
self.* = .{
.interface = interface,
.runtimes_allocator = runtimes_allocator_arena,
.stages = std.EnumMap(Stages, Shader).init(.{
.compute = blk: {
self.stages.put(.compute, blk: {
var shader: Shader = undefined;
soft_module.ref();
shader.module = soft_module;
const runtimes = runtimes_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfDeviceMemory;
const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfDeviceMemory;
for (runtimes) |*runtime| {
runtime.* = spv.Runtime.init(
runtime.mutex = .init;
runtime.rt = spv.Runtime.init(
runtimes_allocator,
&soft_module.module,
.{
@@ -88,6 +101,8 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
.readImageInt4 = readImageInt4,
.writeImageFloat4 = writeImageFloat4,
.writeImageInt4 = writeImageInt4,
.sampleImageFloat4 = sampleImageFloat4,
.queryImageSize = queryImageSize,
},
) catch |err| {
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)});
@@ -97,7 +112,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
if (specialization.p_map_entries) |map| {
const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size];
for (map[0..], 0..specialization.map_entry_count) |entry, _| {
runtime.addSpecializationInfo(
runtime.rt.addSpecializationInfo(
runtimes_allocator,
.{
.id = @intCast(entry.constant_id),
@@ -116,9 +131,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
std.log.scoped(.ComputePipeline).debug("Created {d} runtimes for compute stage", .{runtimes_count});
break :blk shader;
},
}),
};
});
return self;
}
@@ -135,9 +148,13 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device));
const device_allocator = soft_device.device_allocator.allocator();
var runtimes_allocator_arena: std.heap.ArenaAllocator = .init(device_allocator);
errdefer runtimes_allocator_arena.deinit();
const runtimes_allocator = runtimes_allocator_arena.allocator();
self.* = .{
.interface = interface,
.runtimes_allocator = .init(device_allocator),
.stages = std.EnumMap(Stages, Shader).init(.{}),
};
errdefer self.runtimes_allocator.deinit();
const runtimes_allocator = self.runtimes_allocator.allocator();
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
const runtimes_count = switch (instance.threaded.async_limit) {
@@ -149,12 +166,6 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
},
};
self.* = .{
.interface = interface,
.runtimes_allocator = runtimes_allocator_arena,
.stages = std.EnumMap(Stages, Shader).init(.{}),
};
if (info.p_stages) |stages| {
for (stages[0..], 0..info.stage_count) |stage, _| {
var shader: Shader = undefined;
@@ -164,10 +175,11 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
soft_module.ref();
shader.module = soft_module;
const runtimes = runtimes_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory;
const runtimes = runtimes_allocator.alloc(Runtime, runtimes_count) catch return VkError.OutOfHostMemory;
for (runtimes) |*runtime| {
runtime.* = spv.Runtime.init(
runtime.mutex = .init;
runtime.rt = spv.Runtime.init(
runtimes_allocator,
&soft_module.module,
.{
@@ -175,6 +187,8 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
.readImageInt4 = readImageInt4,
.writeImageFloat4 = writeImageFloat4,
.writeImageInt4 = writeImageInt4,
.sampleImageFloat4 = sampleImageFloat4,
.queryImageSize = queryImageSize,
},
) catch |err| {
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)});
@@ -184,7 +198,7 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
if (specialization.p_map_entries) |map| {
const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size];
for (map[0..], 0..specialization.map_entry_count) |entry, _| {
runtime.addSpecializationInfo(runtimes_allocator, .{
runtime.rt.addSpecializationInfo(runtimes_allocator, .{
.id = @intCast(entry.constant_id),
.offset = @intCast(entry.offset),
.size = @intCast(entry.size),
@@ -228,30 +242,45 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner));
const device_allocator = soft_device.device_allocator.allocator();
var it = self.stages.iterator();
while (it.next()) |entry| {
entry.value.module.unref(allocator);
for (entry.value.runtimes) |*runtime| {
runtime.rt.function_stack.clearAndFree(device_allocator); // Hacky to avoid leaks
}
}
self.runtimes_allocator.deinit();
allocator.destroy(self);
}
fn readImageFloat4(context: *anyopaque, x: i32, y: i32, z: i32) SpvRuntimeError!spv.Runtime.Vec4(f32) {
fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32) SpvRuntimeError!spv.Runtime.Vec4(f32) {
var pixel = zm.f32x4s(0.0);
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
pixel = blitter.readFloat4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
} else {
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const pixel = image.readFloat4(
const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0;
pixel = image.readFloat4(
.{
.x = x,
.y = y,
.z = z,
.z = if (dim == .Cube) 0 else z,
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
.mip_level = image_view.interface.subresource_range.base_mip_level,
.array_layer = image_view.interface.subresource_range.base_array_layer,
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
},
image_view.interface.format,
) catch return SpvRuntimeError.Unknown;
}
return .{
.x = pixel[0],
.y = pixel[1],
@@ -260,22 +289,31 @@ fn readImageFloat4(context: *anyopaque, x: i32, y: i32, z: i32) SpvRuntimeError!
};
}
fn readImageInt4(context: *anyopaque, x: i32, y: i32, z: i32) SpvRuntimeError!spv.Runtime.Vec4(u32) {
fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32) SpvRuntimeError!spv.Runtime.Vec4(u32) {
var pixel = @Vector(4, u32){ 0, 0, 0, 0 };
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
pixel = blitter.readInt4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
} else {
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const pixel = image.readInt4(
const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0;
pixel = image.readInt4(
.{
.x = x,
.y = y,
.z = z,
.z = if (dim == .Cube) 0 else z,
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
.mip_level = image_view.interface.subresource_range.base_mip_level,
.array_layer = image_view.interface.subresource_range.base_array_layer,
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
},
image_view.interface.format,
) catch return SpvRuntimeError.Unknown;
}
return .{
.x = pixel[0],
.y = pixel[1],
@@ -284,33 +322,239 @@ fn readImageInt4(context: *anyopaque, x: i32, y: i32, z: i32) SpvRuntimeError!sp
};
}
fn writeImageFloat4(context: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) SpvRuntimeError!void {
fn writeImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) SpvRuntimeError!void {
const vec_pixel = zm.f32x4(pixel.x, pixel.y, pixel.z, pixel.w);
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
blitter.writeFloat4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
} else {
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0;
image.writeFloat4(
.{
.x = x,
.y = y,
.z = z,
.z = if (dim == .Cube) 0 else z,
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
.mip_level = image_view.interface.subresource_range.base_mip_level,
.array_layer = image_view.interface.subresource_range.base_array_layer,
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
},
image_view.interface.format,
.{ pixel.x, pixel.y, pixel.z, pixel.w },
vec_pixel,
) catch return SpvRuntimeError.Unknown;
}
}
fn writeImageInt4(context: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) SpvRuntimeError!void {
fn writeImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) SpvRuntimeError!void {
const vec_pixel = @Vector(4, u32){ pixel.x, pixel.y, pixel.z, pixel.w };
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
blitter.writeInt4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format);
} else {
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const cube_face: u32 = if (dim == .Cube) @intCast(z) else 0;
image.writeInt4(
.{
.x = x,
.y = y,
.z = z,
.z = if (dim == .Cube) 0 else z,
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
.mip_level = image_view.interface.subresource_range.base_mip_level,
.array_layer = image_view.interface.subresource_range.base_array_layer + cube_face,
},
image_view.interface.format,
vec_pixel,
) catch return SpvRuntimeError.Unknown;
}
}
const CubeCoordinate = struct {
face: u32,
u: f32,
v: f32,
};
fn resolveCubeCoordinate(x: f32, y: f32, z: f32) CubeCoordinate {
const ax = @abs(x);
const ay = @abs(y);
const az = @abs(z);
var face: u32 = 0;
var sc: f32 = 0.0;
var tc: f32 = 0.0;
var ma: f32 = 1.0;
if (ax >= ay and ax >= az) {
ma = ax;
if (x >= 0.0) {
face = 0;
sc = -z;
tc = -y;
} else {
face = 1;
sc = z;
tc = -y;
}
} else if (ay >= ax and ay >= az) {
ma = ay;
if (y >= 0.0) {
face = 2;
sc = x;
tc = z;
} else {
face = 3;
sc = x;
tc = -z;
}
} else {
ma = az;
if (z >= 0.0) {
face = 4;
sc = x;
tc = -y;
} else {
face = 5;
sc = -x;
tc = -y;
}
}
const inv_ma = if (ma == 0.0) 0.0 else 1.0 / ma;
return .{
.face = face,
.u = (sc * inv_ma + 1.0) * 0.5,
.v = (tc * inv_ma + 1.0) * 0.5,
};
}
fn cubeDirection(face: u32, u: f32, v: f32) struct { x: f32, y: f32, z: f32 } {
const sc = u * 2.0 - 1.0;
const tc = v * 2.0 - 1.0;
return switch (face) {
0 => .{ .x = 1.0, .y = -tc, .z = -sc },
1 => .{ .x = -1.0, .y = -tc, .z = sc },
2 => .{ .x = sc, .y = 1.0, .z = tc },
3 => .{ .x = sc, .y = -1.0, .z = -tc },
4 => .{ .x = sc, .y = -tc, .z = 1.0 },
5 => .{ .x = -sc, .y = -tc, .z = -1.0 },
else => .{ .x = 0.0, .y = 0.0, .z = 0.0 },
};
}
fn readSampledFloat4(
image: *SoftImage,
image_view: *SoftImageView,
dim: spv.SpvDim,
coord: CubeCoordinate,
ix: i32,
iy: i32,
) VkError!zm.F32x4 {
const width_f: f32 = @floatFromInt(image.interface.extent.width);
const height_f: f32 = @floatFromInt(image.interface.extent.height);
const texel = if (dim == .Cube) blk: {
const dir = cubeDirection(
coord.face,
(@as(f32, @floatFromInt(ix)) + 0.5) / width_f,
(@as(f32, @floatFromInt(iy)) + 0.5) / height_f,
);
break :blk resolveCubeCoordinate(dir.x, dir.y, dir.z);
} else coord;
const result = try image.readFloat4(
.{
.x = if (dim == .Cube)
std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, image.interface.extent.width - 1)
else
std.math.clamp(ix, 0, image.interface.extent.width - 1),
.y = if (dim == .Cube)
std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, image.interface.extent.height - 1)
else
std.math.clamp(iy, 0, image.interface.extent.height - 1),
.z = 0,
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
.mip_level = image_view.interface.subresource_range.base_mip_level,
.array_layer = image_view.interface.subresource_range.base_array_layer + texel.face,
},
image_view.interface.format,
);
return result;
}
fn sampleNearestFloat4(image: *SoftImage, image_view: *SoftImageView, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 {
const width_f: f32 = @floatFromInt(image.interface.extent.width);
const height_f: f32 = @floatFromInt(image.interface.extent.height);
return readSampledFloat4(
image,
image_view,
dim,
coord,
@intFromFloat(coord.u * width_f),
@intFromFloat(coord.v * height_f),
);
}
fn sampleLinearFloat4(image: *SoftImage, image_view: *SoftImageView, dim: spv.SpvDim, coord: CubeCoordinate) VkError!zm.F32x4 {
const width_f: f32 = @floatFromInt(image.interface.extent.width);
const height_f: f32 = @floatFromInt(image.interface.extent.height);
const x = coord.u * width_f - 0.5;
const y = coord.v * height_f - 0.5;
const x0: i32 = @intFromFloat(@floor(x));
const y0: i32 = @intFromFloat(@floor(y));
const x1 = x0 + 1;
const y1 = y0 + 1;
const wx = x - @as(f32, @floatFromInt(x0));
const wy = y - @as(f32, @floatFromInt(y0));
const p00 = try readSampledFloat4(image, image_view, dim, coord, x0, y0);
const p10 = try readSampledFloat4(image, image_view, dim, coord, x1, y0);
const p01 = try readSampledFloat4(image, image_view, dim, coord, x0, y1);
const p11 = try readSampledFloat4(image, image_view, dim, coord, x1, y1);
const row0 = p00 * zm.f32x4s(1.0 - wx) + p10 * zm.f32x4s(wx);
const row1 = p01 * zm.f32x4s(1.0 - wx) + p11 * zm.f32x4s(wx);
return row0 * zm.f32x4s(1.0 - wy) + row1 * zm.f32x4s(wy);
}
fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32) SpvRuntimeError!spv.Runtime.Vec4(f32) {
var pixel = zm.f32x4s(0.0);
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer));
const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown;
_ = map;
} else {
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const sampler: *SoftSampler = @ptrCast(@alignCast(context2));
if (dim == .Cube) {
const coord = resolveCubeCoordinate(x, y, z);
pixel = switch (sampler.interface.mag_filter) {
.linear => sampleLinearFloat4(image, image_view, dim, coord),
else => sampleNearestFloat4(image, image_view, dim, coord),
} catch return SpvRuntimeError.Unknown;
} else {
pixel = image.readFloat4(
.{
.x = std.math.clamp(@as(i32, @intFromFloat(x * @as(f32, @floatFromInt(image.interface.extent.width)))), 0, image.interface.extent.width - 1),
.y = std.math.clamp(@as(i32, @intFromFloat(y * @as(f32, @floatFromInt(image.interface.extent.height)))), 0, image.interface.extent.height - 1),
.z = std.math.clamp(@as(i32, @intFromFloat(z * @as(f32, @floatFromInt(image.interface.extent.depth)))), 0, image.interface.extent.depth - 1),
},
.{
.aspect_mask = image_view.interface.subresource_range.aspect_mask,
@@ -318,6 +562,50 @@ fn writeImageInt4(context: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtim
.array_layer = image_view.interface.subresource_range.base_array_layer,
},
image_view.interface.format,
.{ pixel.x, pixel.y, pixel.z, pixel.w },
) catch return SpvRuntimeError.Unknown;
}
}
return .{
.x = pixel[0],
.y = pixel[1],
.z = pixel[2],
.w = pixel[3],
};
}
fn queryImageSize(context: *anyopaque, dim: spv.SpvDim, arrayed: bool) SpvRuntimeError!spv.Runtime.Vec4(u32) {
if (dim == .Buffer) {
const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context));
const range = if (buffer_view.interface.range == vk.WHOLE_SIZE)
buffer_view.interface.buffer.size - buffer_view.interface.offset
else
buffer_view.interface.range;
return .{
.x = @intCast(@divTrunc(range, base.format.texelSize(buffer_view.interface.format))),
.y = 0,
.z = 0,
.w = 0,
};
}
const image_view: *SoftImageView = @ptrCast(@alignCast(context));
const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image));
const extent = image.getMipLevelExtent(image_view.interface.subresource_range.base_mip_level);
const layers = if (image_view.interface.subresource_range.layer_count == vk.REMAINING_ARRAY_LAYERS)
image.interface.array_layers - image_view.interface.subresource_range.base_array_layer
else
image_view.interface.subresource_range.layer_count;
return switch (dim) {
.@"1D" => if (arrayed)
.{ .x = extent.width, .y = layers, .z = 0, .w = 0 }
else
.{ .x = extent.width, .y = 0, .z = 0, .w = 0 },
.@"2D", .Cube, .Rect => if (arrayed)
.{ .x = extent.width, .y = extent.height, .z = layers, .w = 0 }
else
.{ .x = extent.width, .y = extent.height, .z = 0, .w = 0 },
.@"3D" => .{ .x = extent.width, .y = extent.height, .z = extent.depth, .w = 0 },
else => .{ .x = extent.width, .y = extent.height, .z = layers, .w = 0 },
};
}
+1 -2
View File
@@ -95,8 +95,7 @@ fn taskRunner(self: *Self, info: Interface.SubmitInfo, p_fence: ?*base.Fence, ru
}
var execution_device: ExecutionDevice = undefined;
execution_device.init(soft_device);
defer execution_device.deinit();
execution_device.setup(soft_device);
for (info.command_buffers.items) |command_buffer| {
const soft_command_buffer: *SoftCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer));
+2
View File
@@ -47,9 +47,11 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
spv.Module.ModuleError.OutOfMemory => return VkError.OutOfHostMemory,
else => {
std.log.scoped(.@"SPIR-V module").err("module creation catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return VkError.ValidationFailed;
},
},
-1
View File
@@ -1 +0,0 @@
#include <cpuinfo.h>
+8 -10
View File
@@ -6,19 +6,19 @@ const Self = @This();
const Allocator = std.mem.Allocator;
const Alignment = std.mem.Alignment;
mutex: base.SpinMutex,
child_allocator: std.mem.Allocator,
bound: usize,
total_bytes_allocated: std.atomic.Value(usize),
peak_concurrent_bytes_allocated: std.atomic.Value(usize),
current_bytes_allocated: std.atomic.Value(usize),
pub fn init(child_allocator: Allocator, bound: usize) Self {
return .{
.mutex = .{},
.child_allocator = child_allocator,
.bound = bound,
.total_bytes_allocated = std.atomic.Value(usize).init(0),
.current_bytes_allocated = std.atomic.Value(usize).init(0),
.peak_concurrent_bytes_allocated = std.atomic.Value(usize).init(0),
};
}
@@ -38,20 +38,22 @@ pub inline fn queryFootprint(self: *Self) usize {
return self.total_bytes_allocated.load(.monotonic);
}
pub inline fn queryPeakFootprint(self: *Self) usize {
return self.peak_concurrent_bytes_allocated.load(.monotonic);
}
fn alloc(context: *anyopaque, len: usize, alignment: Alignment, ret_addr: usize) ?[*]u8 {
const self: *Self = @ptrCast(@alignCast(context));
self.mutex.lock();
defer self.mutex.unlock();
if (self.current_bytes_allocated.fetchAdd(len, .monotonic) >= self.bound)
return null;
_ = self.total_bytes_allocated.fetchAdd(len, .monotonic);
if (self.current_bytes_allocated.load(.monotonic) > self.peak_concurrent_bytes_allocated.load(.monotonic))
self.peak_concurrent_bytes_allocated.store(self.current_bytes_allocated.load(.monotonic), .monotonic);
return self.child_allocator.rawAlloc(len, alignment, ret_addr);
}
fn resize(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) bool {
const self: *Self = @ptrCast(@alignCast(context));
self.mutex.lock();
defer self.mutex.unlock();
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
if (self.current_bytes_allocated.fetchAdd(new_len, .monotonic) >= self.bound)
return false;
@@ -61,8 +63,6 @@ fn resize(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize,
fn remap(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
const self: *Self = @ptrCast(@alignCast(context));
self.mutex.lock();
defer self.mutex.unlock();
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
if (self.current_bytes_allocated.fetchAdd(new_len, .monotonic) >= self.bound)
return null;
@@ -72,8 +72,6 @@ fn remap(context: *anyopaque, ptr: []u8, alignment: Alignment, new_len: usize, r
fn free(context: *anyopaque, ptr: []u8, alignment: Alignment, ret_addr: usize) void {
const self: *Self = @ptrCast(@alignCast(context));
self.mutex.lock();
defer self.mutex.unlock();
_ = self.current_bytes_allocated.fetchSub(ptr.len, .monotonic);
return self.child_allocator.rawFree(ptr, alignment, ret_addr);
}
+97 -46
View File
@@ -4,7 +4,8 @@ const base = @import("base");
const spv = @import("spv");
const lib = @import("../lib.zig");
const PipelineState = @import("Device.zig").PipelineState;
const ExecutionDevice = @import("Device.zig");
const PipelineState = ExecutionDevice.PipelineState;
const SoftDevice = @import("../SoftDevice.zig");
const SoftPipeline = @import("../SoftPipeline.zig");
@@ -45,10 +46,6 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self {
};
}
pub fn deinit(self: *Self) void {
_ = self;
}
pub fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count_z: u32) VkError!void {
const group_count: usize = @intCast(group_count_x * group_count_y * group_count_z);
@@ -61,6 +58,14 @@ pub fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count
self.invocation_index.store(0, .monotonic);
const io = self.device.interface.io();
const timer = std.Io.Timestamp.now(io, .real);
defer if (comptime base.config.logs != .none) {
const duration = timer.untilNow(io, .real);
const ms: f32 = @floatFromInt(duration.toMicroseconds());
std.log.scoped(.ComputeDispatcher).debug("Compute dispatch took {}ms", .{ms / 1000});
};
var wg: std.Io.Group = .init;
for (0..@min(self.batch_size, group_count)) |batch_id| {
const run_data: RunData = .{
@@ -85,9 +90,11 @@ pub fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count
fn runWrapper(data: RunData) void {
@call(.always_inline, run, .{data}) catch |err| {
std.log.scoped(.@"SPIR-V runtime").err("SPIR-V runtime catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
};
}
@@ -96,11 +103,31 @@ inline fn run(data: RunData) !void {
const io = data.self.device.interface.io();
const shader = data.pipeline.stages.getPtrAssertContains(.compute);
const rt = &shader.runtimes[data.batch_id];
const rt = &shader.runtimes[data.batch_id].rt;
const entry = try rt.getEntryPointByName(shader.entry);
const uses_control_barrier = hasControlBarrier(rt.mod.code);
try data.self.writeDescriptorSets(rt);
var barrier_runtimes: []spv.Runtime = &.{};
var barrier_statuses: []spv.Runtime.EntryPointStatus = &.{};
if (uses_control_barrier) {
barrier_runtimes = try allocator.alloc(spv.Runtime, data.invocations_per_workgroup);
barrier_statuses = try allocator.alloc(spv.Runtime.EntryPointStatus, data.invocations_per_workgroup);
for (barrier_runtimes) |*barrier_rt| {
barrier_rt.* = try spv.Runtime.init(allocator, rt.mod, rt.image_api);
try barrier_rt.copySpecializationConstantsFrom(allocator, rt);
}
}
defer {
for (barrier_runtimes) |*barrier_rt| {
barrier_rt.deinit(allocator);
}
allocator.free(barrier_runtimes);
allocator.free(barrier_statuses);
}
if (!uses_control_barrier)
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
var group_index: usize = data.batch_id;
while (group_index < data.group_count) : (group_index += data.self.batch_size) {
@@ -114,15 +141,23 @@ inline fn run(data: RunData) !void {
modulo -= group_y * data.group_count_x;
const group_x = modulo;
try setupWorkgroupBuiltins(data.self, rt, .{
const group_count_vec = @Vector(3, u32){
@as(u32, @intCast(data.group_count_x)),
@as(u32, @intCast(data.group_count_y)),
@as(u32, @intCast(data.group_count_z)),
}, .{
};
const group_id_vec = @Vector(3, u32){
@as(u32, @intCast(group_x)),
@as(u32, @intCast(group_y)),
@as(u32, @intCast(group_z)),
});
};
if (uses_control_barrier) {
try runBarrierWorkgroup(data, barrier_runtimes, barrier_statuses, entry, group_count_vec, group_id_vec);
continue;
}
try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec);
for (0..data.invocations_per_workgroup) |i| {
const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic);
@@ -156,6 +191,58 @@ inline fn run(data: RunData) !void {
}
}
fn runBarrierWorkgroup(
data: RunData,
runtimes: []spv.Runtime,
statuses: []spv.Runtime.EntryPointStatus,
entry: spv.SpvWord,
group_count: @Vector(3, u32),
group_id: @Vector(3, u32),
) !void {
const allocator = data.self.device.device_allocator.allocator();
for (runtimes, 0..) |*rt, i| {
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
try setupWorkgroupBuiltins(data.self, rt, group_count, group_id);
try setupSubgroupBuiltins(data.self, rt, group_id, i);
statuses[i] = try rt.beginEntryPoint(allocator, entry);
try rt.flushDescriptorSets(allocator);
}
while (true) {
var pending = false;
for (statuses) |status| {
if (status == .barrier) {
pending = true;
break;
}
}
if (!pending)
break;
for (runtimes, 0..) |*rt, i| {
if (statuses[i] == .completed)
continue;
statuses[i] = try rt.continueEntryPoint(allocator);
try rt.flushDescriptorSets(allocator);
}
}
}
/// TODO: Move this in the SPIR-V Interpreter
fn hasControlBarrier(code: []const spv.SpvWord) bool {
var i: usize = 5;
while (i < code.len) {
const opcode_data = code[i];
const word_count = (opcode_data & (~spv.spv.SpvOpCodeMask)) >> spv.spv.SpvWordCountShift;
const opcode: spv.spv.SpvOp = @enumFromInt(opcode_data & spv.spv.SpvOpCodeMask);
if (opcode == .ControlBarrier)
return true;
i += @max(word_count, 1);
}
return false;
}
inline fn dumpResultsTable(allocator: std.mem.Allocator, io: std.Io, rt: *spv.Runtime, is_early: bool) !void {
@branchHint(.cold);
const file = try std.Io.Dir.cwd().createFile(
@@ -169,42 +256,6 @@ inline fn dumpResultsTable(allocator: std.mem.Allocator, io: std.Io, rt: *spv.Ru
try rt.dumpResultsTable(allocator, &writer.interface);
}
fn writeDescriptorSets(self: *Self, rt: *spv.Runtime) !void {
sets: for (self.state.sets[0..], 0..) |set, set_index| {
if (set == null)
continue :sets;
bindings: for (set.?.descriptors[0..], 0..) |binding, binding_index| {
switch (binding) {
.buffer => |buffer_data_array| for (buffer_data_array, 0..) |buffer_data, descriptor_index| {
if (buffer_data.object) |buffer| {
const memory = if (buffer.interface.memory) |memory| memory else continue :bindings;
const map: []u8 = @as([*]u8, @ptrCast(try memory.map(buffer_data.offset, buffer_data.size)))[0..buffer_data.size];
try rt.writeDescriptorSet(
map,
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
}
},
.image => |image_data_array| for (image_data_array, 0..) |image_data, descriptor_index| {
if (image_data.object) |image_view| {
const addr: usize = @intFromPtr(image_view);
try rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
}
},
else => {},
}
}
}
}
fn setupWorkgroupBuiltins(
self: *Self,
rt: *spv.Runtime,
+74 -4
View File
@@ -2,6 +2,7 @@ const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const lib = @import("../lib.zig");
const spv = @import("spv");
const SoftDescriptorSet = @import("../SoftDescriptorSet.zig");
const SoftDevice = @import("../SoftDevice.zig");
@@ -39,7 +40,7 @@ pipeline_states: [2]PipelineState,
/// Initializating an execution device and
/// not creating one to avoid dangling pointers
pub fn init(self: *Self, device: *SoftDevice) void {
pub fn setup(self: *Self, device: *SoftDevice) void {
for (self.pipeline_states[0..], 0..) |*state, i| {
state.* = .{
.pipeline = null,
@@ -61,7 +62,76 @@ pub fn init(self: *Self, device: *SoftDevice) void {
self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.graphics)]);
}
pub fn deinit(self: *Self) void {
self.compute.deinit();
self.renderer.deinit();
pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void {
sets: for (state.sets[0..], 0..) |set, set_index| {
if (set == null)
continue :sets;
bindings: for (set.?.descriptors[0..], 0..) |binding, binding_index| {
switch (binding) {
.buffer => |buffer_data_array| for (buffer_data_array, 0..) |buffer_data, descriptor_index| {
if (buffer_data.object) |buffer| {
const map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset, buffer_data.size) catch continue :bindings;
try rt.writeDescriptorSet(
map,
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
}
},
.image => |image_data_array| for (image_data_array, 0..) |image_data, descriptor_index| {
if (image_data.object) |image_view| {
const addr: usize = @intFromPtr(image_view);
try rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
}
},
.texel_buffer => |texel_data_array| for (texel_data_array, 0..) |texel_data, descriptor_index| {
if (texel_data.object) |buffer_view| {
const addr: usize = @intFromPtr(buffer_view);
try rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
}
},
.texture => |texture_data_array| for (texture_data_array, 0..) |texture_data, descriptor_index| {
const SampledImage = packed struct {
image: usize,
sampler: usize,
};
var data: SampledImage = undefined;
if (texture_data.view) |image_view| {
const addr: usize = @intFromPtr(image_view);
data.image = addr;
}
if (texture_data.sampler) |sampler| {
const addr: usize = @intFromPtr(sampler);
data.sampler = addr;
}
try rt.writeDescriptorSet(
std.mem.asBytes(&data),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
},
else => {},
}
}
}
}
+74 -10
View File
@@ -4,7 +4,8 @@ const base = @import("base");
const zm = base.zm;
const spv = @import("spv");
const PipelineState = @import("Device.zig").PipelineState;
const ExecutionDevice = @import("Device.zig");
const PipelineState = ExecutionDevice.PipelineState;
const BoundedAllocator = @import("BoundedAllocator.zig");
const SoftBuffer = @import("../SoftBuffer.zig");
@@ -59,12 +60,35 @@ pub const DrawCall = struct {
viewport: vk.Viewport,
scissor: vk.Rect2D,
pub fn init(allocator: std.mem.Allocator, vertex_count: usize, instance_count: usize, renderer: *Self) VkError!@This() {
color_attachments: []*base.ImageView,
depth_attachment: ?*base.ImageView,
render_pass: *SoftRenderPass,
framebuffer: *SoftFramebuffer,
rasterizer_wait_group: std.Io.Group,
stats: struct {
polygons_drawn: usize,
},
fn init(allocator: std.mem.Allocator, vertex_count: usize, instance_count: usize, renderer: *Self) VkError!@This() {
const framebuffer = renderer.framebuffer orelse return VkError.InvalidHandleDrv;
const render_pass = renderer.render_pass orelse return VkError.InvalidHandleDrv;
const self: @This() = .{
.vertices = allocator.alloc(Vertex, vertex_count * instance_count) catch return VkError.OutOfDeviceMemory,
.renderer = renderer,
.viewport = undefined,
.scissor = undefined,
.color_attachments = framebuffer.interface.attachments[0..],
.depth_attachment = if (render_pass.interface.subpasses[renderer.subpass_index].depth_stencil_attachments) |desc| framebuffer.interface.attachments[desc.attachment] else null,
.render_pass = render_pass,
.framebuffer = framebuffer,
.rasterizer_wait_group = .init,
.stats = .{
.polygons_drawn = 0,
},
};
for (self.vertices) |*vertex| {
@@ -73,6 +97,17 @@ pub const DrawCall = struct {
return self;
}
fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
for (self.vertices) |*vertex| {
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
if (vertex.outputs[location]) |output| {
allocator.free(output.blob);
}
}
}
allocator.free(self.vertices);
}
};
device: *SoftDevice,
@@ -82,6 +117,8 @@ render_pass: ?*SoftRenderPass,
framebuffer: ?*SoftFramebuffer,
dynamic_state: DynamicState,
subpass_index: usize,
pub fn init(device: *SoftDevice, state: *PipelineState) Self {
return .{
.device = device,
@@ -93,13 +130,10 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self {
.scissor = null,
.line_width = null,
},
.subpass_index = 0,
};
}
pub fn deinit(self: *Self) void {
_ = self;
}
pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void {
var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB");
try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null);
@@ -119,24 +153,54 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi
const allocator = bounded_allocator.allocator();
var draw_call = try DrawCall.init(allocator, vertex_count, instance_count, self);
defer draw_call.deinit(allocator);
const timer = std.Io.Timestamp.now(io, .real);
defer if (comptime base.config.logs != .none) {
const duration = timer.untilNow(io, .real);
const ms = duration.toMicroseconds();
const ms: f32 = @floatFromInt(duration.toMicroseconds());
const memory_footprint = @divTrunc(bounded_allocator.queryFootprint(), 1000);
const peak_memory_footprint = @divTrunc(bounded_allocator.queryPeakFootprint(), 1000);
const fmt =
\\Drawcall stats:
\\> Took {d:.3}ms
\\> Total allocation of {d} KB
\\> Peak concurrent allocation of {d} KB
\\> Total polygons drawn {d}
;
const args = .{
ms / 1000,
memory_footprint,
peak_memory_footprint,
draw_call.stats.polygons_drawn,
};
const logger = std.log.scoped(.SoftwareRenderer);
if (memory_footprint > 256_000)
logger.warn("Drawcall stats:\n> Took {d}us\n> Allocated {d} KB", .{ ms, memory_footprint })
logger.warn(fmt, args)
else
logger.debug("Drawcall stats:\n> Took {d}us\n> Allocated {d} KB", .{ ms, memory_footprint });
logger.debug(fmt, args);
};
const pipeline = self.state.pipeline orelse return VkError.InvalidPipelineDrv;
const vertex_shader = pipeline.stages.getPtrAssertContains(.vertex);
for (vertex_shader.runtimes[0..]) |*runtime| {
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
}
const fragment_shader = pipeline.stages.getPtrAssertContains(.fragment);
for (fragment_shader.runtimes[0..]) |*runtime| {
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
}
self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices) catch |err| {
std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return VkError.Unknown;
};
@@ -183,7 +247,7 @@ fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize
const byte_offset = buffer.interface.offset + index_buffer.offset + (first_index * index_size);
const byte_size = index_count * index_size;
const index_memory: []const u8 = @as([*]const u8, @ptrCast(@alignCast(try buffer_memory.map(byte_offset, byte_size))))[0..byte_size];
const index_memory: []const u8 = try buffer_memory.map(byte_offset, byte_size);
const indices = allocator.alloc(i32, index_count) catch return VkError.OutOfDeviceMemory;
for (indices, 0..) |*index, i| {
File diff suppressed because it is too large Load Diff
+138 -109
View File
@@ -2,7 +2,6 @@ const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const zm = base.zm;
const lib = @import("../lib.zig");
const spv = @import("spv");
pub const F32x4 = zm.F32x4;
@@ -23,6 +22,11 @@ const ClipPlane = enum {
const MAX_CLIPPED_POLYGON_VERTICES = 16;
pub const ClippedLine = struct {
v0: Vertex,
v1: Vertex,
};
const ClippedPolygon = struct {
vertices: [MAX_CLIPPED_POLYGON_VERTICES]Vertex = undefined,
len: usize = 0,
@@ -36,114 +40,6 @@ const ClippedPolygon = struct {
}
};
fn clipDistance(position: F32x4, plane: ClipPlane) f32 {
const x = position[0];
const y = position[1];
const z = position[2];
const w = position[3];
return switch (plane) {
.Left => x + w,
.Right => w - x,
.Bottom => y + w,
.Top => w - y,
.Near => z,
.Far => w - z,
};
}
fn vertexInsidePlane(vertex: *const Vertex, plane: ClipPlane) bool {
return clipDistance(vertex.position, plane) >= 0.0;
}
fn copyBlob(allocator: std.mem.Allocator, blob: []const u8) VkError![]u8 {
const result = allocator.alloc(u8, blob.len) catch return VkError.OutOfDeviceMemory;
@memcpy(result, blob);
return result;
}
fn writePacked(comptime T: type, bytes: []u8, value: T) void {
const raw: [@sizeOf(T)]u8 = @bitCast(value);
@memcpy(bytes[0..@sizeOf(T)], raw[0..]);
}
fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t: f32) VkError![]u8 {
const len = @min(a.len, b.len);
const result = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory;
var byte_index: usize = 0;
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
const value_a = std.mem.bytesToValue(F32x4, a[byte_index..]);
const value_b = std.mem.bytesToValue(F32x4, b[byte_index..]);
writePacked(F32x4, result[byte_index..], value_a + ((value_b - value_a) * @as(F32x4, @splat(t))));
}
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
const value_a = std.mem.bytesToValue(f32, a[byte_index..]);
const value_b = std.mem.bytesToValue(f32, b[byte_index..]);
writePacked(f32, result[byte_index..], value_a + ((value_b - value_a) * t));
}
if (byte_index < len)
@memcpy(result[byte_index..], a[byte_index..len]);
return result;
}
fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, b: *const Vertex, t: f32) VkError!Vertex {
var result: Vertex = .{
.position = a.position + ((b.position - a.position) * @as(F32x4, @splat(t))),
.outputs = undefined,
};
@memset(result.outputs[0..], null);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const out_a = a.outputs[location] orelse continue;
const out_b = b.outputs[location] orelse continue;
result.outputs[location] = .{
.interpolation_type = out_a.interpolation_type,
.blob = if (out_a.interpolation_type == .flat)
try copyBlob(allocator, out_a.blob)
else
try interpolateBlob(allocator, out_a.blob, out_b.blob, t),
};
}
return result;
}
fn clipPolygonAgainstPlane(allocator: std.mem.Allocator, input: *const ClippedPolygon, plane: ClipPlane) VkError!ClippedPolygon {
var output: ClippedPolygon = .{};
if (input.len == 0)
return output;
var previous = input.vertices[input.len - 1];
var previous_inside = vertexInsidePlane(&previous, plane);
var previous_distance = clipDistance(previous.position, plane);
for (input.vertices[0..input.len]) |current| {
const current_inside = vertexInsidePlane(&current, plane);
const current_distance = clipDistance(current.position, plane);
if (current_inside != previous_inside) {
const t = previous_distance / (previous_distance - current_distance);
try output.append(try interpolateVertexForClipping(allocator, &previous, &current, t));
}
if (current_inside)
try output.append(current);
previous = current;
previous_inside = current_inside;
previous_distance = current_distance;
}
return output;
}
pub fn clipTriangle(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!ClippedPolygon {
var polygon: ClippedPolygon = .{};
try polygon.append(v0.*);
@@ -168,6 +64,46 @@ pub fn clipTriangle(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const
return polygon;
}
pub fn clipLine(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const Vertex) VkError!?ClippedLine {
var line: ClippedLine = .{
.v0 = v0.*,
.v1 = v1.*,
};
const planes = [_]ClipPlane{
.Left,
.Right,
.Bottom,
.Top,
.Near,
.Far,
};
for (planes) |plane| {
const v0_distance = clipDistance(line.v0.position, plane);
const v1_distance = clipDistance(line.v1.position, plane);
const v0_inside = v0_distance >= 0.0;
const v1_inside = v1_distance >= 0.0;
if (!v0_inside and !v1_inside)
return null;
if (v0_inside and v1_inside)
continue;
const t = v0_distance / (v0_distance - v1_distance);
const clipped_vertex = try interpolateVertexForClipping(allocator, &line.v0, &line.v1, t);
if (v0_inside) {
line.v1 = clipped_vertex;
} else {
line.v0 = clipped_vertex;
}
}
return line;
}
pub fn viewportTransformVertex(viewport: vk.Viewport, vertex: *Vertex) void {
const x, const y, const z, const w = vertex.position;
@@ -189,3 +125,96 @@ pub fn viewportTransformVertex(viewport: vk.Viewport, vertex: *Vertex) void {
vertex.position = zm.f32x4(x_screen, y_screen, z_screen, w);
}
fn clipDistance(position: F32x4, plane: ClipPlane) f32 {
const x, const y, const z, const w = position;
return switch (plane) {
.Left => x + w,
.Right => w - x,
.Bottom => y + w,
.Top => w - y,
.Near => z,
.Far => w - z,
};
}
fn isVertexInsidePlane(vertex: *const Vertex, plane: ClipPlane) bool {
return clipDistance(vertex.position, plane) >= 0.0;
}
fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t: f32) VkError![]u8 {
const len = @min(a.len, b.len);
const result = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory;
var byte_index: usize = 0;
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
const value_a = std.mem.bytesToValue(F32x4, a[byte_index..]);
const value_b = std.mem.bytesToValue(F32x4, b[byte_index..]);
base.utils.writePacked(F32x4, result[byte_index..], value_a + ((value_b - value_a) * zm.f32x4s(t)));
}
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
const value_a = std.mem.bytesToValue(f32, a[byte_index..]);
const value_b = std.mem.bytesToValue(f32, b[byte_index..]);
base.utils.writePacked(f32, result[byte_index..], value_a + ((value_b - value_a) * t));
}
if (byte_index < len)
@memcpy(result[byte_index..], a[byte_index..len]);
return result;
}
fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, b: *const Vertex, t: f32) VkError!Vertex {
var result: Vertex = .{
.position = a.position + ((b.position - a.position) * zm.f32x4s(t)),
.outputs = undefined,
};
@memset(result.outputs[0..], null);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const out_a = a.outputs[location] orelse continue;
const out_b = b.outputs[location] orelse continue;
result.outputs[location] = .{
.interpolation_type = out_a.interpolation_type,
.blob = if (out_a.interpolation_type == .flat)
allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory
else
try interpolateBlob(allocator, out_a.blob, out_b.blob, t),
};
}
return result;
}
fn clipPolygonAgainstPlane(allocator: std.mem.Allocator, input: *const ClippedPolygon, plane: ClipPlane) VkError!ClippedPolygon {
var output: ClippedPolygon = .{};
if (input.len == 0)
return output;
var previous = input.vertices[input.len - 1];
var previous_inside = isVertexInsidePlane(&previous, plane);
var previous_distance = clipDistance(previous.position, plane);
for (input.vertices[0..input.len]) |current| {
const current_inside = isVertexInsidePlane(&current, plane);
const current_distance = clipDistance(current.position, plane);
if (current_inside != previous_inside) {
const t = previous_distance / (previous_distance - current_distance);
try output.append(try interpolateVertexForClipping(allocator, &previous, &current, t));
}
if (current_inside)
try output.append(current);
previous = current;
previous_inside = current_inside;
previous_distance = current_distance;
}
return output;
}
+38 -12
View File
@@ -4,7 +4,7 @@ const base = @import("base");
const zm = base.zm;
const spv = @import("spv");
const lib = @import("../lib.zig");
const VertexInterpolation = @import("rasterizer/common.zig").VertexInterpolation;
const Renderer = @import("Renderer.zig");
const SoftImage = @import("../SoftImage.zig");
@@ -12,23 +12,38 @@ const SoftImage = @import("../SoftImage.zig");
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
pub fn shaderInvocation(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, batch_id: usize, position: zm.F32x4, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][]const u8) SpvRuntimeError!zm.F32x4 {
_ = position;
const pipeline = draw_call.renderer.state.pipeline orelse return zm.f32x4s(0.0);
pub fn shaderInvocation(
allocator: std.mem.Allocator,
draw_call: *Renderer.DrawCall,
batch_id: usize,
position: zm.F32x4,
inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation,
) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 {
const io = draw_call.renderer.device.interface.io();
const shader = pipeline.stages.getPtrAssertContains(.fragment);
const rt = &shader.runtimes[batch_id];
_ = position;
const pipeline = draw_call.renderer.state.pipeline orelse return undefined;
const shader = pipeline.stages.getPtr(.fragment) orelse return undefined;
const runtime = &shader.runtimes[batch_id];
const mutex = &runtime.mutex;
const rt = &runtime.rt;
mutex.lock(io) catch return SpvRuntimeError.Unknown;
defer mutex.unlock(io);
try rt.populatePushConstants(draw_call.renderer.state.push_constant_blob[0..]);
const entry = try rt.getEntryPointByName(shader.entry);
const output_result = try rt.getResultByLocation(0, .output);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const result_word = rt.getResultByLocation(@intCast(location), .input) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
try rt.writeInput(inputs[location], result_word);
allocator.free(inputs[location]);
try rt.writeInput(inputs[location].blob, result_word);
if (inputs[location].free_responsability)
allocator.free(inputs[location].blob);
}
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
@@ -39,7 +54,18 @@ pub fn shaderInvocation(allocator: std.mem.Allocator, draw_call: *Renderer.DrawC
else => return err,
};
var color = zm.f32x4s(0.0);
try rt.readOutput(std.mem.asBytes(&color), output_result);
return std.math.clamp(color, zm.f32x4s(0.0), zm.f32x4s(1.0));
var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 = undefined;
@memset(std.mem.asBytes(&outputs), 0);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
try rt.readOutput(&outputs[location], result_word);
}
try rt.flushDescriptorSets(allocator);
return outputs;
}
+192 -11
View File
@@ -1,21 +1,80 @@
const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const clip = @import("clip.zig");
const bresenham = @import("rasterizer/bresenham.zig");
const edge_function = @import("rasterizer/edge_function.zig");
const common = @import("rasterizer/common.zig");
const Renderer = @import("Renderer.zig");
const Vertex = Renderer.Vertex;
const DrawCall = Renderer.DrawCall;
const SoftImage = @import("../SoftImage.zig");
const VkError = base.VkError;
pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall) VkError!void {
const io = draw_call.renderer.device.interface.io();
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const topology = pipeline_data.input_assembly.topology;
const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse return VkError.InvalidAttachmentDrv;
const color_attachment_access = allocator.alloc(?common.RenderTargetAccess, color_attachments.len) catch return VkError.OutOfDeviceMemory;
@memset(color_attachment_access, null);
for (color_attachments, color_attachment_access) |attachment_ref, *access| {
if (attachment_ref.attachment == vk.ATTACHMENT_UNUSED)
continue;
const render_target_view: *base.ImageView = draw_call.color_attachments[attachment_ref.attachment];
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
const color_range = render_target_view.subresource_range;
const color_format = render_target_view.format;
const color_attachment_subresource_offset = try render_target.getSubresourceOffset(
color_range.aspect_mask,
color_range.base_mip_level,
color_range.base_array_layer,
);
const color_attachment_subresource_size = render_target.getLayerSize(color_range.aspect_mask);
access.* = .{
.mutex = undefined,
.base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size),
.row_pitch = render_target.getRowPitchMemSizeForMipLevelWithFormat(color_range.aspect_mask, color_range.base_mip_level, color_format),
.texel_size = base.format.texelSize(color_format),
.format = color_format,
};
}
const depth_attachment_view: ?*base.ImageView = if (draw_call.depth_attachment) |view| view else null;
const depth_attachment: ?*SoftImage = if (depth_attachment_view) |view| @alignCast(@fieldParentPtr("interface", view.image)) else null;
var depth_attachment_access: ?common.RenderTargetAccess = blk: {
if (depth_attachment == null)
break :blk null;
const depth_range = depth_attachment_view.?.subresource_range;
const depth_format = depth_attachment_view.?.format;
const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset(
depth_range.aspect_mask,
depth_range.base_mip_level,
depth_range.base_array_layer,
);
const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_range.aspect_mask);
break :blk .{
.mutex = .init,
.base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size),
.row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_range.aspect_mask, depth_range.base_mip_level, depth_format),
.texel_size = base.format.texelSize(depth_format),
.format = depth_format,
};
};
switch (topology) {
.triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| {
const first_vertex = triangle_index * 3;
@@ -23,7 +82,16 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
const v1 = &draw_call.vertices[first_vertex + 1];
const v2 = &draw_call.vertices[first_vertex + 2];
try clipTransformAndRasterizeTriangle(renderer, allocator, draw_call, v0, v1, v2);
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
},
.triangle_fan => if (draw_call.vertices.len >= 3) {
const v0 = &draw_call.vertices[0];
@@ -31,7 +99,16 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
const v1 = &draw_call.vertices[vertex_index];
const v2 = &draw_call.vertices[vertex_index + 1];
try clipTransformAndRasterizeTriangle(renderer, allocator, draw_call, v0, v1, v2);
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
}
},
.triangle_strip => if (draw_call.vertices.len >= 3) {
@@ -41,17 +118,101 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
const v2 = &draw_call.vertices[vertex_index + 2];
if ((vertex_index & 1) == 0) {
try clipTransformAndRasterizeTriangle(renderer, allocator, draw_call, v0, v1, v2);
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
} else {
try clipTransformAndRasterizeTriangle(renderer, allocator, draw_call, v1, v0, v2);
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v1,
v0,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
}
}
},
.line_list => for (0..@divTrunc(draw_call.vertices.len, 2)) |line_index| {
const first_vertex = line_index * 2;
const v0 = &draw_call.vertices[first_vertex + 0];
const v1 = &draw_call.vertices[first_vertex + 1];
try clipTransformAndRasterizeLine(
allocator,
draw_call,
v0,
v1,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
},
.line_strip => if (draw_call.vertices.len >= 2) {
for (0..(draw_call.vertices.len - 1)) |vertex_index| {
const v0 = &draw_call.vertices[vertex_index + 0];
const v1 = &draw_call.vertices[vertex_index + 1];
try clipTransformAndRasterizeLine(
allocator,
draw_call,
v0,
v1,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
}
},
else => base.unsupported("primitive topology {any}", .{topology}),
}
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
fn clipTransformAndRasterizeTriangle(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!void {
fn clipTransformAndRasterizeLine(
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const clipped_line = (try clip.clipLine(allocator, v0, v1)) orelse return;
var tv0 = clipped_line.v0;
var tv1 = clipped_line.v1;
clip.viewportTransformVertex(draw_call.viewport, &tv0);
clip.viewportTransformVertex(draw_call.viewport, &tv1);
try bresenham.drawLine(
allocator,
draw_call,
&tv0,
&tv1,
color_attachment_access,
depth_attachment_access,
);
}
fn clipTransformAndRasterizeTriangle(
renderer: *Renderer,
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
v2: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const clipped_polygon = try clip.clipTriangle(allocator, v0, v1, v2);
if (clipped_polygon.len < 3)
@@ -66,21 +227,41 @@ fn clipTransformAndRasterizeTriangle(renderer: *Renderer, allocator: std.mem.All
clip.viewportTransformVertex(draw_call.viewport, &tv1);
clip.viewportTransformVertex(draw_call.viewport, &tv2);
try rasterizeTriangle(renderer, allocator, draw_call, &tv0, &tv1, &tv2);
try rasterizeTriangle(
renderer,
allocator,
draw_call,
&tv0,
&tv1,
&tv2,
color_attachment_access,
depth_attachment_access,
);
}
}
fn rasterizeTriangle(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall, v0: *Vertex, v1: *Vertex, v2: *Vertex) VkError!void {
fn rasterizeTriangle(
renderer: *Renderer,
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
v2: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
if (try triangleIsCulled(renderer, v0, v1, v2))
return;
draw_call.stats.polygons_drawn += 1;
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
switch (pipeline_data.rasterization.polygon_mode) {
.fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2),
.fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, color_attachment_access, depth_attachment_access),
.line => {
try bresenham.drawLine(allocator, draw_call, v0, v1);
try bresenham.drawLine(allocator, draw_call, v1, v2);
try bresenham.drawLine(allocator, draw_call, v2, v0);
try bresenham.drawLine(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access);
try bresenham.drawLine(allocator, draw_call, v1, v2, color_attachment_access, depth_attachment_access);
try bresenham.drawLine(allocator, draw_call, v2, v0, color_attachment_access, depth_attachment_access);
},
.point => {}, // TODO
else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}),
+36 -24
View File
@@ -3,6 +3,7 @@ const base = @import("base");
const spv = @import("spv");
const zm = base.zm;
const blitter = @import("../blitter.zig");
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
@@ -27,9 +28,18 @@ const RunData = struct {
end_vertex: *Renderer.Vertex,
start_step: usize,
end_step: usize,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
};
pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: *Renderer.Vertex, v1: *Renderer.Vertex) VkError!void {
pub fn drawLine(
allocator: std.mem.Allocator,
draw_call: *Renderer.DrawCall,
v0: *Renderer.Vertex,
v1: *Renderer.Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const io = draw_call.renderer.device.interface.io();
var x0: i32 = @intFromFloat(v0.position[0]);
@@ -60,7 +70,6 @@ pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0:
const pipeline = draw_call.renderer.state.pipeline orelse return;
var wg: std.Io.Group = .init;
const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len;
if (runtimes_count == 0)
return;
@@ -93,14 +102,20 @@ pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0:
.end_vertex = end_vertex,
.start_step = start_step,
.end_step = end_step,
.color_attachment_access = color_attachment_access,
.depth_attachment_access = depth_attachment_access,
};
wg.async(io, runWrapper, .{run_data});
}
wg.await(io) catch return VkError.DeviceLost;
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
}
inline fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 {
// Not syncing workers between triangles when rendering without depth buffer
// will lead to pixel rendering order issues between triangles.
if (depth_attachment_access == null)
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 {
if (d_x == 0)
return y0;
@@ -112,16 +127,15 @@ inline fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usi
fn runWrapper(data: RunData) void {
@call(.always_inline, run, .{data}) catch |err| {
std.log.scoped(.@"Rasterization stage").err("line fill mode catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
};
}
inline fn run(data: RunData) !void {
const render_target_view: *base.ImageView = (data.draw_call.renderer.framebuffer orelse return).interface.attachments[0];
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
var step = data.start_step;
while (step <= data.end_step) : (step += 1) {
const x = data.x0 + @as(i32, @intCast(step));
@@ -137,7 +151,15 @@ inline fn run(data: RunData) !void {
const t = @as(f32, @floatFromInt(step)) / @as(f32, @floatFromInt(@max(data.d_x, 1)));
const z = ((1.0 - t) * data.start_vertex.position[2]) + (t * data.end_vertex.position[2]);
const pixel = fragment.shaderInvocation(
// Early depth test to avoid unnecesary computations
if (data.depth_attachment_access) |depth| {
const offset = @as(usize, @intCast(pixel_x)) * depth.texel_size + @as(usize, @intCast(pixel_y)) * depth.row_pitch;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
if (z >= depth_value[0])
continue;
}
const outputs = fragment.shaderInvocation(
data.allocator,
data.draw_call,
data.batch_id,
@@ -145,25 +167,15 @@ inline fn run(data: RunData) !void {
try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, t),
) catch |err| {
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return;
};
try render_target.writeFloat4(
.{
.x = pixel_x,
.y = pixel_y,
.z = 0, // FIXME
},
.{
.aspect_mask = render_target_view.subresource_range.aspect_mask,
.mip_level = render_target_view.subresource_range.base_mip_level,
.array_layer = render_target_view.subresource_range.base_array_layer,
},
render_target_view.format,
pixel,
);
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(pixel_x), @intCast(pixel_y), z);
}
}
+72 -16
View File
@@ -4,10 +4,25 @@ const base = @import("base");
const zm = base.zm;
const spv = @import("spv");
const blitter = @import("../blitter.zig");
const Renderer = @import("../Renderer.zig");
const VkError = base.VkError;
const F32x4 = zm.F32x4;
const U32x4 = @Vector(4, u32);
pub const RenderTargetAccess = struct {
mutex: std.Io.Mutex,
base: []u8,
row_pitch: usize,
texel_size: usize,
format: vk.Format,
};
pub const VertexInterpolation = struct {
blob: []const u8,
free_responsability: bool,
};
pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool {
const min_x: i64 = @as(i64, scissor.offset.x);
@@ -25,15 +40,6 @@ pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool {
pixel_y < max_y;
}
fn writePacked(comptime T: type, bytes: []u8, value: T) void {
const raw: [@sizeOf(T)]u8 = @bitCast(value);
@memcpy(bytes[0..@sizeOf(T)], raw[0..]);
}
fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 {
return (value0 * @as(F32x4, @splat(b0))) + (value1 * @as(F32x4, @splat(b1))) + (value2 * @as(F32x4, @splat(b2)));
}
pub fn interpolateVertexOutputs(
allocator: std.mem.Allocator,
v0: *const Renderer.Vertex,
@@ -42,8 +48,8 @@ pub fn interpolateVertexOutputs(
b0: f32,
b1: f32,
b2: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 {
var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 = undefined;
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation {
var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation = undefined;
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const out0 = v0.outputs[location] orelse continue;
@@ -51,7 +57,7 @@ pub fn interpolateVertexOutputs(
const out2 = v2.outputs[location] orelse continue;
if (out0.interpolation_type == .flat or out0.blob.len == 0) {
inputs[location] = out0.blob;
inputs[location] = .{ .blob = out0.blob, .free_responsability = false };
continue;
}
@@ -63,25 +69,75 @@ pub fn interpolateVertexOutputs(
const value0 = std.mem.bytesToValue(F32x4, out0.blob[byte_index..]);
const value1 = std.mem.bytesToValue(F32x4, out1.blob[byte_index..]);
const value2 = std.mem.bytesToValue(F32x4, out2.blob[byte_index..]);
writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, b0, b1, b2));
base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, b0, b1, b2));
}
while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) {
const value0 = std.mem.bytesToValue(f32, out0.blob[byte_index..]);
const value1 = std.mem.bytesToValue(f32, out1.blob[byte_index..]);
const value2 = std.mem.bytesToValue(f32, out2.blob[byte_index..]);
writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2));
base.utils.writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2));
}
if (byte_index < len)
@memcpy(input[byte_index..], out0.blob[byte_index..len]);
inputs[location] = input;
inputs[location] = .{ .blob = input, .free_responsability = true };
}
return inputs;
}
pub fn interpolateLineOutputs(allocator: std.mem.Allocator, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, t: f32) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 {
pub fn interpolateLineOutputs(
allocator: std.mem.Allocator,
v0: *const Renderer.Vertex,
v1: *const Renderer.Vertex,
t: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation {
return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0);
}
inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 {
return (value0 * zm.f32x4s(b0)) + (value1 * zm.f32x4s(b1)) + (value2 * zm.f32x4s(b2));
}
inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 {
const color = std.mem.bytesToValue(F32x4, &output);
return if (base.format.isSrgb(format)) zm.rgbToSrgb(color) else color;
}
pub fn writeToTargets(
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8,
draw_call: *Renderer.DrawCall,
color_attachment_access: []const ?RenderTargetAccess,
depth_attachment_access: ?*RenderTargetAccess,
x: usize,
y: usize,
z: f32,
) VkError!void {
const io = draw_call.renderer.device.interface.io();
// After work depth test to avoid overwritten depth pixels during fragment invocations
if (depth_attachment_access) |depth| {
const depth_offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch;
depth.mutex.lock(io) catch return VkError.DeviceLost;
defer depth.mutex.unlock(io);
const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format);
if (z >= depth_value[0])
return;
blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format);
}
for (color_attachment_access, 0..) |maybe_color, location| {
const color = maybe_color orelse continue;
const color_offset = @as(usize, @intCast(x)) * color.texel_size + @as(usize, @intCast(y)) * color.row_pitch;
if (base.format.isUnnormalizedInteger(color.format)) {
blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format);
} else {
blitter.writeFloat4(fragmentOutputFloat4(outputs[location], color.format), color.base[color_offset..], color.format);
}
}
}
+42 -30
View File
@@ -6,9 +6,9 @@ const zm = base.zm;
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
const blitter = @import("../blitter.zig");
const Renderer = @import("../Renderer.zig");
const SoftImage = @import("../../SoftImage.zig");
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
@@ -23,12 +23,22 @@ const RunData = struct {
min_y: i32,
max_y: i32,
area: f32,
v0: Renderer.Vertex,
v1: Renderer.Vertex,
v2: Renderer.Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
};
pub fn drawTriangle(
allocator: std.mem.Allocator,
draw_call: *Renderer.DrawCall,
v0: *Renderer.Vertex,
v1: *Renderer.Vertex,
v2: *Renderer.Vertex,
};
pub fn drawTriangle(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: *Renderer.Vertex, v1: *Renderer.Vertex, v2: *Renderer.Vertex) VkError!void {
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const io = draw_call.renderer.device.interface.io();
const min_x: i32 = @intFromFloat(@floor(@min(v0.position[0], v1.position[0], v2.position[0])));
@@ -42,9 +52,8 @@ pub fn drawTriangle(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall,
const pipeline = draw_call.renderer.state.pipeline orelse return;
var wg: std.Io.Group = .init;
const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len;
const grid_size: usize = @intFromFloat(@floor(@sqrt(@as(f32, @floatFromInt(runtimes_count)))));
const grid_size: usize = @intFromFloat(@ceil(@sqrt(@as(f32, @floatFromInt(runtimes_count)))));
const width: usize = @intCast(max_x - min_x + 1);
const height: usize = @intCast(max_y - min_y + 1);
@@ -53,6 +62,7 @@ pub fn drawTriangle(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall,
const rows_per_run = @divTrunc(height + grid_size - 1, grid_size);
var batch_id: usize = 0;
for (0..grid_size) |gy| {
for (0..grid_size) |gx| {
defer batch_id = @mod(batch_id + 1, runtimes_count);
@@ -77,20 +87,26 @@ pub fn drawTriangle(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall,
.allocator = allocator,
.draw_call = draw_call,
.batch_id = batch_id,
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v0 = v0.*,
.v1 = v1.*,
.v2 = v2.*,
.area = area,
.min_x = run_min_x,
.max_x = run_max_x,
.min_y = run_min_y,
.max_y = run_max_y,
.color_attachment_access = color_attachment_access,
.depth_attachment_access = depth_attachment_access,
};
wg.async(io, runWrapper, .{run_data});
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
}
}
wg.await(io) catch return VkError.DeviceLost;
// Not syncing workers between triangles when rendering without depth buffer
// will lead to pixel rendering order issues between triangles.
if (depth_attachment_access == null)
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 {
@@ -100,16 +116,15 @@ inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 {
fn runWrapper(data: RunData) void {
@call(.always_inline, run, .{data}) catch |err| {
std.log.scoped(.@"Rasterization stage").err("triangle fill mode catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
};
}
inline fn run(data: RunData) !void {
const render_target_view: *base.ImageView = (data.draw_call.renderer.framebuffer orelse return).interface.attachments[0];
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
var y = data.min_y;
while (y <= data.max_y) : (y += 1) {
var x = data.min_x;
@@ -137,34 +152,31 @@ inline fn run(data: RunData) !void {
const b2 = w2 / data.area;
const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]);
const pixel = fragment.shaderInvocation(
// Early depth test to avoid unnecesary computations
if (data.depth_attachment_access) |depth| {
const offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
if (z >= depth_value[0])
continue;
}
const outputs = fragment.shaderInvocation(
data.allocator,
data.draw_call,
data.batch_id,
zm.f32x4(@floatFromInt(x), @floatFromInt(y), z, 1.0),
try common.interpolateVertexOutputs(data.allocator, data.v0, data.v1, data.v2, b0, b1, b2),
try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2),
) catch |err| {
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return;
};
try render_target.writeFloat4(
.{
.x = x,
.y = y,
.z = 0, // FIXME
},
.{
.aspect_mask = render_target_view.subresource_range.aspect_mask,
.mip_level = render_target_view.subresource_range.base_mip_level,
.array_layer = render_target_view.subresource_range.base_array_layer,
},
render_target_view.format,
pixel,
);
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(x), @intCast(y), z);
}
}
}
+6 -15
View File
@@ -27,15 +27,17 @@ pub const RunData = struct {
pub fn runWrapper(data: RunData) void {
@call(.always_inline, run, .{data}) catch |err| {
std.log.scoped(.@"SPIR-V runtime").err("SPIR-V runtime catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
};
}
inline fn run(data: RunData) !void {
const shader = data.pipeline.stages.getPtrAssertContains(.vertex);
const rt = &shader.runtimes[data.batch_id];
const rt = &shader.runtimes[data.batch_id].rt;
try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]);
const entry = try rt.getEntryPointByName(shader.entry);
@@ -62,7 +64,7 @@ inline fn run(data: RunData) !void {
const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
const offset = buffer.interface.offset + vertex_buffer.offset + (binding_info.stride * vertex_index) + attribute.offset;
const buffer_memory_map: []u8 = @as([*]u8, @ptrCast(@alignCast(try buffer_memory.map(offset, buffer_memory_size))))[0..buffer_memory_size];
const buffer_memory_map: []u8 = try buffer_memory.map(offset, buffer_memory_size);
try rt.writeInput(buffer_memory_map, location_result);
}
@@ -79,19 +81,6 @@ inline fn run(data: RunData) !void {
const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index];
try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position);
if (invocation_index == 0) {
const io = data.draw_call.renderer.device.interface.io();
const file = try std.Io.Dir.cwd().createFile(
io,
"vertex_result_table_dump.txt",
.{ .truncate = true },
);
defer file.close(io);
var buffer = [_]u8{0} ** 1024;
var writer = file.writer(io, buffer[0..]);
try rt.dumpResultsTable(data.allocator, &writer.interface);
}
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
@@ -103,6 +92,8 @@ inline fn run(data: RunData) !void {
};
try rt.readOutput(output.outputs[location].?.blob, result_word);
}
try rt.flushDescriptorSets(data.allocator);
}
}
+1
View File
@@ -68,6 +68,7 @@ pub const MAX_IMAGE_LEVELS_3D = 12;
pub const MAX_IMAGE_LEVELS_CUBE = 15;
pub const MAX_IMAGE_ARRAY_LAYERS = 2048;
pub const PHYSICAL_DEVICE_DEFAULT_NAME = "Ape software device";
pub const PHYSICAL_DEVICE_FALLBACK_HEAP_SIZE = 0x10000000; // 256MB
pub const std_options = base.std_options;
+10 -3
View File
@@ -1,16 +1,20 @@
const std = @import("std");
const vk = @import("vulkan");
const NonDispatchable = @import("NonDispatchable.zig");
const VkError = @import("error_set.zig").VkError;
const NonDispatchable = @import("NonDispatchable.zig").NonDispatchable;
const Device = @import("Device.zig");
const Buffer = @import("Buffer.zig");
const Self = @This();
pub const ObjectType: vk.ObjectType = .buffer_view;
owner: *Device,
buffer: *Buffer,
format: vk.Format,
offset: vk.DeviceSize,
range: vk.DeviceSize,
vtable: *const VTable,
@@ -20,10 +24,13 @@ pub const VTable = struct {
pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.BufferViewCreateInfo) VkError!Self {
_ = allocator;
_ = info;
return .{
.owner = device,
.buffer = try NonDispatchable(Buffer).fromHandleObject(info.buffer),
.vtable = undefined,
.format = info.format,
.offset = info.offset,
.range = info.range,
};
}
+19
View File
@@ -47,6 +47,7 @@ pub const DispatchTable = struct {
blitImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, []const vk.ImageBlit, vk.Filter) VkError!void,
clearAttachment: *const fn (*Self, vk.ClearAttachment, vk.ClearRect) VkError!void,
clearColorImage: *const fn (*Self, *Image, vk.ImageLayout, *const vk.ClearColorValue, vk.ImageSubresourceRange) VkError!void,
clearDepthStencilImage: *const fn (*Self, *Image, vk.ImageLayout, *const vk.ClearDepthStencilValue, vk.ImageSubresourceRange) VkError!void,
copyBuffer: *const fn (*Self, *Buffer, *Buffer, []const vk.BufferCopy) VkError!void,
copyBufferToImage: *const fn (*Self, *Buffer, *Image, vk.ImageLayout, []const vk.BufferImageCopy) VkError!void,
copyImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, []const vk.ImageCopy) VkError!void,
@@ -61,10 +62,12 @@ pub const DispatchTable = struct {
endRenderPass: *const fn (*Self) VkError!void,
executeCommands: *const fn (*Self, *Self) VkError!void,
fillBuffer: *const fn (*Self, *Buffer, vk.DeviceSize, vk.DeviceSize, u32) VkError!void,
nextSubpass: *const fn (*Self, vk.SubpassContents) VkError!void,
pipelineBarrier: *const fn (*Self, vk.PipelineStageFlags, vk.PipelineStageFlags, vk.DependencyFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void,
pushConstants: *const fn (*Self, vk.ShaderStageFlags, u32, []const u8) VkError!void,
reset: *const fn (*Self, vk.CommandBufferResetFlags) VkError!void,
resetEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void,
resolveImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, vk.ImageResolve) VkError!void,
setEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void,
setScissor: *const fn (*Self, u32, []const vk.Rect2D) VkError!void,
setViewport: *const fn (*Self, u32, []const vk.Viewport) VkError!void,
@@ -191,6 +194,12 @@ pub fn clearColorImage(self: *Self, image: *Image, layout: vk.ImageLayout, color
}
}
pub fn clearDepthStencilImage(self: *Self, image: *Image, layout: vk.ImageLayout, value: *const vk.ClearDepthStencilValue, ranges: []const vk.ImageSubresourceRange) VkError!void {
for (ranges) |range| {
try self.dispatch_table.clearDepthStencilImage(self, image, layout, value, range);
}
}
pub inline fn copyBuffer(self: *Self, src: *Buffer, dst: *Buffer, regions: []const vk.BufferCopy) VkError!void {
try self.dispatch_table.copyBuffer(self, src, dst, regions);
}
@@ -243,6 +252,10 @@ pub inline fn fillBuffer(self: *Self, buffer: *Buffer, offset: vk.DeviceSize, si
try self.dispatch_table.fillBuffer(self, buffer, offset, size, data);
}
pub inline fn nextSubpass(self: *Self, contents: vk.SubpassContents) VkError!void {
try self.dispatch_table.nextSubpass(self, contents);
}
pub inline fn pipelineBarrier(
self: *Self,
src_stage: vk.PipelineStageFlags,
@@ -263,6 +276,12 @@ pub inline fn resetEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlag
try self.dispatch_table.resetEvent(self, event, stage);
}
pub inline fn resolveImage(self: *Self, src: *Image, src_layout: vk.ImageLayout, dst: *Image, dst_layout: vk.ImageLayout, regions: []const vk.ImageResolve) VkError!void {
for (regions[0..]) |region| {
try self.dispatch_table.resolveImage(self, src, src_layout, dst, dst_layout, region);
}
}
pub inline fn setEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlags) VkError!void {
try self.dispatch_table.setEvent(self, event, stage);
}
+3 -3
View File
@@ -18,7 +18,7 @@ layout: *DescriptorSetLayout,
vtable: *const VTable,
pub const VTable = struct {
copy: *const fn (*Self, vk.CopyDescriptorSet) VkError!void,
copy: *const fn (*Self, *const Self, vk.CopyDescriptorSet) VkError!void,
destroy: *const fn (*Self, std.mem.Allocator) void,
write: *const fn (*Self, vk.WriteDescriptorSet) VkError!void,
};
@@ -33,8 +33,8 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, layout: *DescriptorSe
};
}
pub inline fn copy(self: *Self, copy_data: vk.CopyDescriptorSet) VkError!void {
try self.vtable.copy(self, copy_data);
pub inline fn copy(self: *Self, src: *const Self, copy_data: vk.CopyDescriptorSet) VkError!void {
try self.vtable.copy(self, src, copy_data);
}
pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void {
-13
View File
@@ -34,19 +34,6 @@ dynamic_descriptor_count: usize,
/// Shader stages affected by this descriptor set
stages: vk.ShaderStageFlags,
/// Mesa's common Vulkan runtime states:
///
/// It's often necessary to store a pointer to the descriptor set layout in
/// the descriptor so that any entrypoint which has access to a descriptor
/// set also has the layout. While layouts are often passed into various
/// entrypoints, they're notably missing from vkUpdateDescriptorSets(). In
/// order to implement descriptor writes, you either need to stash a pointer
/// to the descriptor set layout in the descriptor set or you need to copy
/// all of the relevant information. Storing a pointer is a lot cheaper.
///
/// Because descriptor set layout lifetimes and descriptor set lifetimes are
/// not guaranteed to coincide, we have to reference count if we're going to
/// do this.
ref_count: std.atomic.Value(usize),
vtable: *const VTable,
+2 -2
View File
@@ -18,7 +18,7 @@ pub const VTable = struct {
destroy: *const fn (*Self, std.mem.Allocator) void,
flushRange: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!void,
invalidateRange: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!void,
map: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!?*anyopaque,
map: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError![]u8,
unmap: *const fn (*Self) void,
};
@@ -44,7 +44,7 @@ pub inline fn invalidateRange(self: *Self, offset: vk.DeviceSize, size: vk.Devic
try self.vtable.invalidateRange(self, offset, size);
}
pub inline fn map(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque {
pub inline fn map(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError![]u8 {
return self.vtable.map(self, offset, size);
}
-2
View File
@@ -6,8 +6,6 @@ const VkError = @import("error_set.zig").VkError;
const NonDispatchable = @import("NonDispatchable.zig").NonDispatchable;
const Device = @import("Device.zig");
const DeviceMemory = @import("DeviceMemory.zig");
const Image = @import("Image.zig");
const Self = @This();
+49
View File
@@ -10,8 +10,19 @@ const Device = @import("Device.zig");
const Self = @This();
pub const ObjectType: vk.ObjectType = .render_pass;
const SubpassDescription = struct {
flags: vk.SubpassDescriptionFlags,
pipeline_bind_point: vk.PipelineBindPoint,
input_attachments: ?[]const vk.AttachmentReference,
color_attachments: ?[]const vk.AttachmentReference,
resolve_attachments: ?[]const vk.AttachmentReference,
depth_stencil_attachments: ?vk.AttachmentReference,
preserve_attachments: ?[]const u32,
};
owner: *Device,
attachments: []vk.AttachmentDescription,
subpasses: []SubpassDescription,
vtable: *const VTable,
@@ -33,14 +44,52 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Rende
return VkError.ValidationFailed;
}
const subpasses = allocator.alloc(SubpassDescription, info.subpass_count) catch return VkError.OutOfHostMemory;
errdefer allocator.free(subpasses);
for (subpasses[0..], info.p_subpasses[0..]) |*subpass, subpass_info| {
subpass.* = .{
.flags = subpass_info.flags,
.pipeline_bind_point = subpass_info.pipeline_bind_point,
.input_attachments = if (subpass_info.p_input_attachments) |subpass_attachments|
allocator.dupe(vk.AttachmentReference, subpass_attachments[0..subpass_info.input_attachment_count]) catch return VkError.OutOfHostMemory
else
null,
.color_attachments = if (subpass_info.p_color_attachments) |subpass_attachments|
allocator.dupe(vk.AttachmentReference, subpass_attachments[0..subpass_info.color_attachment_count]) catch return VkError.OutOfHostMemory
else
null,
.resolve_attachments = if (subpass_info.p_resolve_attachments) |subpass_attachments|
allocator.dupe(vk.AttachmentReference, subpass_attachments[0..subpass_info.color_attachment_count]) catch return VkError.OutOfHostMemory
else
null,
.depth_stencil_attachments = if (subpass_info.p_depth_stencil_attachment) |subpass_attachment|
if (subpass_attachment.attachment != vk.ATTACHMENT_UNUSED) subpass_attachment.* else null
else
null,
.preserve_attachments = if (subpass_info.p_preserve_attachments) |subpass_attachments|
allocator.dupe(u32, subpass_attachments[0..subpass_info.preserve_attachment_count]) catch return VkError.OutOfHostMemory
else
null,
};
}
return .{
.owner = device,
.attachments = attachments,
.subpasses = subpasses,
.vtable = undefined,
};
}
pub fn destroy(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(self.attachments);
for (self.subpasses[0..]) |subpass| {
if (subpass.input_attachments) |attachments| allocator.free(attachments);
if (subpass.color_attachments) |attachments| allocator.free(attachments);
if (subpass.resolve_attachments) |attachments| allocator.free(attachments);
if (subpass.preserve_attachments) |attachments| allocator.free(attachments);
}
allocator.free(self.subpasses);
self.vtable.destroy(self, allocator);
}
+10 -1
View File
@@ -11,6 +11,11 @@ const Self = @This();
pub const ObjectType: vk.ObjectType = .sampler;
owner: *Device,
mag_filter: vk.Filter,
min_filter: vk.Filter,
address_mode_u: vk.SamplerAddressMode,
address_mode_v: vk.SamplerAddressMode,
address_mode_w: vk.SamplerAddressMode,
vtable: *const VTable,
@@ -20,9 +25,13 @@ pub const VTable = struct {
pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.SamplerCreateInfo) VkError!Self {
_ = allocator;
_ = info;
return .{
.owner = device,
.mag_filter = info.mag_filter,
.min_filter = info.min_filter,
.address_mode_u = info.address_mode_u,
.address_mode_v = info.address_mode_v,
.address_mode_w = info.address_mode_w,
.vtable = undefined,
};
}
+1
View File
@@ -55,6 +55,7 @@ pub const VkError = error{
InvalidHandleDrv,
InvalidPipelineDrv,
InvalidDeviceMemoryDrv,
InvalidAttachmentDrv,
};
pub inline fn errorLogger(err: VkError) void {
+149
View File
@@ -423,3 +423,152 @@ pub fn isUnsignedComponent(format: vk.Format, component: usize) bool {
pub inline fn isUnsigned(format: vk.Format) bool {
return isUnsignedComponent(format, 0);
}
pub inline fn isUnnormalizedInteger(format: vk.Format) bool {
return isSint(format) or isUint(format);
}
pub inline fn isSscaled(format: vk.Format) bool {
return lib.c.vkuFormatIsSSCALED(@intCast(@intFromEnum(format)));
}
pub inline fn isUscaled(format: vk.Format) bool {
return lib.c.vkuFormatIsUSCALED(@intCast(@intFromEnum(format)));
}
fn maxComponentBits(format: vk.Format) u32 {
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 64)) return 64;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 32)) return 32;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 24)) return 24;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 16)) return 16;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 11)) return 11;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 10)) return 10;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 8)) return 8;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 6)) return 6;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 5)) return 5;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 4)) return 4;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 2)) return 2;
if (lib.c.vkuFormatHasComponentSize(@intCast(@intFromEnum(format)), 1)) return 1;
lib.unsupported("format component bits {any}", .{format});
return 0;
}
fn maxUnsignedValue(bits: u32) f32 {
return switch (bits) {
1 => 1.0,
2 => 3.0,
4 => 15.0,
5 => 31.0,
6 => 63.0,
8 => @as(f32, @floatFromInt(std.math.maxInt(u8))),
10 => 1023.0,
11 => 2047.0,
16 => @as(f32, @floatFromInt(std.math.maxInt(u16))),
24 => 0xffffff,
32 => @as(f32, @floatFromInt(std.math.maxInt(u32))),
64 => @as(f32, @floatFromInt(std.math.maxInt(u64))),
else => blk: {
lib.unsupported("format component bits {d}", .{bits});
break :blk 1.0;
},
};
}
fn maxSignedValue(bits: u32) f32 {
return switch (bits) {
2 => 1.0,
4 => 7.0,
5 => 15.0,
6 => 31.0,
8 => @as(f32, @floatFromInt(std.math.maxInt(i8))),
10 => 511.0,
11 => 1023.0,
16 => @as(f32, @floatFromInt(std.math.maxInt(i16))),
24 => 0x7fffff,
32 => @as(f32, @floatFromInt(std.math.maxInt(i32))),
64 => @as(f32, @floatFromInt(std.math.maxInt(i64))),
else => blk: {
lib.unsupported("format component bits {d}", .{bits});
break :blk 1.0;
},
};
}
fn minSignedValue(bits: u32) f32 {
return switch (bits) {
2 => -2.0,
4 => -8.0,
5 => -16.0,
6 => -32.0,
8 => @as(f32, @floatFromInt(std.math.minInt(i8))),
10 => -512.0,
11 => -1024.0,
16 => @as(f32, @floatFromInt(std.math.minInt(i16))),
24 => -0x800000,
32 => @as(f32, @floatFromInt(std.math.minInt(i32))),
64 => @as(f32, @floatFromInt(std.math.minInt(i64))),
else => blk: {
lib.unsupported("format component bits {d}", .{bits});
break :blk -1.0;
},
};
}
fn maxFloatValue(format: vk.Format) f32 {
return switch (format) {
.r16_sfloat,
.r16g16_sfloat,
.r16g16b16_sfloat,
.r16g16b16a16_sfloat,
.b10g11r11_ufloat_pack32,
.e5b9g9r9_ufloat_pack32,
.bc6h_ufloat_block,
.bc6h_sfloat_block,
=> std.math.floatMax(f16),
else => std.math.floatMax(f32),
};
}
pub fn maxElementValue(format: vk.Format) f32 {
if (isDepth(format))
return 1.0;
if (isStencil(format))
return maxUnsignedValue(8);
if (isSnorm(format) or isUnorm(format) or isSrgb(format))
return 1.0;
if (isUscaled(format) or isUint(format))
return maxUnsignedValue(maxComponentBits(format));
if (isSscaled(format) or isSint(format))
return maxSignedValue(maxComponentBits(format));
if (isUfloat(format) or isSfloat(format))
return maxFloatValue(format);
lib.unsupported("format max element value {any}", .{format});
return 1.0;
}
pub fn minElementValue(format: vk.Format) f32 {
if (isDepth(format) or isStencil(format))
return 0.0;
if (isSnorm(format))
return -1.0;
if (isUnorm(format) or isSrgb(format) or isUscaled(format) or isUint(format) or isUfloat(format))
return 0.0;
if (isSscaled(format) or isSint(format))
return minSignedValue(maxComponentBits(format));
if (isSfloat(format))
return -maxFloatValue(format);
lib.unsupported("format min element value {any}", .{format});
return 0.0;
}
+2 -4
View File
@@ -12,6 +12,7 @@ pub const lib_vulkan = @import("lib_vulkan.zig");
pub const logger = @import("logger.zig");
pub const format = @import("format.zig");
pub const config = @import("config");
pub const utils = @import("utils.zig");
pub const Dispatchable = @import("Dispatchable.zig").Dispatchable;
pub const fallback_host_allocator = @import("fallback_host_allocator.zig").fallback_host_allocator;
@@ -53,11 +54,8 @@ pub const SwapchainKHR = @import("wsi/SwapchainKHR.zig");
pub const VULKAN_VENDOR_ID = @typeInfo(vk.VendorId).@"enum".fields[@typeInfo(vk.VendorId).@"enum".fields.len - 1].value + 1;
pub const DRIVER_DEBUG_ALLOCATOR_ENV_NAME = "STROLL_DEBUG_ALLOCATOR";
pub const DRIVER_LOGS_ENV_NAME = "STROLL_LOGS_LEVEL";
/// Default driver name
pub const DRIVER_NAME = "Unnamed Stroll Driver";
pub const DRIVER_NAME = "Unnamed Ape Driver";
/// Default Vulkan version
pub const VULKAN_VERSION = vk.makeApiVersion(0, 1, 0, 0);
+172 -197
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -87,7 +87,7 @@ pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), compti
};
term.setColor(.magenta) catch {};
writer.writeAll("[StrollDriver") catch continue;
writer.writeAll("[ApeDriver") catch continue;
if (!builtin.is_test) {
term.setColor(.cyan) catch {};
writer.writeAll(" " ++ root.DRIVER_NAME ++ " ") catch continue;
+4
View File
@@ -0,0 +1,4 @@
pub fn writePacked(comptime T: type, bytes: []u8, value: T) void {
const raw: [@sizeOf(T)]u8 = @bitCast(value);
@memcpy(bytes[0..@sizeOf(T)], raw[0..]);
}
+1 -1
View File
@@ -156,7 +156,7 @@ pub fn presentImage(interface: *Interface, allocator: std.mem.Allocator, image:
}
fn createShmFile(size: usize) VkError!std.posix.fd_t {
const name = "stroll_vk_wayland_surface";
const name = "ape_vk_wayland_surface";
const fd = std.posix.memfd_create(name, std.posix.FD_CLOEXEC) catch return VkError.Unknown;
errdefer std.c.close(fd);
+10 -13
View File
@@ -65,10 +65,7 @@ pub fn load() VkError!void {
if (ref_count.load(.monotonic) != 0)
return;
module = std.DynLib.open("libwayland-client.so.0") catch {
_ = ref_count.fetchSub(1, .monotonic);
return VkError.Unknown;
};
module = std.DynLib.open("libwayland-client.so.0") catch return VkError.Unknown;
errdefer module.close();
errdefer std.log.scoped(.WaylandClient).err("Could not open 'libwayland-client.so.0': {s}", .{std.c.dlerror() orelse "unknown error"});
@@ -94,7 +91,7 @@ pub fn unload() void {
}
}
pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) callconv(.c) ?*wl_proxy {
pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_interface, version: u32) ?*wl_proxy {
return wl_proxy_marshal_flags(
@ptrCast(@alignCast(registry)),
WL_REGISTRY_BIND,
@@ -108,7 +105,7 @@ pub fn wl_registry_bind(registry: *wl_registry, name: u32, interface: *const wl_
);
}
pub fn wl_display_get_registry(display: *wl_display) callconv(.c) ?*wl_registry {
pub fn wl_display_get_registry(display: *wl_display) ?*wl_registry {
return @ptrCast(@alignCast(wl_proxy_marshal_flags(
@ptrCast(@alignCast(display)),
WL_DISPLAY_GET_REGISTRY,
@@ -119,7 +116,7 @@ pub fn wl_display_get_registry(display: *wl_display) callconv(.c) ?*wl_registry
)));
}
pub fn wl_registry_add_listener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) callconv(.c) c_int {
pub fn wl_registry_add_listener(registry: *wl_registry, listener: *const wl_registry_listener, data: ?*anyopaque) c_int {
return wl_proxy_add_listener(@ptrCast(@alignCast(registry)), @ptrCast(@alignCast(@constCast(listener))), data);
}
@@ -136,7 +133,7 @@ pub fn wl_shm_create_pool(shm: *wl_shm, fd: i32, size: i32) callconv(.c) ?*wl_sh
)));
}
pub fn wl_shm_pool_destroy(shm_pool: *wl_shm_pool) callconv(.c) void {
pub fn wl_shm_pool_destroy(shm_pool: *wl_shm_pool) void {
_ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(shm_pool)),
WL_SHM_POOL_DESTROY,
@@ -146,7 +143,7 @@ pub fn wl_shm_pool_destroy(shm_pool: *wl_shm_pool) callconv(.c) void {
);
}
pub fn wl_shm_pool_create_buffer(shm_pool: *wl_shm_pool, offset: i32, width: i32, height: i32, stride: i32, format: u32) callconv(.c) ?*wl_buffer {
pub fn wl_shm_pool_create_buffer(shm_pool: *wl_shm_pool, offset: i32, width: i32, height: i32, stride: i32, format: u32) ?*wl_buffer {
return @ptrCast(@alignCast(wl_proxy_marshal_flags(
@ptrCast(@alignCast(shm_pool)),
WL_SHM_POOL_CREATE_BUFFER,
@@ -162,7 +159,7 @@ pub fn wl_shm_pool_create_buffer(shm_pool: *wl_shm_pool, offset: i32, width: i32
)));
}
pub fn wl_buffer_destroy(buffer: *wl_buffer) callconv(.c) void {
pub fn wl_buffer_destroy(buffer: *wl_buffer) void {
_ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(buffer)),
WL_BUFFER_DESTROY,
@@ -172,7 +169,7 @@ pub fn wl_buffer_destroy(buffer: *wl_buffer) callconv(.c) void {
);
}
pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) callconv(.c) void {
pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i32) void {
_ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)),
WL_SURFACE_ATTACH,
@@ -185,7 +182,7 @@ pub fn wl_surface_attach(surface: *wl_surface, buffer: *wl_buffer, x: i32, y: i3
);
}
pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) callconv(.c) void {
pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, height: i32) void {
_ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)),
WL_SURFACE_DAMAGE,
@@ -199,7 +196,7 @@ pub fn wl_surface_damage(surface: *wl_surface, x: i32, y: i32, width: i32, heigh
);
}
pub fn wl_surface_commit(surface: *wl_surface) callconv(.c) void {
pub fn wl_surface_commit(surface: *wl_surface) void {
_ = wl_proxy_marshal_flags(
@ptrCast(@alignCast(surface)),
WL_SURFACE_COMMIT,