Run GPU visibility without optional draw parameters feature
Native and manual checks / native (windows-2025) (push) Waiting to run
Native and manual checks / native (ubuntu-24.04) (push) Failing after 42s
Native and manual checks / manual (push) Successful in 32s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s

This commit is contained in:
Emil
2026-09-23 23:50:02 +03:00
parent ba3efa82e3
commit 433bce08c0
4 changed files with 36 additions and 12 deletions
+1
View File
@@ -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()
+4 -2
View File
@@ -60,8 +60,10 @@ struct GpuFrameParameters {
[[vk::binding(1,1)]] StructuredBuffer<uint> gfxVisibleIds;
[[vk::binding(2,1)]] StructuredBuffer<ViewRecord> 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)));
}
+4 -10
View File
@@ -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<std::uint32_t>(bin.instances.size()),
visible_base,
static_cast<std::uint32_t>(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);
}
+27
View File
@@ -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),