From 433bce08c038104e40580a5f1b68ba1acad23a13 Mon Sep 17 00:00:00 2001 From: Emil <65846814+emil28092005@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:50:02 +0300 Subject: [PATCH] Run GPU visibility without optional draw parameters feature --- cmake/GpuVisibility.cmake | 1 + shaders/gpu_scene.slang | 6 ++++-- src/render/renderer.cpp | 14 ++++---------- tests/test_shader_reflection.py | 27 +++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cmake/GpuVisibility.cmake b/cmake/GpuVisibility.cmake index fe0d68b..6ab20e2 100644 --- a/cmake/GpuVisibility.cmake +++ b/cmake/GpuVisibility.cmake @@ -9,4 +9,5 @@ if(TARGET faset_render AND BUILD_TESTING) "${PROJECT_SOURCE_DIR}/tests/render_gpu_visibility_tests.cpp") target_link_libraries(faset_render_gpu_visibility_tests PRIVATE faset_render) add_test(NAME gpu_visibility COMMAND faset_render_gpu_visibility_tests) + set_tests_properties(gpu_visibility PROPERTIES LABELS "gpu;p2") endif() diff --git a/shaders/gpu_scene.slang b/shaders/gpu_scene.slang index 4776b42..a0fae48 100644 --- a/shaders/gpu_scene.slang +++ b/shaders/gpu_scene.slang @@ -60,8 +60,10 @@ struct GpuFrameParameters { [[vk::binding(1,1)]] StructuredBuffer gfxVisibleIds; [[vk::binding(2,1)]] StructuredBuffer gfxViews; +// All indirect commands use firstInstance=0. The raw Vulkan index avoids the +// BaseInstance read that Slang adds for SV_InstanceID (DrawParameters feature). [shader("vertex")] -GpuSceneOutput gpuVertexMain(GpuSceneVertex vertex, uint drawInstance : SV_InstanceID) { +GpuSceneOutput gpuVertexMain(GpuSceneVertex vertex, uint drawInstance : SV_VulkanInstanceID) { InstanceRecord instance = gfxInstances[gfxVisibleIds[gpuFrame.drawInfo.x + drawInstance]]; float4 world = mul(instance.model, float4(vertex.position, 1)); GpuSceneOutput output; @@ -79,7 +81,7 @@ GpuSceneOutput gpuVertexMain(GpuSceneVertex vertex, uint drawInstance : SV_Insta } [shader("vertex")] -float4 gpuShadowMain(GpuSceneVertex vertex, uint drawInstance : SV_InstanceID) : SV_Position { +float4 gpuShadowMain(GpuSceneVertex vertex, uint drawInstance : SV_VulkanInstanceID) : SV_Position { InstanceRecord instance = gfxInstances[gfxVisibleIds[gpuFrame.drawInfo.x + drawInstance]]; return mul(gpuFrame.lightViewProjection, mul(instance.model, float4(vertex.position, 1))); } diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index abaa537..bc659b1 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -608,14 +608,11 @@ struct Renderer::Impl { vkGetPhysicalDeviceProperties(gpu, &properties); if (properties.apiVersion < VK_API_VERSION_1_3) continue; - VkPhysicalDeviceVulkan11Features f11{}; - f11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; VkPhysicalDeviceVulkan13Features f13{}; f13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; - f11.pNext = &f13; VkPhysicalDeviceFeatures2 features{}; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - features.pNext = &f11; + features.pNext = &f13; vkGetPhysicalDeviceFeatures2(gpu, &features); if (!f13.synchronization2 || !f13.dynamicRendering) continue; @@ -651,7 +648,6 @@ struct Renderer::Impl { max_storage_buffer_range = properties.limits.maxStorageBufferRange; max_image_dimension = properties.limits.maxImageDimension2D; scene.available = (queues[i].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0 && - f11.shaderDrawParameters && properties.limits.maxPerStageDescriptorStorageBuffers >= 8 && properties.limits.maxDescriptorSetStorageBuffers >= 8 && properties.limits.maxComputeWorkGroupInvocations >= 64 && @@ -679,17 +675,13 @@ struct Renderer::Impl { qi.queueFamilyIndex = queue_family; qi.queueCount = 1; qi.pQueuePriorities = &priority; - VkPhysicalDeviceVulkan11Features f11{}; - f11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; - f11.shaderDrawParameters = scene.available ? VK_TRUE : VK_FALSE; VkPhysicalDeviceVulkan13Features f13{}; f13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; f13.synchronization2 = VK_TRUE; f13.dynamicRendering = VK_TRUE; - f11.pNext = &f13; VkDeviceCreateInfo di{}; di.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - di.pNext = &f11; + di.pNext = &f13; di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi; const char* swap_extension = VK_KHR_SWAPCHAIN_EXTENSION_NAME; @@ -1858,6 +1850,8 @@ struct Renderer::Impl { static_cast(bin.instances.size()), visible_base, static_cast(bin.instances.size())}); + // gpuVertexMain/gpuShadowMain read raw Vulkan InstanceIndex. + // Keep firstInstance at zero; visible_base is supplied separately. gpu_frame.commands.push_back({bin.vertex_count, 0, bin.first_vertex, 0}); gpu_frame.textures.push_back(bin.texture); } diff --git a/tests/test_shader_reflection.py b/tests/test_shader_reflection.py index 12eff72..3a81afb 100644 --- a/tests/test_shader_reflection.py +++ b/tests/test_shader_reflection.py @@ -36,6 +36,33 @@ def parameter(name: str, index: int, shape: str, access: str, stride: int | None class ReflectionTests(unittest.TestCase): + def test_gpu_vertex_paths_do_not_require_shader_draw_parameters(self): + # SV_InstanceID makes Slang subtract BaseInstance and emit DrawParameters. + # Our indirect commands always use firstInstance=0, so the Vulkan instance + # index is sufficient and also runs on devices without that optional feature. + compiler = os.environ["FASET_TEST_SLANGC"] + with tempfile.TemporaryDirectory(prefix="faset-gpu-instance-index-") as directory: + for entry in ("gpuVertexMain", "gpuShadowMain"): + process = subprocess.run( + [sys.executable, str(SCRIPT), "--compiler", compiler, "--source", + str(SCRIPT.parents[1] / "shaders" / "gpu_scene.slang"), "--entry", + entry, "--define", "FASET_GPU_GRAPHICS=1", "--output", directory], + capture_output=True, text=True, + ) + self.assertEqual(process.returncode, 0, process.stderr) + bytecode = (Path(directory) / f"{entry}.spv").read_bytes() + words = struct.unpack(f"<{len(bytecode) // 4}I", bytecode) + capabilities = set() + offset = 5 + while offset < len(words): + count, opcode = words[offset] >> 16, words[offset] & 0xffff + self.assertGreater(count, 0) + if opcode == 17: # OpCapability + capabilities.add(words[offset + 1]) + offset += count + self.assertIn(1, capabilities) # Shader + self.assertNotIn(4427, capabilities) # DrawParameters + def test_gpu_storage_resources_keep_kind_and_stride(self): parameters = [ parameter("instances", 0, "structuredBuffer", "read", 224),