diff --git a/docs/conformance.md b/docs/conformance.md index b1c0b94..3fe369d 100644 --- a/docs/conformance.md +++ b/docs/conformance.md @@ -168,6 +168,7 @@ All are Mesa patches in `patches/mesa`. | 0082 | macOS has no `pthread_barrier`, so Mesa builds one from a mutex and a condition variable. That version told every thread it was the "serial" one, where the real one tells exactly one. `util_queue_finish` destroys its barrier in the serial thread, so on macOS the last thread to arrive destroyed the mutex and condition variable while the others were still waking up inside them, and one of them could block for good: a test process sat idle at exit in Zink's screen teardown for an hour (seen once, with the machine under load; an OpenGL program would hang when it quits). The barrier now returns true to the last thread only, and only after the others have left it. | A stress test of the barrier alone: the old version hangs within a minute, the new one passes 200,000 reuse rounds and 20,000 barriers destroyed by their serial thread | | 0083 | Zink submits a finished batch from a separate thread, and a read of a buffer waits for the batch that last wrote it. Two races between the two, both seen in `shader_atomic_counter_ops`, which reads a buffer straight after `glFlush`. The reader checked the batch's "not submitted yet" flag and then waited on a condition variable without looking again, so when the submit thread finished in between, the wake-up was already gone and the read waited for good (25 of 300 runs of those four tests; a program doing this would freeze). And the check for "does this buffer have a pending use" read the batch's id and then the flag while the submit thread wrote both: the old id with the new flag reads as "no use", and the read returned without waiting (6 of 300 runs: the first buffer still held its input, the second, read microseconds later, was complete, and reading the first again gave the right value). The flag is now cleared under the batch's mutex and the reader waits on it as a predicate; the flag is read before the id, with an ordered load. The wrong-value race is established from the compiled code, the captured values and the comparison below, not reproduced by injection. | 0 of 200 runs fail, beside 31 of 300 for the build before it, run at the same time. A delay injected between the reader's check and its wait hung 2 of 2 runs before and 0 of 3 after. `gl46-r12` has no retried test | | 0085 | The driver reported no depth bounds test below Apple GPU family 10, where Metal has none; DOOM (2016) needs it. It is emulated now: the render pass is ended before a draw with the test on so that the depth is in memory, and a variant of the fragment shader reads each sample's depth and drops the samples outside the bounds (a draw that writes depth reads a copy). The variant is built on first use, as the statistics one was, which 0085 generalises: in every shader it would turn off hidden surface removal for every Zink pipeline. Details and what is left out in findings #57. | 3,821 depth bounds tests pass where 1,844 did, 0 fail; `tests/mac/vk-depth-bounds-msaa.sh` for multisampled images, which the CTS does not test | +| 0086 | A buffer descriptor whose range runs past the end of its buffer, which is invalid but which DOOM (2016) writes, aborted on an assertion in Mesa's shared code; without the assertion its reads would have gone past the buffer into other memory. The range of a buffer descriptor and of a transform feedback buffer is clamped to the buffer now, so such reads are out of bounds like any other. Findings #58. | 52,804 robustness, uniform buffer, transform feedback and descriptor tests pass (one in three of those lists), 0 fail; `tests/mac/vk-ubo-range-past-end.sh` for the invalid range, which the CTS does not test | 0041, 0052, 0053, 0055, 0062, 0071, 0075, 0077 and 0083 are in Zink, 0058 and 0074 in the geometry shader emulation shared with other Mesa drivers, 0069 in NIR, part of 0081 in NIR's texture lowering and 0082 in Mesa's shared utilities. The rest are in the Vulkan driver, so they are not specific to OpenGL: they can equally be hit by a Direct3D 12 game through vkd3d-proton or by a Vulkan game. diff --git a/docs/findings.md b/docs/findings.md index add4727..4a5a14a 100644 --- a/docs/findings.md +++ b/docs/findings.md @@ -1868,3 +1868,28 @@ loaded from a pipeline cache. It found that the MSL backend ignored the sample index of a multisampled image load (every sample read sample 0; the driver offers no multisampled storage images, so nothing reached it before), and the test reads through a texture fetch instead. + +## A buffer descriptor whose range ran past its buffer aborted DOOM (fixed, #58, issue #128) + +With depth bounds reported (#57), DOOM (2016) gets past device creation and then stops on an +assertion in Mesa, `range + offset <= buffer->size` (`vk_buffer.h`). It writes uniform buffer +descriptors with a range of 64 KB on buffers of 49,152 and 65,520 bytes (issue #128, found by +tracing its descriptor writes). That is invalid (VUID-VkDescriptorBufferInfo-range-00342), so +Vulkan leaves the result undefined; the game runs on Windows drivers, and on Linux, where Mesa is +built with its assertions off and the drivers use the range as given. Hadron's Mesa is built with +them on (`--buildtype=debugoptimized`, #47), so the game aborts. + +Turning the assertions off would only trade the abort for reads of other memory: with the range +as given, KosmicKrisp's bounds checks let the shader read past the buffer into whatever is +allocated after it. Mesa patch 0086 clamps the range of a buffer descriptor (and of a transform +feedback buffer) to the part of the buffer from its offset, so that a read past the buffer's end +is out of bounds and is handled as any other one: zero with robust buffer access, nothing outside +the buffer without it. A valid range is unchanged. + +`tests/mac/vk-ubo-range-past-end.sh` writes descriptors as DOOM does, plain and dynamic, with a +buffer filled with a marker right after the small one, and reads inside and past the end with +robustBufferAccess2 on. The installed driver aborts on the assertion; with the clamp every read +inside sees the buffer's data and every read past the end sees zero; with the range kept and no +assertion, the reads past the end see the next buffer's marker. A third of the Vulkan CTS lists for +robustness, uniform buffers, transform feedback and descriptor binding passes with the clamp: +52,804 pass, none fail. diff --git a/patches/mesa/0086-kk-Clamp-buffer-descriptor-range-to-buffer-bounds.patch b/patches/mesa/0086-kk-Clamp-buffer-descriptor-range-to-buffer-bounds.patch new file mode 100644 index 0000000..4bd4209 --- /dev/null +++ b/patches/mesa/0086-kk-Clamp-buffer-descriptor-range-to-buffer-bounds.patch @@ -0,0 +1,30 @@ +From f1769a547a249534953378b83d0ee094895b9e83 Mon Sep 17 00:00:00 2001 +From: Broyojo +Date: Sat, 24 Oct 2026 19:30:16 -0400 +Subject: [PATCH] kk: Clamp buffer descriptor range to buffer bounds + +Generated-by: LLM +--- + src/kosmickrisp/vulkan/kk_buffer.h | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/kosmickrisp/vulkan/kk_buffer.h b/src/kosmickrisp/vulkan/kk_buffer.h +index 61db828e239..c3fcb129311 100644 +--- a/src/kosmickrisp/vulkan/kk_buffer.h ++++ b/src/kosmickrisp/vulkan/kk_buffer.h +@@ -51,9 +51,11 @@ kk_buffer_addr_range(const struct kk_buffer *buffer, uint64_t offset, + if (buffer == NULL) + return (struct kk_addr_range){.range = 0}; + ++ uint64_t avail = buffer->vk.size - MIN2(offset, buffer->vk.size); ++ + return (struct kk_addr_range){ + .addr = vk_buffer_address(&buffer->vk, offset), +- .range = vk_buffer_range(&buffer->vk, offset, range), ++ .range = range == VK_WHOLE_SIZE ? avail : MIN2(range, avail), + }; + } + +-- +2.54.0 (Apple Git-157) + diff --git a/tests/mac/vk-ubo-range-past-end.c b/tests/mac/vk-ubo-range-past-end.c new file mode 100644 index 0000000..fe25447 --- /dev/null +++ b/tests/mac/vk-ubo-range-past-end.c @@ -0,0 +1,264 @@ +/* A uniform buffer descriptor whose range runs past the end of its buffer, as DOOM (2016) writes + * them (issue #128: a 64 KB range on buffers of 49152 and 65520 bytes). That is invalid + * (VUID-VkDescriptorBufferInfo-range-00342), so Vulkan says nothing about the result, but a + * driver should not abort or read other memory. Buffer A is the small one; buffer B, filled with + * a marker, sits right after it in the same allocation. A compute shader reads A's descriptor + * inside A and past its end, with robustBufferAccess2 on, through a plain and a dynamic uniform + * buffer descriptor. Inside it must see A's data; past the end it must not see B's marker (a + * driver that keeps the range reads B there) and, with the range clamped to the buffer, sees + * zero. Runs on the first Vulkan device; VK_DRIVER_FILES picks the driver. + * tests/mac/vk-ubo-range-past-end.sh writes the shader, builds this and runs it. */ +#include +#include +#include +#include + +static const uint32_t cs_spv[] = +#include "vk-ubo-range-past-end.comp.inc" +; + +#define RANGE 65536 +#define MARKER 0xdeadbeefu +#define READS 4 +#define CHECK(x) do { VkResult r_ = (x); if (r_ != VK_SUCCESS) { \ + fprintf( stderr, "%s:%d: %s failed: %d\n", __FILE__, __LINE__, #x, r_ ); exit( 2 ); } } while (0) + +static VkDevice device; +static VkPhysicalDevice physical; + +static uint32_t memory_type( uint32_t bits, VkMemoryPropertyFlags flags ) +{ + VkPhysicalDeviceMemoryProperties props; + vkGetPhysicalDeviceMemoryProperties( physical, &props ); + for (uint32_t i = 0; i < props.memoryTypeCount; i++) + if ((bits & (1u << i)) && (props.memoryTypes[i].propertyFlags & flags) == flags) return i; + fprintf( stderr, "no memory type\n" ); + exit( 2 ); +} + +static VkBuffer create_buffer( VkDeviceSize size, VkBufferUsageFlags usage ) +{ + VkBufferCreateInfo info = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, + }; + VkBuffer buffer; + CHECK( vkCreateBuffer( device, &info, NULL, &buffer ) ); + return buffer; +} + +/* Returns 0 if every read is as expected. */ +static int run( VkQueue queue, uint32_t family, VkDeviceSize a_size, VkBool32 dynamic ) +{ + VkBufferUsageFlags usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + VkBuffer a = create_buffer( a_size, usage ), b = create_buffer( RANGE, usage ); + VkBuffer out = create_buffer( READS * 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT ); + VkMemoryRequirements a_req, b_req, out_req; + vkGetBufferMemoryRequirements( device, a, &a_req ); + vkGetBufferMemoryRequirements( device, b, &b_req ); + vkGetBufferMemoryRequirements( device, out, &out_req ); + VkDeviceSize b_offset = (a_req.size + b_req.alignment - 1) & ~(b_req.alignment - 1); + VkDeviceSize out_offset = (b_offset + b_req.size + out_req.alignment - 1) & ~(out_req.alignment - 1); + VkMemoryAllocateInfo alloc = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = out_offset + out_req.size, + .memoryTypeIndex = memory_type( a_req.memoryTypeBits & b_req.memoryTypeBits & out_req.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ), + }; + VkDeviceMemory memory; + CHECK( vkAllocateMemory( device, &alloc, NULL, &memory ) ); + CHECK( vkBindBufferMemory( device, a, memory, 0 ) ); + CHECK( vkBindBufferMemory( device, b, memory, b_offset ) ); + CHECK( vkBindBufferMemory( device, out, memory, out_offset ) ); + + uint8_t *map; + CHECK( vkMapMemory( device, memory, 0, VK_WHOLE_SIZE, 0, (void **)&map ) ); + memset( map, 0, alloc.allocationSize ); + for (VkDeviceSize i = 0; i < a_size / 4; i++) ((uint32_t *)map)[i] = (uint32_t)i + 1; + for (VkDeviceSize i = 0; i < RANGE / 4; i++) ((uint32_t *)(map + b_offset))[i] = MARKER; + + VkDescriptorType ubo_type = dynamic ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC + : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + VkDescriptorSetLayoutBinding bindings[2] = { + {0, ubo_type, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, NULL}, + }; + VkDescriptorSetLayoutCreateInfo set_layout_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 2, .pBindings = bindings, + }; + VkDescriptorSetLayout set_layout; + CHECK( vkCreateDescriptorSetLayout( device, &set_layout_info, NULL, &set_layout ) ); + VkPushConstantRange push = {VK_SHADER_STAGE_COMPUTE_BIT, 0, READS * 4}; + VkPipelineLayoutCreateInfo layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, + .pSetLayouts = &set_layout, .pushConstantRangeCount = 1, .pPushConstantRanges = &push, + }; + VkPipelineLayout layout; + CHECK( vkCreatePipelineLayout( device, &layout_info, NULL, &layout ) ); + + VkShaderModuleCreateInfo module_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = sizeof(cs_spv), .pCode = cs_spv, + }; + VkShaderModule module; + CHECK( vkCreateShaderModule( device, &module_info, NULL, &module ) ); + VkComputePipelineCreateInfo pipeline_info = { + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = module, .pName = "main"}, + .layout = layout, + }; + VkPipeline pipeline; + CHECK( vkCreateComputePipelines( device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &pipeline ) ); + + VkDescriptorPoolSize sizes[2] = {{ubo_type, 1}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}}; + VkDescriptorPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = 1, + .poolSizeCount = 2, .pPoolSizes = sizes, + }; + VkDescriptorPool pool; + CHECK( vkCreateDescriptorPool( device, &pool_info, NULL, &pool ) ); + VkDescriptorSetAllocateInfo set_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = pool, + .descriptorSetCount = 1, .pSetLayouts = &set_layout, + }; + VkDescriptorSet set; + CHECK( vkAllocateDescriptorSets( device, &set_info, &set ) ); + VkDescriptorBufferInfo ubo = {a, 0, RANGE}, ssbo = {out, 0, VK_WHOLE_SIZE}; + VkWriteDescriptorSet writes[2] = { + {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = set, .dstBinding = 0, + .descriptorCount = 1, .descriptorType = ubo_type, .pBufferInfo = &ubo}, + {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = set, .dstBinding = 1, + .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &ssbo}, + }; + vkUpdateDescriptorSets( device, 2, writes, 0, NULL ); + + /* The vec4 indices read: A's first and last, the first past A's end, and the last in range. */ + uint32_t index[READS] = {0, (uint32_t)(a_size / 16) - 1, (uint32_t)(a_size / 16), RANGE / 16 - 1}; + + VkCommandPoolCreateInfo cmd_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = family, + }; + VkCommandPool cmd_pool; + CHECK( vkCreateCommandPool( device, &cmd_pool_info, NULL, &cmd_pool ) ); + VkCommandBufferAllocateInfo cmd_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, + }; + VkCommandBuffer cmd; + CHECK( vkAllocateCommandBuffers( device, &cmd_info, &cmd ) ); + VkCommandBufferBeginInfo begin = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + CHECK( vkBeginCommandBuffer( cmd, &begin ) ); + vkCmdBindPipeline( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline ); + uint32_t dynamic_offset = 0; + vkCmdBindDescriptorSets( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, 1, &set, + dynamic ? 1 : 0, &dynamic_offset ); + vkCmdPushConstants( cmd, layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(index), index ); + vkCmdDispatch( cmd, 1, 1, 1 ); + VkMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_HOST_READ_BIT, + }; + vkCmdPipelineBarrier( cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, + 1, &barrier, 0, NULL, 0, NULL ); + CHECK( vkEndCommandBuffer( cmd ) ); + VkSubmitInfo submit = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd, + }; + CHECK( vkQueueSubmit( queue, 1, &submit, VK_NULL_HANDLE ) ); + CHECK( vkQueueWaitIdle( queue ) ); + + const uint32_t *result = (const uint32_t *)(map + out_offset); + int failed = 0; + for (int i = 0; i < READS; i++) { + uint32_t expect = index[i] < a_size / 16 ? index[i] * 4 + 1 : 0; + printf( " %s size %llu: vec4 %u reads %#x (expected %#x)\n", dynamic ? "dynamic" : "plain", + (unsigned long long)a_size, index[i], result[i * 4], expect ); + if (result[i * 4] != expect) failed = 1; + } + + vkDestroyCommandPool( device, cmd_pool, NULL ); + vkDestroyDescriptorPool( device, pool, NULL ); + vkDestroyPipeline( device, pipeline, NULL ); + vkDestroyShaderModule( device, module, NULL ); + vkDestroyPipelineLayout( device, layout, NULL ); + vkDestroyDescriptorSetLayout( device, set_layout, NULL ); + vkDestroyBuffer( device, a, NULL ); + vkDestroyBuffer( device, b, NULL ); + vkDestroyBuffer( device, out, NULL ); + vkFreeMemory( device, memory, NULL ); + return failed; +} + +int main( void ) +{ + VkApplicationInfo app = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = VK_API_VERSION_1_2, + }; + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app, + .flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, + .enabledExtensionCount = 1, + .ppEnabledExtensionNames = (const char *[]){VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME}, + }; + VkInstance instance; + CHECK( vkCreateInstance( &instance_info, NULL, &instance ) ); + uint32_t count = 1; + vkEnumeratePhysicalDevices( instance, &count, &physical ); + if (!count) { fprintf( stderr, "no Vulkan device\n" ); return 2; } + + VkPhysicalDeviceRobustness2FeaturesEXT robustness2 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, + }; + VkPhysicalDeviceFeatures2 features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &robustness2, + }; + vkGetPhysicalDeviceFeatures2( physical, &features ); + if (!features.features.robustBufferAccess || !robustness2.robustBufferAccess2) { + printf( "robustBufferAccess2 not supported\n" ); + return 77; + } + + uint32_t ext_count = 0; + vkEnumerateDeviceExtensionProperties( physical, NULL, &ext_count, NULL ); + VkExtensionProperties *exts = calloc( ext_count, sizeof(*exts) ); + vkEnumerateDeviceExtensionProperties( physical, NULL, &ext_count, exts ); + const char *names[2] = {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME}; + uint32_t name_count = 1; + for (uint32_t i = 0; i < ext_count; i++) + if (!strcmp( exts[i].extensionName, "VK_KHR_portability_subset" )) + names[name_count++] = "VK_KHR_portability_subset"; + free( exts ); + + float priority = 1; + VkDeviceQueueCreateInfo queue_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = 0, + .queueCount = 1, .pQueuePriorities = &priority, + }; + VkPhysicalDeviceRobustness2FeaturesEXT enable_robustness2 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, + .robustBufferAccess2 = VK_TRUE, + }; + VkPhysicalDeviceFeatures2 enable = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &enable_robustness2, + .features = {.robustBufferAccess = VK_TRUE}, + }; + VkDeviceCreateInfo device_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &enable, + .queueCreateInfoCount = 1, .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = name_count, .ppEnabledExtensionNames = names, + }; + CHECK( vkCreateDevice( physical, &device_info, NULL, &device ) ); + VkQueue queue; + vkGetDeviceQueue( device, 0, 0, &queue ); + + int failed = 0; + VkDeviceSize sizes[2] = {49152, 65520}; + for (int s = 0; s < 2; s++) + for (int dynamic = 0; dynamic < 2; dynamic++) + failed |= run( queue, 0, sizes[s], dynamic ); + + vkDestroyDevice( device, NULL ); + vkDestroyInstance( instance, NULL ); + printf( "%s\n", failed ? "FAIL" : "PASS" ); + return failed; +} diff --git a/tests/mac/vk-ubo-range-past-end.sh b/tests/mac/vk-ubo-range-past-end.sh new file mode 100755 index 0000000..daabc10 --- /dev/null +++ b/tests/mac/vk-ubo-range-past-end.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Builds and runs vk-ubo-range-past-end.c: a uniform buffer descriptor whose range runs past the +# end of its buffer, as DOOM (2016) writes them. +# Usage: tests/mac/vk-ubo-range-past-end.sh [ICD json] (default: the installed KosmicKrisp) +# For a KosmicKrisp that is built but not installed, pass +# build/mesa/src/kosmickrisp/vulkan/kosmickrisp_mesa_devenv_icd.aarch64.json. +set -eu +ROOT=$(cd "$(dirname "$0")/../.." && pwd) +OUT="$ROOT/build/vk-ubo-range-past-end" +BREW=$(brew --prefix) +mkdir -p "$OUT" + +cat > "$OUT/vk-ubo-range-past-end.comp" <<'GLSL' +#version 450 +layout(local_size_x = 1) in; +layout(binding = 0) uniform A { uvec4 a[4096]; }; +layout(binding = 1) buffer Out { uvec4 result[4]; }; +layout(push_constant) uniform Index { uint index[4]; }; +void main() +{ + for (int i = 0; i < 4; i++) + result[i] = a[index[i]]; +} +GLSL +glslc -mfmt=c "$OUT/vk-ubo-range-past-end.comp" -o "$OUT/vk-ubo-range-past-end.comp.inc" +clang -O1 -Wall "$ROOT/tests/mac/vk-ubo-range-past-end.c" -I"$OUT" -I"$BREW/include" -L"$BREW/lib" \ + -lvulkan -Wl,-rpath,"$BREW/lib" -o "$OUT/vk-ubo-range-past-end" +if [ $# -gt 0 ]; then + VK_DRIVER_FILES="$1" "$OUT/vk-ubo-range-past-end" +else + VK_DRIVER_FILES="$ROOT/dist/mesa/share/vulkan/icd.d/kosmickrisp_mesa_icd.aarch64.json" \ + "$OUT/vk-ubo-range-past-end" +fi