Skip to content

Commit b20f711

Browse files
dpwizclaude
andcommitted
Embed reflect-example and fixture shaders with the GLSL quasiquoters
The reflect examples and the vulkan-utils-spirv test suite consumed committed .spv files produced by out-of-band build-shaders.sh scripts, which broke CI on a fresh checkout (the .spv were gitignored, and two fixtures had no GLSL source at all). Shaders are now compiled at build time by the vulkan-utils GLSL quasiquoters and reflected from the embedded bytes; the scripts, shader files and file-embed dependency are gone. vulkan-utils-spirv gains the missing Bytes variants for the stage TH: reflectStageSigBytes and reflectPipelineLayoutSigBytes, mirroring reflectShaderTypesBytes. Each example is restructured around per-pipeline modules whose <Pipeline>.Shader submodule holds the quasiquoted SPIR-V (satisfying the TH stage restriction) and whose pipeline module reflects it and builds the pipeline; Main does the setup and Render the headless frame: * compute-reflect: Main, Render, Julia(.Shader) * mesh-reflect: Main, Render, Mesh(.Shader) * texture-reflect: Main, Render, Tri(.Shader), Cube(.Shader) * pathtrace-reflect: Main, Render, Scene, Pathtracer(.Shader) The pathtracer and bda shaders use compileShaderQ with a vulkan1.2 target (buffer_reference), wide.comp with vulkan1.1 (Int64). The sourceless julia.comp and tri.vert fixtures are reconstructed from the test suite's layout assertions, which pin them exactly. All four examples render correctly (mesh/texture self-checks pass) and the 75 vulkan-utils-spirv tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0036ca7 commit b20f711

50 files changed

