graphics: bind unbound textures as native null descriptors

This commit is contained in:
nmzik
2026-10-14 17:56:47 +02:00
parent 7c9da0e56f
commit 892e70c8a1
5 changed files with 178 additions and 161 deletions
+17 -52
View File
@@ -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");
-3
View File
@@ -14,7 +14,6 @@
#include <map>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -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<Image> m_slot_images;
ImagePageTable m_image_page_table;
std::unordered_map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::unordered_set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
@@ -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<PreparedBindings* const>
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;