From 460a196fcaeb2cbdd018728f99af8472d9308a5d Mon Sep 17 00:00:00 2001 From: Ali Almohaya Date: Mon, 12 Oct 2026 19:11:00 +0200 Subject: [PATCH] shader: emit dynamic sample offsets with the enabled maintenance8 feature --- src/graphics/host_gpu/graphicContext.h | 2 +- .../renderer/pipeline/pipelineCache.cpp | 20 ++-- src/graphics/host_gpu/renderer/renderDraw.cpp | 2 +- .../presentation/window/vulkanWindow.cpp | 8 +- .../shader/recompiler/ShaderRecompiler.cpp | 3 +- .../shader/recompiler/ShaderRecompiler.h | 1 + .../recompiler/backend/spirv/SpirvEmitter.cpp | 4 +- .../recompiler/backend/spirv/SpirvEmitter.h | 3 +- .../backend/spirv/spirvEmitterImage.cpp | 5 +- .../backend/spirv/spirvEmitterInternal.h | 5 +- tests/ShaderRecompilerComputeTests.cpp | 108 +++++++++++++++++- 11 files changed, 135 insertions(+), 26 deletions(-) diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 113a086d..3aa09cda 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -33,7 +33,7 @@ struct GraphicContext { // bool fp64_denorm_preserve = false; // Temporarily disabled. bool attachment_feedback_loop_enabled = false; bool provoking_vertex_last_enabled = false; - bool depth_color_copy_enabled = false; + bool maintenance8_enabled = false; bool supports_block_texel_view = false; bool mesh_shader_enabled = false; vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {}; diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index d56b629b..e066e0ac 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -148,7 +148,7 @@ void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash, } bool ValidateShaderSpirv(const char* label, uint64_t shader_hash, - const std::vector& spirv) { + const std::vector& spirv, bool maintenance8_enabled) { if (!Config::ShaderValidationEnabled()) { return true; } @@ -160,7 +160,9 @@ bool ValidateShaderSpirv(const char* label, uint64_t shader_hash, static_cast(position.column), static_cast(position.index), message); }); - if (tools.Validate(spirv)) { + spvtools::ValidatorOptions validator_options; + validator_options.SetAllowOffsetTextureOperand(maintenance8_enabled); + if (tools.Validate(spirv.data(), spirv.size(), validator_options)) { return true; } spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2); @@ -296,14 +298,15 @@ struct PipelineCache::ProgramCache { std::span function_code) { auto result = ShaderRecompiler::CompileProgram(std::move(translated), options, specialization, push_data_start_dword); - if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) { + if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv, + options.maintenance8_enabled)) { DumpShaderSpirv(stage_name, options.shader_hash, result.spirv); EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label, options.shader_hash); } DumpShaderSpirv(stage_name, options.shader_hash, result.spirv); - const auto module = CompileSPV(result.spirv, device); + const auto module = CompileSPV(result.spirv, graphics.device); EXIT_IF(module == nullptr); if (options.dump_ir) { LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label, @@ -384,6 +387,7 @@ struct PipelineCache::ProgramCache { } ShaderRecompiler::CompileOptions options; options.stage = stage; + options.maintenance8_enabled = graphics.maintenance8_enabled; options.shader_hash = params.hash; options.user_data = user_data; options.back_code = params.back_code; @@ -456,26 +460,26 @@ struct PipelineCache::ProgramCache { return permutation.handle; } - explicit ProgramCache(vk::Device device): device(device) { + explicit ProgramCache(GraphicContext& graphics): graphics(graphics) { lookup_key.static_state.reserve(MaxStaticKeyWords); } ~ProgramCache() { for (const auto& [key, entry]: programs) { (void)key; for (const auto& permutation: entry.permutations) { - device.destroyShaderModule(permutation.handle.module, nullptr); + graphics.device.destroyShaderModule(permutation.handle.module, nullptr); } } } std::unordered_map programs; ProgramKey lookup_key; - vk::Device device; + GraphicContext& graphics; uint64_t next_shader_id = 0; }; PipelineCache::PipelineCache(GraphicContext& graphics) - : m_graphics(graphics), m_program_cache(std::make_unique(graphics.device)) { + : m_graphics(graphics), m_program_cache(std::make_unique(graphics)) { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); InitializeDriverCache(); } diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index fb007103..6f762b3a 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -1038,7 +1038,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo& const auto& hw = buffer.GetRegisters(); auto& depth = state.depth_info; auto& color = state.color_info[0]; - EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().depth_color_copy_enabled || + EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled || !Prospero::IsRectList(ucfg.GetPrimType()) || draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 || emit.first_vertex != 0 || emit.first_instance != 0 || diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 0d3fcbe5..b71a18bc 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -553,9 +553,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, graphics.attachment_feedback_loop_enabled = feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout && feedback_dynamic.attachmentFeedbackLoopDynamicState; - graphics.depth_color_copy_enabled = maintenance8_extension && maintenance8.maintenance8; - LOGF("Vulkan depth/color copy support: %s\n", - graphics.depth_color_copy_enabled ? "true" : "false"); + graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8; + LOGF("Vulkan maintenance8 support: %s\n", + graphics.maintenance8_enabled ? "true" : "false"); LOGF("Vulkan depth feedback support: %s\n", graphics.attachment_feedback_loop_enabled ? "true" : "false"); if (graphics.mesh_shader_enabled) { @@ -652,7 +652,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, image_atomic_int64.sparseImageInt64Atomics = VK_FALSE; create_info.pNext = &image_atomic_int64; } - if (graphics.depth_color_copy_enabled) { + if (graphics.maintenance8_enabled) { maintenance8.pNext = const_cast(create_info.pNext); create_info.pNext = &maintenance8; } diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.cpp b/src/graphics/shader/recompiler/ShaderRecompiler.cpp index 2bbda045..3c1aebe0 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.cpp +++ b/src/graphics/shader/recompiler/ShaderRecompiler.cpp @@ -885,7 +885,8 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", GetDumpLabel(options), StageName(ir.stage), ir.shader_hash); - auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword); + auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, + options.maintenance8_enabled); LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64 " elapsed_ms=%" PRIu64 "\n", GetDumpLabel(options), StageName(ir.stage), ir.shader_hash, diff --git a/src/graphics/shader/recompiler/ShaderRecompiler.h b/src/graphics/shader/recompiler/ShaderRecompiler.h index 91b538bd..a8e3b77b 100644 --- a/src/graphics/shader/recompiler/ShaderRecompiler.h +++ b/src/graphics/shader/recompiler/ShaderRecompiler.h @@ -21,6 +21,7 @@ struct CompileOptions { uint64_t shader_hash = 0; bool dump_ir = true; bool early_dump = false; + bool maintenance8_enabled = false; const char* dump_label = nullptr; std::span user_data; std::span back_code; diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp index 9198f776..9ac26195 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.cpp @@ -197,7 +197,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) { } // namespace std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, - uint32_t push_data_start_dword) { + uint32_t push_data_start_dword, bool maintenance8_enabled) { using namespace Emitter; if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex && @@ -215,7 +215,7 @@ std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo inp } IR::ValidateProgram(program, true); program.bindings = {.push_data_start_dword = push_data_start_dword}; - EmitterState state(program, input_info); + EmitterState state(program, input_info, maintenance8_enabled); const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info); state.lane_count = workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u diff --git a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h index 0626148e..94f3c745 100644 --- a/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h +++ b/src/graphics/shader/recompiler/backend/spirv/SpirvEmitter.h @@ -11,7 +11,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv { // Resets and writes backend IDs and descriptor indices; do not emit the same program concurrently. std::vector EmitProgram(IR::Program& program, ShaderStageInputInfo input_info, - uint32_t push_data_start_dword = 0); + uint32_t push_data_start_dword = 0, + bool maintenance8_enabled = false); } // namespace Libs::Graphics::ShaderRecompiler::Spirv diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index d2a19cbd..47f45285 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -333,6 +333,7 @@ uint32_t PackedOffset(ValueEmitContext& ctx, IR::Value value, ImageDimension dim TypeI32Vector(ctx.state, components), std::span(values).first(components)); } + ctx.state.builder.RequireCapability(spv::CapabilityImageGatherExtended); mask |= spv::ImageOperandsOffsetMask; const auto packed = Unary(ctx.state, spv::OpBitcast, TypeI32(ctx.state), ctx.Def(value)); for (uint32_t index = 0; index < components; index++) { @@ -937,8 +938,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) { operand_count = 2; } if (layout.offset != NoImageComponent && - !offset_value.IsImmediate()) { - ctx.Fail(inst, "has a nonconstant sample texel offset"); + !offset_value.IsImmediate() && !state.maintenance8_enabled) { + ctx.Fail(inst, "requires maintenance8 for a dynamic sample texel offset"); } const auto samplers = EmitImageSamplers(ctx, inst); const auto EmitSample = [&](uint32_t resource, uint32_t array_index) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index a8650235..e3611f0d 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -73,9 +73,9 @@ struct ImageDefinition { }; struct EmitterState { - EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_) + EmitterState(IR::Program& program_, ShaderStageInputInfo input_info_, bool maintenance8_enabled_) : builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u), - program(program_), input_info(input_info_) { + program(program_), input_info(input_info_), maintenance8_enabled(maintenance8_enabled_) { if (ShaderWorkgroupInput(program.stage, input_info) != nullptr) { lds_storage_class = spv::StorageClassWorkgroup; } @@ -88,6 +88,7 @@ struct EmitterState { Builder builder; IR::Program& program; ShaderStageInputInfo input_info; + bool maintenance8_enabled; uint32_t void_type = 0; uint32_t bool_type = 0; uint32_t u32_type = 0; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 62e3887c..995ffcc4 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1429,7 +1429,9 @@ void ValidateSpirv(const char *shader_name, const std::vector &spirv) { messages += buffer; }); - if (!tools.Validate(spirv)) { + spvtools::ValidatorOptions options; + options.SetAllowOffsetTextureOperand(true); + if (!tools.Validate(spirv.data(), spirv.size(), options)) { Fail(shader_name, "SPIR-V validation", messages); } } @@ -1648,6 +1650,7 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) { } ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Compute; + options.maintenance8_enabled = true; options.dump_ir = true; auto compute_info = test.compute_info; compute_info.host_subgroup_size = host_subgroup_size; @@ -1717,7 +1720,8 @@ CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) { } result.program.shader_info_complete = false; ShaderRecompiler::IR::CollectShaderInfo(result.program, options.input_info); - result.spirv = ShaderRecompiler::Spirv::EmitProgram(result.program, options.input_info); + result.spirv = ShaderRecompiler::Spirv::EmitProgram( + result.program, options.input_info, 0u, options.maintenance8_enabled); const auto shader_data = result.program.bindings.descriptor_counts[ static_cast(ShaderRecompiler::IR::DescriptorBindingKind::ShaderData)]; Require(test.name, "binding layout", @@ -1825,6 +1829,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { ShaderRecompiler::CompileOptions options; options.stage = ShaderType::Pixel; + options.maintenance8_enabled = true; options.dump_ir = false; options.input_info.pixel = &pixel_info; options.user_data = user_data; @@ -18712,6 +18717,7 @@ private: m_runtime_context.queue_family = m_queue_family; m_runtime_context.queue = m_queue; m_runtime_context.shader_image_int64_atomics_enabled = true; + m_runtime_context.maintenance8_enabled = true; m_runtime_context.attachment_feedback_loop_enabled = m_rasterization_supported; m_runtime_context.provoking_vertex_last_enabled = m_rasterization_supported; const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{ @@ -18859,9 +18865,11 @@ private: available_provoking_vertex.pNext = &available_feedback_dynamic; vk::PhysicalDeviceImageViewMinLodFeaturesEXT available_min_lod{}; available_min_lod.pNext = &available_provoking_vertex; + vk::PhysicalDeviceMaintenance8FeaturesKHR available_maintenance8{}; + available_maintenance8.pNext = &available_min_lod; vk::PhysicalDeviceFeatures2 available_features2{}; available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; - available_features2.pNext = &available_min_lod; + available_features2.pNext = &available_maintenance8; m_physical_device.getFeatures2(&available_features2); Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, @@ -18899,6 +18907,8 @@ private: "nonuniform storage buffer indexing is not supported"); Require("VulkanHarness", "dispatch", available_min_lod.minLod == true, "image view minimum LOD is not supported"); + Require("VulkanHarness", "dispatch", available_maintenance8.maintenance8 == true, + "maintenance8 dynamic image offsets are not supported"); Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true, "vertex layer output is not supported"); m_rasterization_supported = available_features.fillModeNonSolid && @@ -18977,7 +18987,10 @@ private: min_lod.pNext = m_rasterization_supported ? static_cast(&provoking_vertex) : static_cast(&derivatives); - device_info.pNext = &min_lod; + vk::PhysicalDeviceMaintenance8FeaturesKHR maintenance8{}; + maintenance8.maintenance8 = true; + maintenance8.pNext = &min_lod; + device_info.pNext = &maintenance8; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -18994,6 +19007,7 @@ private: VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME, + VK_KHR_MAINTENANCE_8_EXTENSION_NAME, VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME}; if (m_rasterization_supported) { device_extensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); @@ -35228,6 +35242,88 @@ template TestCase ImageSampleOffsetsReadFourNeighbors() { return test; } +void CheckDynamicSampleOffsets(VulkanHarness &vulkan, u32 wave_size) { + using O = ShaderOpcode; + TestCase test; + test.name = wave_size == 32u ? "DynamicSampleOffsets32" : "DynamicSampleOffsets64"; + test.has_compute_info = test.has_user_data = true; + test.compute_info.wave_size = wave_size; + test.compute_info.host_subgroup_size = vulkan.SubgroupSize(); + test.compute_info.threads_num[0] = wave_size; + test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1u; + test.compute_info.thread_ids_num = 1; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float); + test.user_data[2] = 7u | (7u << 14u); + test.user_data[3] |= 1u << 16u; + test.user_data[5] = 1u << 4u; + test.user_data[50] = wave_size * sizeof(u32); + // Read the offset selector from a texture, as the native shader does. + test.code = {EncodeVop2(0x1b, 20, InlineU32(7), 0), + EncodeVop2(0x16, 21, InlineU32(3), 0), + EncodeVop2(0x1b, 21, InlineU32(1), 21), + EncodeMimg0(0x00, 0x1), EncodeMimg1(4, 20), + EncodeVop2(0x1b, 20, InlineU32(3), 4), + EncodeVop2(0x16, 22, InlineU32(2), 4), + EncodeVop2(0x1b, 22, InlineU32(3), 22)}; + for (u32 reg : {20u, 22u}) { + test.code.push_back(EncodeVop2(0x25, reg, InlineU32(63), reg)); + test.code.push_back(EncodeVop2(0x1b, reg, InlineU32(63), reg)); + } + test.code.push_back(EncodeVop2(0x1a, 22, InlineU32(8), 22)); + test.code.push_back(EncodeVop2(0x1c, 20, Vgpr(22), 20)); + AppendVMovLiteral(&test.code, 21, std::bit_cast(0.375f)); + AppendVMovLiteral(&test.code, 22, std::bit_cast(0.375f)); + AppendVMovLiteral(&test.code, 23, std::bit_cast(1.0f)); + test.code.push_back(EncodeMimg0(0x34, 0x1)); // SAMPLE_L_O: offset is applied at mip 1. + test.code.push_back(EncodeMimg1(5, 20)); + AppendStoreVgprAtLaneDwordOffset(&test.code, 5, 0, 0); + AppendEnd(&test.code); + test.opcodes = {O::V_AND_B32, O::V_LSHRREV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32, + O::V_OR_B32, O::V_MOV_B32, O::IMAGE_LOAD, O::IMAGE_SAMPLE, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"ImageGatherExtended", "Lod|Offset", "OpBitFieldSExtract %int"}; + test.forbidden_spirv = {"OpSwitch", "OpImageQuerySizeLod", "OpFDiv"}; + test.initial.assign(wave_size, 0xdeadbeefu); + for (u32 lane = 0; lane < wave_size; ++lane) + test.expected.push_back(std::bit_cast(float(100u + (lane & 15u)))); + auto compiled = CompileCase(test, vulkan.SubgroupSize()); + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2); + std::string text; + const auto halves = wave_size > vulkan.SubgroupSize() ? 2u : 1u; + Require(test.name, "direct dynamic offset", tools.Disassemble(compiled.spirv, &text) && + CountText(text, "OpImageSampleExplicitLod") == halves && + CountText(text, "OpBitFieldSExtract %int") == 2u * halves, + "dynamic offsets expanded samples or lost signed six-bit components"); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + if (wave_size == 32u) { + Require(test.name, "default feature gate", !ShaderRecompiler::CompileOptions{}.maintenance8_enabled, + "maintenance8 must reflect the enabled device feature"); + ExpectFatal("DynamicSampleOffsetsRequireMaintenance8", [&] { + ShaderRecompiler::Spirv::EmitProgram(compiled.program, {.compute = &test.compute_info}); + }); + } +#endif + auto base = MakeRgbaImage(8, 8); + auto mip = MakeRgbaImage(4, 4); + for (u32 selector = 0; selector < 16u; ++selector) { + SetRgbaPixel(&base, 8, selector & 7u, selector >> 3u, 0x3f800000u | selector, 0, 0, 0); + SetRgbaPixel(&mip, 4, selector & 3u, selector >> 2u, + std::bit_cast(float(100u + selector)), 0, 0, 0); + } + auto texture = vulkan.CreateImageMips(test.name, 8, 8, vk::Format::eR32G32B32A32Sfloat, + vk::ImageUsageFlagBits::eSampled, {base, mip}, 4u, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageType::e2D, vk::ImageViewType::e2D, 1u); + const auto sampler = vulkan.CreateSampler(test.name, 2u); + auto output = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size()); + vulkan.Dispatch(test, compiled, output, nullptr, &texture, nullptr, nullptr, sampler); + CompareWords(test, "image-derived offsets -1..2 at mip 1", test.expected, + vulkan.ReadBuffer(test.name, output, test.expected.size())); + vulkan.DestroyBuffer(&output); + vulkan.Device().destroySampler(sampler); + vulkan.DestroyImage(&texture); + std::printf("[compute] %-32s ok\n", test.name); +} + TestCase ImageSampleA16CompareBiasRdna2AddressOrder() { using O = ShaderOpcode; @@ -43794,6 +43890,8 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--image-sample-offset-only") == 0) { VulkanHarness vulkan; + CheckDynamicSampleOffsets(vulkan, 32u); + CheckDynamicSampleOffsets(vulkan, 64u); RunCase(&vulkan, ImageSampleOffsetsReadFourNeighbors<32>()); RunCase(&vulkan, ImageSampleOffsetsReadFourNeighbors<64>()); RunCase(&vulkan, ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu<32>()); @@ -44463,6 +44561,8 @@ int main(int argc, char **argv) { CheckRuntimeBufferRecords(vulkan); CheckComputeThreadDimensions(vulkan); CheckIndirectImageKeySwitch(vulkan); + CheckDynamicSampleOffsets(vulkan, 32u); + CheckDynamicSampleOffsets(vulkan, 64u); CheckFiniteInlineSamplerPhi(vulkan); CheckWave64WholeWaveResults(); CheckPs5GameExampleImageClearRuntimeShape();