Lines changed: 2848 additions & 2363 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/compute-reflect/Julia.hs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{-# LANGUAGE DataKinds #-}
2+
{-# LANGUAGE DeriveAnyClass #-}
3+
{-# LANGUAGE DeriveGeneric #-}
4+
{-# LANGUAGE DerivingStrategies #-}
5+
{-# LANGUAGE DerivingVia #-}
6+
{-# LANGUAGE OverloadedLists #-}
7+
{-# LANGUAGE TemplateHaskell #-}
8+
{-# LANGUAGE TypeFamilies #-}
9+
10+
{-| Julia-set compute pipeline, its interface reflected from 'Julia.Shader.code':
11+
12+
* 'reflectShaderTypesBytes' generates the 'Params' push-constant record (with
13+
a gl-block std430 'Storable');
14+
* 'singleDescriptorSetLayoutInfo' builds the descriptor set layout for the
15+
output SSBO;
16+
* 'pushConstantRanges' builds the pipeline layout's push-constant range; and
17+
* 'allocateSpecializationInfo' builds the pipeline's specialization info
18+
(@maxIterations@, @escapeRadius@) from the reflected constant ids.
19+
20+
Compare with the @compute@ example, which hand-writes all of this.
21+
-}
22+
module Julia
23+
( Params (..)
24+
, Pipeline (..)
25+
, allocatePipeline
26+
, workgroup
27+
) where
28+
29+
import Control.Monad.Trans.Resource (ResourceT, allocate)
30+
import qualified Data.Vector as V
31+
import Data.Word (Word32)
32+
import qualified Geomancy
33+
import Graphics.Gl.Block (Std430 (..))
34+
import Vulkan.CStruct.Extends (SomeStruct (..))
35+
import qualified Vulkan.Core10 as PipelineLayoutCreateInfo (PipelineLayoutCreateInfo (..))
36+
import qualified Vulkan.Core10 as Vk
37+
import Vulkan.Utils.Shader (shaderModuleStage)
38+
import Vulkan.Utils.SpirV.Descriptors (pushConstantRanges, singleDescriptorSetLayoutInfo)
39+
import Vulkan.Utils.SpirV.Reflect (reflectBytes)
40+
import Vulkan.Utils.SpirV.Specialization (allocateSpecializationInfo)
41+
import Vulkan.Utils.SpirV.TH (reflectShaderTypesBytes)
42+
import Vulkan.Zero (zero)
43+
44+
import qualified Julia.Shader as Shader
45+
46+
-- Generate the @Params@ push-constant record (and its std430 'Storable') from
47+
-- the same SPIR-V the runtime loads.
48+
reflectShaderTypesBytes Shader.code
49+
50+
-- | Workgroup size on each axis (matches @local_size_x/y@ in the shader).
51+
workgroup :: Int
52+
workgroup = 16
53+
54+
data Pipeline = Pipeline
55+
{ pipeline :: Vk.Pipeline
56+
, pipelineLayout :: Vk.PipelineLayout
57+
, descriptorSetLayout :: Vk.DescriptorSetLayout
58+
}
59+
60+
{- | The pipeline, specialized to the given iteration cap and escape radius.
61+
62+
Descriptor set layout, push-constant range and specialization info all come
63+
from reflecting 'Shader.code'.
64+
-}
65+
allocatePipeline :: Vk.Device -> Word32 -> Float -> ResourceT IO Pipeline
66+
allocatePipeline dev maxIterations escapeRadius = do
67+
-- Reflect the embedded module once; reuse it for the descriptor set layout,
68+
-- the push-constant range and the specialization info.
69+
reflected <- reflectBytes Shader.code
70+
71+
setLayoutInfo <- either fail pure (singleDescriptorSetLayoutInfo reflected)
72+
(_, descriptorSetLayout) <- Vk.withDescriptorSetLayout dev setLayoutInfo Nothing allocate
73+
74+
mSpec <- allocateSpecializationInfo reflected (maxIterations, escapeRadius)
75+
(_, shader) <- shaderModuleStage dev Vk.SHADER_STAGE_COMPUTE_BIT mSpec Shader.code
76+
(_, pipelineLayout) <-
77+
Vk.withPipelineLayout
78+
dev
79+
zero
80+
{ PipelineLayoutCreateInfo.setLayouts = [descriptorSetLayout]
81+
, PipelineLayoutCreateInfo.pushConstantRanges =
82+
V.fromList (pushConstantRanges reflected)
83+
}
84+
Nothing
85+
allocate
86+
let
87+
pipelineCreateInfo :: Vk.ComputePipelineCreateInfo '[]
88+
pipelineCreateInfo =
89+
zero
90+
{ Vk.layout = pipelineLayout
91+
, Vk.stage = shader
92+
, Vk.basePipelineHandle = zero
93+
}
94+
(_, (_, [computePipeline])) <-
95+
Vk.withComputePipelines dev zero [SomeStruct pipelineCreateInfo] Nothing allocate
96+
pure
97+
Pipeline
98+
{ pipeline = computePipeline
99+
, pipelineLayout = pipelineLayout
100+
, descriptorSetLayout = descriptorSetLayout
101+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{-# LANGUAGE QuasiQuotes #-}
2+
3+
{-| The Julia-set compute shader, compiled to SPIR-V at build time.
4+
5+
Its interface is reflected by vulkan-utils-spirv in the "Julia" module:
6+
the @Params@ push-constant block, the output SSBO's descriptor set layout,
7+
and the specialization constants.
8+
-}
9+
module Julia.Shader
10+
( code
11+
) where
12+
13+
import Data.ByteString (ByteString)
14+
import Vulkan.Utils.ShaderQQ.GLSL.Glslang (comp)
15+
16+
code :: ByteString
17+
code =
18+
[comp|
19+
#version 450
20+
#extension GL_ARB_separate_shader_objects : enable
21+
22+
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
23+
24+
// Specialized at pipeline-creation time (see Vulkan.Utils.SpirV.Specialization).
25+
layout(constant_id = 0) const uint maxIterations = 1000u;
26+
layout(constant_id = 1) const float escapeRadius = 2.0;
27+
28+
// Fields are ordered by non-increasing alignment (vec2/uvec2 = 8) so gl-block's
29+
// Generic std430 layout matches the shader; see the guardrail in
30+
// Vulkan.Utils.SpirV.Block.
31+
layout(push_constant, std430) uniform Params {
32+
vec2 center; // c in the Julia iteration
33+
uvec2 resolution; // output size in pixels
34+
} params;
35+
36+
layout(set = 0, binding = 0, std430) buffer OutputBuffer {
37+
vec4 pixels[];
38+
};
39+
40+
// From https://iquilezles.org/articles/palettes/
41+
vec3 palette(float t) {
42+
const vec3 a = vec3(0.5);
43+
const vec3 b = vec3(0.5);
44+
const vec3 c = vec3(1.0);
45+
const vec3 d = vec3(0.0, 0.10, 0.20);
46+
return a + b * cos(6.28318530718 * (c * t + d));
47+
}
48+
49+
void main() {
50+
uvec2 gid = gl_GlobalInvocationID.xy;
51+
if (gid.x >= params.resolution.x || gid.y >= params.resolution.y) {
52+
return;
53+
}
54+
55+
vec2 uv =
56+
( vec2(gid) / vec2(params.resolution) * 2.0 - 1.0 ) * escapeRadius;
57+
58+
vec2 z = uv;
59+
int i = 0;
60+
for (; i < int(maxIterations); ++i) {
61+
vec2 z2 = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y);
62+
z = z2 + params.center;
63+
if (dot(z, z) > escapeRadius * escapeRadius) {
64+
break;
65+
}
66+
}
67+
68+
uint idx = gid.y * params.resolution.x + gid.x;
69+
if (i == int(maxIterations)) {
70+
pixels[idx] = vec4(0.0, 0.0, 0.0, 1.0);
71+
} else {
72+
pixels[idx] = vec4(palette(float(i) / float(maxIterations)), 1.0);
73+
}
74+
}
75+
|]

0 commit comments

Comments
 (0)