diff --git a/glslc/src/main.cc b/glslc/src/main.cc index 4de210068..229c512b9 100644 --- a/glslc/src/main.cc +++ b/glslc/src/main.cc @@ -130,7 +130,9 @@ An input file of - represents standard input. -fshader-stage= Treat subsequent input files as having stage . Valid stages are vertex, vert, fragment, frag, tesscontrol, - tesc, tesseval, tese, geometry, geom, compute, and comp. + tesc, tesseval, tese, geometry, geom, compute, comp, raygen, + rgen, anyhit, rahit, closest, rchit, miss, rmiss, intersect, + rint, callable, rcall, task, and mesh. -g Generate source-level debug information. -h Display available options. --help Display available options. diff --git a/glslc/src/shader_stage.cc b/glslc/src/shader_stage.cc index 23f15b1cd..9646bd395 100644 --- a/glslc/src/shader_stage.cc +++ b/glslc/src/shader_stage.cc @@ -44,11 +44,17 @@ shaderc_shader_kind MapStageNameToForcedKind(const string_piece& stage_name) { {"geom", shaderc_glsl_geometry_shader}, {"compute", shaderc_glsl_compute_shader}, {"comp", shaderc_glsl_compute_shader}, + {"raygen", shaderc_glsl_raygen_shader}, {"rgen", shaderc_glsl_raygen_shader }, + {"anyhit", shaderc_glsl_anyhit_shader}, {"rahit", shaderc_glsl_anyhit_shader }, + {"closest", shaderc_glsl_closesthit_shader}, {"rchit", shaderc_glsl_closesthit_shader }, + {"miss", shaderc_glsl_miss_shader}, {"rmiss", shaderc_glsl_miss_shader }, + {"intersect", shaderc_glsl_intersection_shader}, {"rint", shaderc_glsl_intersection_shader }, + {"callable", shaderc_glsl_callable_shader}, {"rcall", shaderc_glsl_callable_shader }, {"task", shaderc_glsl_task_shader }, {"mesh", shaderc_glsl_mesh_shader }, diff --git a/glslc/test/option_shader_stage.py b/glslc/test/option_shader_stage.py index 09a920ae4..348cfb7b4 100644 --- a/glslc/test/option_shader_stage.py +++ b/glslc/test/option_shader_stage.py @@ -61,6 +61,266 @@ def simple_compute_shader(): uvec3 temp = gl_WorkGroupID; }""" +def simple_raygen_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + + // 1. Top-Level Acceleration Structure (TLAS) containing your scene geometry + layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; + + // 2. Storage image to write the output colors to + layout(binding = 1, set = 0, rgba8) uniform writeonly image2D imageOutput; + + // 3. Payload layout to receive ray tracing result (matches hit/miss shaders) + layout(location = 0) rayPayloadEXT vec3 hitColor; + + void main() + { + // Compute pixel coordinates based on launch ID + ivec2 pixel = ivec2(gl_LaunchIDEXT.xy); + vec2 uv = vec2(gl_LaunchIDEXT.xy) / vec2(gl_LaunchSizeEXT.xy); + + // Simple orthographic/normalized ray setup + vec3 origin = vec3(uv * 2.0 - 1.0, -1.0); // Normalized Screen Space to World [-1, 1] + vec3 direction = vec3(0.0, 0.0, 1.0); // Ray pointing forward into screen + + uint rayFlags = gl_RayFlagsOpaqueEXT; + uint cullMask = 0xFF; // Trace all geometries + float tMin = 0.001; + float tMax = 1000.0; + + // Trace the ray into the acceleration structure + traceRayEXT( + topLevelAS, // TLAS handle + rayFlags, // Ray behavior flags + cullMask, // Visibility mask + 0, 0, 0, // SBT offset, stride, miss index + origin, // Ray Origin (vec3) + tMin, // Minimum intersection distance + direction, // Ray Direction (vec3) + tMax, // Maximum intersection distance + 0 // Payload location index + ); + + // Store the resulting color into the output image + imageStore(imageOutput, pixel, vec4(hitColor, 1.0)); + } + """ + +def simple_intersection_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + + // Intersection payload/attribute sent to Closest Hit shader + hitAttributeEXT vec3 normal; + + void main() + { + // Built-in ray parameters supplied by Vulkan API + vec3 rayOrigin = gl_WorldRayOriginEXT; + vec3 rayDirection = gl_WorldRayDirectionEXT; + float tMin = gl_RayTminEXT; + float tMax = gl_RayTmaxEXT; + + // Define a sphere centered at (0,0,0) with radius 1.0 + float radius = 1.0; + + // Ray-Sphere Intersection Math (Quadratic Equation: a*t^2 + b*t + c = 0) + float a = dot(rayDirection, rayDirection); + float b = 2.0 * dot(rayOrigin, rayDirection); + float c = dot(rayOrigin, rayOrigin) - (radius * radius); + + float discriminant = b * b - 4.0 * a * c; + + if (discriminant >= 0.0) + { + // Calculate nearest intersection point distance (t) + float t = (-b - sqrt(discriminant)) / (2.0 * a); + + // Check if the intersection point lies within the valid ray range [tMin, tMax] + if (t >= tMin && t <= tMax) + { + // Compute hit normal to pass along to Closest Hit Shader + normal = normalize(rayOrigin + t * rayDirection); + + // Report the intersection to Vulkan (0 is a user-defined hit type) + reportIntersectionEXT(t, 0); + } + } + } + """ + +def simple_anyhit_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + #extension GL_EXT_nonuniform_qualifier : enable + + // Texture sampler layout (e.g., alpha mask) + layout(binding = 2, set = 0) uniform sampler2D alphaTextures[]; + + // Attributes received from Intersection Shader or Built-in Triangle barycentrics + hitAttributeEXT vec2 hitAttribs; + + void main() + { + // Compute UVs using barycentric coordinates (for triangle geometry) + vec3 barycentrics = vec3(1.0 - hitAttribs.x - hitAttribs.y, hitAttribs.x, hitAttribs.y); + + // Fetch UV coordinates or material ID for this instance/primitive + // (Assuming simple UV mapping from vertex attribute data) + vec2 uv = hitAttribs; + + // Sample the alpha channel from texture + float alpha = texture(alphaTextures[gl_InstanceCustomIndexEXT], uv).a; + + // Alpha cutoff threshold + if (alpha < 0.5) + { + // Reject this intersection candidate; traversal continues along the ray + ignoreIntersectionEXT; + } + + // If not ignored, Vulkan accepts this candidate hit by default. + } + """ + +def simple_miss_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + + // Payload output sent back to the Ray Generation Shader + // (Location index MUST match the payload location index passed to traceRayEXT) + layout(location = 0) rayPayloadInEXT vec3 hitColor; + + void main() + { + // Get the normalized direction vector of the ray that missed + vec3 rayDir = normalize(gl_WorldRayDirectionEXT); + + // Create a simple vertical sky gradient (blue to white) based on Y direction + float t = 0.5 * (rayDir.y + 1.0); + vec3 skyColor = mix(vec3(1.0, 1.0, 1.0), vec3(0.5, 0.7, 1.0), t); + + // Write output color into payload + hitColor = skyColor; + } + """ + +def simple_closest_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + + // Payload received from Raygen (location MUST match traceRayEXT call) + layout(location = 0) rayPayloadInEXT vec3 hitColor; + + // Attributes passed from hardware ray-triangle intersection (Barycentric coordinates) + hitAttributeEXT vec2 hitAttribs; + + void main() + { + // 1. Calculate barycentric coordinates for the hit point on the triangle + vec3 barycentrics = vec3(1.0 - hitAttribs.x - hitAttribs.y, hitAttribs.x, hitAttribs.y); + + // 2. Derive surface normal (For demo purposes: using flat normal from triangle hit) + // In a production setup, you would fetch vertex normals using gl_PrimitiveID / SSBOs + vec3 objectNormal = vec3(0.0, 1.0, 0.0); + vec3 worldNormal = normalize(gl_ObjectToWorldEXT * vec4(objectNormal, 0.0)); + + // 3. Define a simple directional light source + vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3)); + vec3 baseColor = vec3(0.8, 0.2, 0.2); // Red material + + // 4. Compute diffuse lighting (N dot L) + float diffuse = max(dot(worldNormal, lightDir), 0.1); // 0.1 ambient floor + + // 5. Output shaded color to payload + hitColor = baseColor * diffuse; + } + """ + +def simple_callable_shader(): + return """#version 460 + #extension GL_EXT_ray_tracing : enable + + layout(location = 0) callableDataInEXT struct Params { + float foo; + } params; + + void main() + { + params.foo *= 2; + } + """ + +def simple_mesh_shader(): + return """#version 460 + #extension GL_EXT_mesh_shader : enable + + layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + // Output configuration: primitive type, max vertex count, max primitive count + layout(triangles, max_vertices = 3, max_primitives = 1) out; + + // Custom per-vertex outputs to the fragment shader + layout(location = 0) out vec3 outColor[]; + + void main() + { + // Set total vertices and primitives emitted by this meshlet thread group + SetMeshOutputsEXT(3, 1); + + // 1. Define Vertex Positions (in Normalized Device Coordinates [-1, 1]) + gl_MeshVerticesEXT[0].gl_Position = vec4(-0.5, -0.5, 0.0, 1.0); + gl_MeshVerticesEXT[1].gl_Position = vec4( 0.5, -0.5, 0.0, 1.0); + gl_MeshVerticesEXT[2].gl_Position = vec4( 0.0, 0.5, 0.0, 1.0); + + // 2. Pass Per-Vertex Attributes + outColor[0] = vec3(1.0, 0.0, 0.0); // Red + outColor[1] = vec3(0.0, 1.0, 0.0); // Green + outColor[2] = vec3(0.0, 0.0, 1.0); // Blue + + // 3. Define Primitive Index Topologies (1 triangle using 3 vertex indices) + gl_PrimitiveTriangleIndicesEXT[0] = uvec3(0, 1, 2); + } + """ + +def simple_task_shader(): + return """#version 460 + #extension GL_EXT_mesh_shader : enable + + // Define workgroup dimensions for task shader evaluation + layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + // Payload structure passed from Task Shader to Mesh Shader + struct TaskPayload { + uint meshletID; + }; + taskPayloadSharedEXT TaskPayload payload; + + void main() + { + // Local meshlet index to test + uint meshletIndex = gl_GlobalInvocationID.x; + + // 1. Perform Culling Test (Frustum, Occlusion, or Backface) + bool isVisible = true; // Placeholder for actual culling logic + + if (isVisible) + { + // 2. Populate payload data shared with spawned Mesh Shaders + payload.meshletID = meshletIndex; + + // 3. Emit task count: Launch 1 Mesh Shader workgroup (X=1, Y=1, Z=1) + EmitMeshTasksEXT(1, 1, 1); + } + else + { + // Cull completely: Do not spawn any Mesh Shader workgroups + EmitMeshTasksEXT(0, 0, 0); + } + } + """ + @inside_glslc_testsuite('OptionShaderStage') class TestShaderStageWithGlslExtension(expect.ValidObjectFile): @@ -104,8 +364,8 @@ class TestShaderStageWithNoExtension(expect.ValidObjectFile): @inside_glslc_testsuite('OptionShaderStage') -class TestAllShaderStages(expect.ValidObjectFile): - """Tests all possible -fshader-stage values.""" +class TestAllShaderStagesForSpv1p0(expect.ValidObjectFile): + """Tests all possible -fshader-stage values for SPIR-V 1.0.""" shader1 = FileShader(simple_vertex_shader(), '.glsl') shader2 = FileShader(simple_fragment_shader(), '.glsl') @@ -123,6 +383,53 @@ class TestAllShaderStages(expect.ValidObjectFile): '-fshader-stage=compute', shader6] +@inside_glslc_testsuite('OptionShaderStage') +class TestAllShaderStagesForSpv1p4(expect.ValidObjectFile1_4): + """Tests all possible -fshader-stage values that only target at least SPIR-V 1.4""" + + raygen = FileShader(simple_raygen_shader(), '.glsl') + intersection = FileShader(simple_intersection_shader(), '.glsl') + anyhit = FileShader(simple_anyhit_shader(), '.glsl') + miss = FileShader(simple_miss_shader(), '.glsl') + closest = FileShader(simple_closest_shader(), '.glsl') + miss = FileShader(simple_miss_shader(), '.glsl') + callable_ = FileShader(simple_callable_shader(), '.glsl') + mesh = FileShader(simple_mesh_shader(), '.glsl') + task = FileShader(simple_task_shader(), '.glsl') + glslc_args = [ + '-c', + '--target-spv=spv1.4', + '-fshader-stage=raygen', raygen, + '-fshader-stage=intersect', intersection, + '-fshader-stage=anyhit', anyhit, + '-fshader-stage=closest', closest, + '-fshader-stage=miss', miss, + '-fshader-stage=callable', callable_, + '-fshader-stage=mesh', mesh, + '-fshader-stage=task', task] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestAllShaderStagesForSpv1p4ShortNames(expect.ValidObjectFile1_4): + """Tests all possible -fshader-stage values that only target at least SPIR-V 1.4""" + + raygen = FileShader(simple_raygen_shader(), '.glsl') + intersection = FileShader(simple_intersection_shader(), '.glsl') + anyhit = FileShader(simple_anyhit_shader(), '.glsl') + miss = FileShader(simple_miss_shader(), '.glsl') + closest = FileShader(simple_closest_shader(), '.glsl') + callable_ = FileShader(simple_callable_shader(), '.glsl') + glslc_args = [ + '-c', + '--target-spv=spv1.4', + '-fshader-stage=rgen', raygen, + '-fshader-stage=rint', intersection, + '-fshader-stage=rahit', anyhit, + '-fshader-stage=rchit', closest, + '-fshader-stage=rmiss', miss, + '-fshader-stage=rcall', callable_] + + @inside_glslc_testsuite('OptionShaderStage') class TestShaderStageOverwriteFileExtension(expect.ValidObjectFile): """Tests -fshader-stage has precedence over file extension.""" diff --git a/glslc/test/parameter_tests.py b/glslc/test/parameter_tests.py index d49621060..ac7aa040c 100644 --- a/glslc/test/parameter_tests.py +++ b/glslc/test/parameter_tests.py @@ -134,7 +134,9 @@ class HelpParameters( -fshader-stage= Treat subsequent input files as having stage . Valid stages are vertex, vert, fragment, frag, tesscontrol, - tesc, tesseval, tese, geometry, geom, compute, and comp. + tesc, tesseval, tese, geometry, geom, compute, comp, raygen, + rgen, anyhit, rahit, closest, rchit, miss, rmiss, intersect, + rint, callable, rcall, task, and mesh. -g Generate source-level debug information. -h Display available options. --help Display available options.