From 892e70c8a131bc2b4b6642ada5e79c9b3b79b4da Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 14 Oct 2026 17:56:47 +0200 Subject: [PATCH] graphics: bind unbound textures as native null descriptors --- .../host_gpu/renderer/cache/textureCache.cpp | 69 ++------ .../host_gpu/renderer/cache/textureCache.h | 3 - .../renderer/pipeline/descriptors.cpp | 67 ++----- .../presentation/window/vulkanWindow.cpp | 37 ++-- tests/ShaderRecompilerComputeTests.cpp | 163 ++++++++++++++---- 5 files changed, 178 insertions(+), 161 deletions(-) diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.cpp b/src/graphics/host_gpu/renderer/cache/textureCache.cpp index f3fdc38ca..640baa8ff 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/textureCache.cpp @@ -199,9 +199,7 @@ TextureCache::BindingType TextureCache::UploadBinding(const Image& image) { ImageId TextureCache::InsertImage(const ImageInfo& info) { const auto id = m_slot_images.insert(m_graphics, m_scheduler, info); - if (!info.data.Empty()) { - RegisterImage(id); - } + RegisterImage(id); return id; } @@ -408,7 +406,7 @@ void TextureCache::UntrackImageTail(ImageId id) { } void TextureCache::TrackImageDownload(ImageId id, Image& image) { - if (m_readback_linear_images && !image.info.IsTiled() && !image.info.data.Empty()) { + if (m_readback_linear_images && !image.info.IsTiled()) { if (!image.IsGpuModified()) { EXIT("TextureCache: cannot enroll a non-GPU-owned image for download\n"); } @@ -452,27 +450,6 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64 return result; } -ImageId TextureCache::GetNullImage(const ImageDesc& desc) { - const auto format = desc.info.pixel_format; - if (const auto found = m_null_images.find(format); found != m_null_images.end()) { - return found->second; - } - ImageInfo info {}; - info.pixel_format = desc.info.pixel_format; - info.guest_format = desc.info.guest_format; - info.type = Prospero::ImageType::kColor2D; - info.extent = {1, 1, 1}; - info.resources = {1, 1}; - info.pitch = 1; - info.bytes_per_block = std::max(desc.info.bytes_per_block, 1u); - info.samples = 1; - info.tile_mode = Prospero::TileMode::kLinear; - info.mip_layout[0] = {0, info.bytes_per_block, 1, 1}; - const auto id = InsertImage(info); - m_null_images.emplace(format, id); - return id; -} - void TextureCache::ValidateImageDesc(const ImageDesc& desc) const { ImageOps::Validate(desc.info); if (desc.view_info.format == vk::Format::eUndefined || desc.view_info.level_count == 0 || @@ -1026,9 +1003,6 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off void TextureCache::InitializeImage(ImageId id) { auto& image = m_slot_images[id]; - if (image.info.data.Empty()) { - return; - } TrackImage(id); if (image.info.metadata.compression != VideoOutCompression::Uncompressed) { if (image.IsCpuDirty()) { @@ -1215,10 +1189,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) { EXIT("TextureCache: image lookup requires a valid command buffer\n"); } ValidateImageDesc(desc); - if (desc.info.data.Empty()) { - std::scoped_lock lock {m_lock}; - return GetNullImage(desc); - } + EXIT_IF(desc.info.data.Empty()); const auto metadata_base_layer = desc.view_info.base_layer; ImageId result {}; @@ -1370,36 +1341,30 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) { std::scoped_lock lock {m_lock}; auto& image = m_slot_images[id]; TouchImage(image); - if (!image.info.data.Empty()) { - if (!image.registered || image.depth_id || image.binding.needs_rebind) { - EXIT("TextureCache: texture requires rediscovery before final acquisition\n"); - } + if (!image.registered || image.depth_id || image.binding.needs_rebind) { + EXIT("TextureCache: texture requires rediscovery before final acquisition\n"); } if (desc.type == BindingType::Storage) { image.MarkGpuModified(); } - if (!image.info.data.Empty()) { - RefreshImage(id); - if (image.info.HasStencil() && - desc.info.data.address >= image.info.stencil.address && - desc.info.data.End() <= image.info.stencil.End()) { - for (const auto stencil_id: - FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) { - const auto* stencil = m_slot_images.try_get(stencil_id); - if (stencil != nullptr && stencil->depth_id == id && - stencil->info.data == image.info.stencil) { - RefreshImage(stencil_id); - break; - } + RefreshImage(id); + if (image.info.HasStencil() && + desc.info.data.address >= image.info.stencil.address && + desc.info.data.End() <= image.info.stencil.End()) { + for (const auto stencil_id: + FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) { + const auto* stencil = m_slot_images.try_get(stencil_id); + if (stencil != nullptr && stencil->depth_id == id && + stencil->info.data == image.info.stencil) { + RefreshImage(stencil_id); + break; } } } switch (desc.type) { case BindingType::Texture: break; case BindingType::Storage: - if (!image.info.data.Empty()) { - CommitGpuWrite(image); - } + CommitGpuWrite(image); TrackImageDownload(id, image); break; default: EXIT("TextureCache: invalid texture binding\n"); diff --git a/src/graphics/host_gpu/renderer/cache/textureCache.h b/src/graphics/host_gpu/renderer/cache/textureCache.h index 6d9b88649..2a9974a43 100644 --- a/src/graphics/host_gpu/renderer/cache/textureCache.h +++ b/src/graphics/host_gpu/renderer/cache/textureCache.h @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -114,7 +113,6 @@ private: } [[nodiscard]] ImageId InsertImage(const ImageInfo& info); - [[nodiscard]] ImageId GetNullImage(const ImageDesc& desc); void RegisterImage(ImageId id); void UnregisterImage(ImageId id); void DeleteImage(ImageId id); @@ -173,7 +171,6 @@ private: BufferCache& m_buffer_cache; Common::SlotVector m_slot_images; ImagePageTable m_image_page_table; - std::unordered_map m_null_images; Common::LeastRecentlyUsedCache m_lru_cache; std::unordered_set m_download_images; std::map m_surface_metas; diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 1e451c397..17ba542ea 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -76,6 +76,10 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) { } vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t element) { + if (!texture.image_id) { + EXIT_IF(texture.image_view != nullptr || !texture.mip_views.empty()); + return {nullptr, nullptr, vk::ImageLayout::eGeneral}; + } vk::ImageView view = nullptr; if (texture.mip_views.empty()) { if (element == 0u) { @@ -84,7 +88,7 @@ vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture, uint32_t el } else if (element < texture.mip_views.size()) { view = texture.mip_views[element]; } - EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined); + EXIT_IF(view == nullptr || texture.layout == vk::ImageLayout::eUndefined); return {nullptr, view, texture.layout}; } @@ -348,39 +352,6 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource, descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]); } -static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource, - TextureCache::BindingType binding) { - TextureCache::ImageDesc desc {}; - switch (resource.numeric_class) { - case Prospero::TextureNumericClass::Float: - desc.info.guest_format = Prospero::BufferFormat::k32Float; - break; - case Prospero::TextureNumericClass::Uint: - desc.info.guest_format = resource.atomic64 ? Prospero::BufferFormat::k32_32UInt - : Prospero::BufferFormat::k32UInt; - break; - case Prospero::TextureNumericClass::Sint: - desc.info.guest_format = Prospero::BufferFormat::k32SInt; - break; - default: EXIT("null image has unsupported numeric class\n"); - } - desc.info.pixel_format = VulkanFormat(desc.info.guest_format); - desc.info.type = Prospero::ImageType::kColor2D; - desc.info.extent = {1, 1, 1}; - desc.info.resources = {1, 1}; - desc.info.bytes_per_block = Prospero::NumBytesPerElement(desc.info.guest_format); - desc.info.samples = 1; - desc.info.mip_layout[0] = {0, 0, 1, 1}; - desc.view_info.format = resource.atomic64 ? vk::Format::eR64Uint : desc.info.pixel_format; - desc.view_info.type = vk::ImageViewType::e2D; - desc.view_info.aspect = vk::ImageAspectFlagBits::eColor; - desc.view_info.usage = binding == TextureCache::BindingType::Storage - ? vk::ImageUsageFlagBits::eStorage - : vk::ImageUsageFlagBits::eSampled; - desc.type = binding; - return desc; -} - static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& resource, const ShaderTextureResource& descriptor, vk::Format format, const SurfaceFormatInfo& surface_format, bool storage, @@ -516,10 +487,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR auto& texture_cache = m_context.GetTextureCache(); if (descriptor.IsNull()) { - auto desc = NullTextureDesc(resource, storage ? TextureCache::BindingType::Storage - : TextureCache::BindingType::Texture); - const auto id = texture_cache.FindImage(desc); - return {id, nullptr, std::move(desc)}; + return {.layout = vk::ImageLayout::eGeneral}; } const auto address = descriptor.Base40(); @@ -689,10 +657,10 @@ static vk::DescriptorBufferInfo NativeUpload(RenderContext& context, } void RenderExecutor::BindImage(ImageId id, bool storage) { - auto& image = m_context.GetTextureCache().GetImage(id); - if (image.info.data.Empty()) { + if (!id) { return; } + auto& image = m_context.GetTextureCache().GetImage(id); if (image.binding.is_bound) { image.binding.force_general |= image.binding.shader_write != storage; } @@ -845,8 +813,9 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) { auto& texture_cache = m_context.GetTextureCache(); // Acquire each view before a later overlapping descriptor can replace its image. for (uint32_t i = 0; i < program.info.images.size(); i++) { + if (!images[i].image_id) continue; const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id); - if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) || + if (old_image == nullptr || !old_image->registered || old_image->binding.needs_rebind) { if (old_image != nullptr) { old_image->binding = {}; @@ -897,7 +866,7 @@ void RenderExecutor::PrepareGraphicsBindings(std::span for (auto& target: colors) { EXIT_IF(!target.image_id); const auto old_image = cache.m_slot_images.try_get(target.image_id); - if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) || + if (old_image == nullptr || !old_image->registered || old_image->binding.needs_rebind) { if (old_image != nullptr) { old_image->binding = {}; @@ -1007,19 +976,14 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer, } for (uint32_t i = 0; i < program.info.images.size(); i++) { - auto& image = m_context.GetTextureCache().GetImage(descriptors.images[i].image_id); auto& binding = descriptors.images[i]; + if (!binding.image_id) continue; + auto& image = m_context.GetTextureCache().GetImage(binding.image_id); const auto& view = binding.desc.view_info; const ImageSubresourceRange range {view.base_level, view.level_count, view.base_layer, view.layer_count}; const bool storage = binding.desc.type == TextureCache::BindingType::Storage; - if (image.info.data.Empty()) { - image.Transit(vk::ImageLayout::eGeneral, - storage ? vk::AccessFlagBits2::eShaderRead | - vk::AccessFlagBits2::eShaderWrite - : vk::AccessFlagBits2::eShaderRead, - range, vk_buffer); - } else if (image.binding.is_target) { + if (image.binding.is_target) { const auto layout = image.binding.attachment_layout; EXIT_IF(layout == vk::ImageLayout::eUndefined); if (image.info.IsDepth()) { @@ -1120,7 +1084,8 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer, for (uint32_t i = 0; i < program.info.images.size(); ++i) { const auto& resource = program.info.images[i]; const auto& texture = descriptors.images[i]; - EXIT_IF(resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size())); + EXIT_IF(texture.image_id && + resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size())); const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource); const auto start = image_starts[ShaderRecompiler::IR::ImageBindingIndex(kind)] + resource.descriptor_index; diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index b71a18bc8..dc85b8b94 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -216,6 +216,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa vk::PhysicalDeviceVulkan12Features features12 {}; vk::PhysicalDeviceVulkan11Features features11 {}; + vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {}; #if defined(__APPLE__) features12.pNext = &depth_clip_control; #else @@ -223,7 +224,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa fragment_barycentric.pNext = &depth_clip_control; features12.pNext = &fragment_barycentric; #endif - features11.pNext = features12.pNext; + robustness2.pNext = features12.pNext; + features11.pNext = &robustness2; features12.pNext = &features11; features13.pNext = &features12; device_features2.pNext = &features13; @@ -289,6 +291,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa "shaderStorageBufferArrayNonUniformIndexing", required_features12.shaderStorageBufferArrayNonUniformIndexing); check_feature(features13.robustImageAccess, "robustImageAccess"); + check_feature(robustness2.nullDescriptor, "nullDescriptor"); check_feature(features13.dynamicRendering, "dynamicRendering", required_features13.dynamicRendering); check_feature(features13.synchronization2, "synchronization2", @@ -450,14 +453,9 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, vk::PhysicalDeviceVulkan13Features supported_features13 {}; supported_features13.pNext = &supported_features12; - const auto robustness2_ext_enabled = - HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); - vk::PhysicalDeviceRobustness2FeaturesEXT supported_robustness2 {}; - if (robustness2_ext_enabled) { - supported_robustness2.pNext = supported_features13.pNext; - supported_features13.pNext = &supported_robustness2; - } + supported_robustness2.pNext = supported_features13.pNext; + supported_features13.pNext = &supported_robustness2; const bool mesh_extension = HasExtension(device_extensions, VK_EXT_MESH_SHADER_EXTENSION_NAME); vk::PhysicalDeviceMeshShaderFeaturesEXT supported_mesh {}; @@ -611,27 +609,19 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, fragment_barycentric.fragmentShaderBarycentric = VK_TRUE; robustness2.pNext = &fragment_barycentric; #endif - if (robustness2_ext_enabled) { - robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2; - robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2; - robustness2.nullDescriptor = supported_robustness2.nullDescriptor; - } + robustness2.robustBufferAccess2 = supported_robustness2.robustBufferAccess2; + robustness2.robustImageAccess2 = supported_robustness2.robustImageAccess2; + robustness2.nullDescriptor = VK_TRUE; auto features13 = WindowContext::RequiredVulkan13Features(); -#if defined(__APPLE__) - features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) - : static_cast(&features12); -#else - features13.pNext = robustness2_ext_enabled ? static_cast(&robustness2) - : static_cast(&fragment_barycentric); -#endif + features13.pNext = &robustness2; features13.robustImageAccess = supported_features13.robustImageAccess; features13.subgroupSizeControl = graphics.compute_subgroup_size_control_enabled ? VK_TRUE : VK_FALSE; LOGF("Vulkan robustness: robustImageAccess=%s robustImageAccess2=%s\n", features13.robustImageAccess == VK_TRUE ? "true" : "false", - robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false"); + robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false"); vk::DeviceCreateInfo create_info {}; vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features {}; @@ -961,7 +951,7 @@ void WindowContext::CreateVulkan() { std::vector device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, - "VK_KHR_maintenance1"}; + VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, "VK_KHR_maintenance1"}; #if defined(__APPLE__) // MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer @@ -1024,8 +1014,7 @@ void WindowContext::CreateVulkan() { device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); graphic_ctx.memory_budget_ext_enabled = true; } - for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, - VK_KHR_MAINTENANCE_8_EXTENSION_NAME, + for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, VK_EXT_MESH_SHADER_EXTENSION_NAME, diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 80d5aaa25..a71ad5341 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -356,10 +356,6 @@ struct TextureCacheTestAccess { return cache.m_download_images.contains(id); } - static size_t NullImageCount(const TextureCache &cache) { - return cache.m_null_images.size(); - } - static void TrackDownload(TextureCache &cache, ImageId id) { std::lock_guard lock(cache.m_lock); cache.TrackImageDownload(id, cache.m_slot_images[id]); @@ -10376,6 +10372,110 @@ public: return result; } + void CheckNullTextureDescriptors() { + constexpr const char *name = "NullTextureDescriptors"; + constexpr uintptr_t base = 0x0000000204900000ull; + constexpr uint64_t size = 0x10000; + std::vector reader{ + EncodeVop2(0x1a, 20, InlineU32(5), 0), + EncodeVop1(0x01, 21, InlineU32(0)), + EncodeVop1(0x01, 22, InlineU32(0)), + EncodeMimg0(0x00, 0xf), EncodeMimg1(1, 21)}; + AppendVMovLiteral(&reader, 23, std::bit_cast(0.5f)); + AppendVMovLiteral(&reader, 24, std::bit_cast(0.5f)); + reader.insert(reader.end(), { + EncodeMimg0(0x27, 0xf), EncodeMimg1(5, 23, 0, 2), + 0xbf8c3f70u, + EncodeMubuf0(0x1e), EncodeMubuf1(1, 3, 20), + EncodeMubuf0(0x1e, 16), EncodeMubuf1(5, 3, 20)}); + AppendEnd(&reader); + std::vector writer{ + EncodeVopc(0xd2, InlineU32(0), 0), + EncodeVop1(0x01, 21, InlineU32(0)), + EncodeVop1(0x01, 22, InlineU32(0))}; + for (u32 i = 1; i <= 4; ++i) + AppendVMovLiteral(&writer, i, std::bit_cast(-1000000.0f)); + writer.insert(writer.end(), {EncodeMimg0(0x08, 0xf), EncodeMimg1(1, 21)}); + AppendEnd(&writer); + for (const auto *code : {&reader, &writer}) + ShaderMapUserData(reinterpret_cast(code->data()), + {.type = Prospero::ShaderBinaryType::kCs, + .code_size_bytes = static_cast(code->size() * sizeof(u32))}); + + EnsureRuntimeContext(); + int64_t physical = -1; + Require(name, "allocate", LibKernel::Memory::KernelAllocateDirectMemory( + 0, LibKernel::Memory::KernelGetDirectMemorySize(), size, size, 0, &physical) == 0, + "null texture output allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "map", LibKernel::Memory::KernelMapDirectMemory( + &mapped, size, 0x3, 0x10, physical, size) == 0 && mapped == reinterpret_cast(base), + "null texture output mapping failed"); + std::memset(mapped, 0xa5, size); + RenderContext context(m_runtime_context); + context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); + context.GetGpu().SendCommandSync([&] { + CommandProcessor processor(context, 0); + processor.BufferInit(); + context.MapMemory(base, size); + auto &shaders = processor.GetShCtx(); + auto &executor = context.GetRenderExecutor(); + for (u32 wave_size : {32u, 64u}) { + const auto output_address = [&](u32 phase) { + return base + (wave_size == 64 ? 0x8000u : 0u) + 0x100u + phase * 0x2000u; + }; + for (u32 sgpr = 0; sgpr < 16; ++sgpr) + shaders.SetCsUserSgpr(sgpr, 0, HW::UserSgprType::Unknown); + shaders.SetCsUserSgpr(8, 0x92u, HW::UserSgprType::Unknown); + for (u32 phase = 0; phase < 3; ++phase) { + const auto &code = phase == 1 ? writer : reader; + shaders.SetCsShader({.data_addr = reinterpret_cast(code.data()), + .num_thread_x = wave_size, .num_thread_y = 1, .num_thread_z = 1, + .wave_size = static_cast(wave_size), .user_sgpr = 16}); + ShaderBufferResource output{}; + output.UpdateAddress48(output_address(phase)); + output.fields[2] = wave_size * 8u * sizeof(u32); + output.fields[3] = DstSel(4, 5, 6, 7) | + (static_cast(Prospero::BufferFormat::k32UInt) << 12u); + for (u32 i = 0; i < 4; ++i) + shaders.SetCsUserSgpr(12 + i, output.fields[i], HW::UserSgprType::Unknown); + ShaderComputeInputInfo input{}; + (void)context.GetPipelineCache().GetComputeProgram( + shaders.GetCs(), processor.GetCtx().GetShaderRegisters(), input); + PreparedBindings bindings; + executor.PrepareBindings(input.stage, bindings); + executor.RebindImages(bindings); + Require(name, "native null image bindings", !bindings.images.empty() && + std::ranges::all_of(bindings.images, [](const auto &binding) { + return !binding.image_id && binding.image_view == nullptr && + binding.mip_views.empty() && + MakeImageInfo(binding).imageView == nullptr && + binding.layout == vk::ImageLayout::eGeneral; + }), "an all-zero guest descriptor allocated or bound a backing image"); + processor.DispatchDirect(1, 1, 1, 0x41u); + } + for (u32 phase : {0u, 2u}) { + const auto address = output_address(phase); + std::vector actual(wave_size * 8u); + context.GetBufferCache().ReadMemory(address, actual.size() * sizeof(u32)); + Require(name, "null RGBA before and after storage write", + LibKernel::Memory::TryReadBacking(address, actual.data(), actual.size() * sizeof(u32)) && + std::ranges::all_of(actual, [](u32 value) { return value == 0; }), + "wave" + std::to_string(wave_size) + " phase" + std::to_string(phase) + + " returned nonzero load/sample components from an unbound texture"); + } + } + context.UnmapMemory(base, size); + context.GetCommandScheduler().Finish(); + }); + LibKernel::Memory::InstallGpuResources(nullptr); + context.ShutdownGpu(); + Require(name, "release", LibKernel::Memory::KernelCheckedReleaseDirectMemory(physical, size) == 0, + "null texture output release failed"); + std::printf("[gpu] %-32s ok\n", name); + } + void CheckScalarLeafCall() { constexpr const char* name = "ScalarLeafCall"; constexpr uintptr_t base = 0x0000000204800000ull; @@ -13007,6 +13107,8 @@ public: } ShaderRecompiler::IR::ApplyResourceSpecialization( null_program, null_specialization); + null_program.info.images.back().mip_mode = ShaderRecompiler::IR::ImageMipMode::Dynamic; + null_program.info.images.back().mip_count = 3; set_image_bindings(null_program); ShaderRecompiler::IR::CompiledShaderInfo null_info{}; null_info.stage = null_program.stage; @@ -13019,38 +13121,27 @@ public: Require(name, "null descriptor count", null_bindings.images.size() == 3, "null descriptor preparation lost an image binding"); - const auto null_image_id = null_bindings.images[0].image_id; - const auto &null_image = texture_cache.GetImage(null_image_id); - Require(name, "one null image per format", - null_bindings.images[1].image_id == null_image_id && - null_bindings.images[2].image_id == null_image_id && - TextureCacheTestAccess::NullImageCount(texture_cache) == 1, - "null view dimension or usage created another image allocation"); - Require(name, "null descriptors share final cache acquisition", - null_image.info.data.Empty() && !null_image.binding.is_bound && - !null_image.binding.is_target && - !null_image.binding.needs_rebind && - null_image.usage.storage && - null_image.IsGpuModified() && - !TextureCacheTestAccess::PendingDownload(texture_cache, - null_image_id), - "the shared null image did not preserve texture/storage " - "acquisition without guest readback or RenderExecutor ownership"); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( executor, scheduler.Current(), null_bindings)); - Require(name, "null descriptor general layouts", + Require(name, "null descriptors have no cache image or native view", std::ranges::all_of(null_bindings.images, [](const auto &binding) { const auto info = MakeImageInfo(binding); - return binding.layout == + return !binding.image_id && + binding.image_view == nullptr && + binding.mip_views.empty() && + binding.layout == vk::ImageLayout::eGeneral && - info.imageView == - binding.image_view && + info.imageView == nullptr && info.imageLayout == binding.layout; - }) && - null_image.backing.state.layout == vk::ImageLayout::eGeneral, - "shared sampled/storage null descriptors did not retain " - "the general layout through descriptor generation"); + }), + "sampled/storage null descriptors acquired a backing image or lost their general layout"); + for (u32 mip = 0; mip < 3; ++mip) { + const auto info = MakeImageInfo(null_bindings.images.back(), mip); + Require(name, "null storage mip descriptors", + info.imageView == nullptr && info.imageLayout == vk::ImageLayout::eGeneral, + "a null storage mip acquired a backing image"); + } constexpr uint64_t target_mip_size = 0x10000; const auto make_target_desc = [&](uint64_t address, uint64_t size, @@ -19244,10 +19335,14 @@ private: available_min_lod.pNext = &available_provoking_vertex; vk::PhysicalDeviceMaintenance8FeaturesKHR available_maintenance8{}; available_maintenance8.pNext = &available_min_lod; + vk::PhysicalDeviceRobustness2FeaturesEXT available_robustness2{}; + available_robustness2.pNext = &available_maintenance8; vk::PhysicalDeviceFeatures2 available_features2{}; available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; - available_features2.pNext = &available_maintenance8; + available_features2.pNext = &available_robustness2; m_physical_device.getFeatures2(&available_features2); + Require("VulkanHarness", "dispatch", available_robustness2.nullDescriptor == true, + "null descriptors are not supported"); Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, "shaderStorageImageWriteWithoutFormat is not supported"); @@ -19367,7 +19462,10 @@ private: vk::PhysicalDeviceMaintenance8FeaturesKHR maintenance8{}; maintenance8.maintenance8 = true; maintenance8.pNext = &min_lod; - device_info.pNext = &maintenance8; + vk::PhysicalDeviceRobustness2FeaturesEXT robustness2{}; + robustness2.nullDescriptor = true; + robustness2.pNext = &maintenance8; + device_info.pNext = &robustness2; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -19384,6 +19482,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_EXT_ROBUSTNESS_2_EXTENSION_NAME, VK_KHR_MAINTENANCE_8_EXTENSION_NAME, VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME}; if (m_rasterization_supported) { @@ -46678,6 +46777,7 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) { CheckImageSamplerSpecialization(); VulkanHarness vulkan; + vulkan.CheckNullTextureDescriptors(); CheckCountedRowImageSelector(vulkan); CheckPointerImageTable(vulkan); CheckNestedPointerImageTable(vulkan); @@ -46872,6 +46972,7 @@ int main(int argc, char **argv) { vulkan.CheckGpuTilerCpuParity(); vulkan.CheckNativeIndirectDispatch(); CheckComputeLdsLimit(vulkan); + vulkan.CheckNullTextureDescriptors(); vulkan.CheckBdaPageTableUploads(); vulkan.CheckLargeSparseImageUpload(); vulkan.CheckUnifiedTextureCacheFlow();