render: clean up texture transfers and compute fills

This commit is contained in:
nmzik
2026-09-05 13:20:52 +04:00
parent e7fe8192bb
commit d5b19850fb
6 changed files with 141 additions and 192 deletions
+40 -49
View File
@@ -130,10 +130,11 @@ bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& request
TextureCache::BindingType TextureCache::UploadBinding(const Image& image) {
if (image.info.IsDepth()) {
return image.info.tile_mode == Prospero::TileMode::kDepth ||
image.info.tile_mode == Prospero::TileMode::kLinear
? BindingType::DepthTarget
: BindingType::Texture;
if (image.info.tile_mode == Prospero::TileMode::kDepth ||
image.info.tile_mode == Prospero::TileMode::kLinear) {
return BindingType::DepthTarget;
}
return BindingType::Texture;
}
if (image.usage.render_target) {
return BindingType::RenderTarget;
@@ -823,7 +824,6 @@ struct TextureCache::TextureTransferPlan {
std::vector<vk::BufferImageCopy> regions;
std::vector<GpuTileInfo> tiles;
uint64_t linear_size = 0;
bool tiled = false;
bool swap_bgra16 = false;
bool valid = false;
};
@@ -846,52 +846,44 @@ TextureCache::TextureTransferPlan
TextureCache::BuildTextureTransfer(const Image& image, BindingType binding,
TransferDirection direction) const {
const auto& info = image.info;
const bool upload = direction == TransferDirection::Upload;
const bool render_target = binding == BindingType::RenderTarget;
const bool video_out = binding == BindingType::VideoOut;
auto format = info.guest_format;
uint32_t layers = info.TransferLayers();
bool volume = info.IsVolume();
bool allow_depth_tile = direction == TransferDirection::Upload;
const char* owner =
direction == TransferDirection::Upload ? "TextureCache" : "TextureCache readback";
bool allow_depth_tile = upload;
const char* owner = "TextureCache readback";
TextureTransferPlan plan;
if (direction == TransferDirection::Upload) {
switch (binding) {
case BindingType::Texture: break;
case BindingType::Storage: owner = "StorageTextureCache"; break;
case BindingType::RenderTarget:
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
info.samples != 1 || image.backing.samples != 1) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block);
allow_depth_tile = true;
plan.swap_bgra16 = info.bgra16;
owner = "RenderTarget";
break;
case BindingType::VideoOut:
if (info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
info.samples != 1 || image.backing.samples != 1 ||
info.metadata.compression != VideoOutCompression::Uncompressed) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
format = info.guest_format;
layers = info.resources.layers;
volume = false;
allow_depth_tile = false;
plan.swap_bgra16 = info.bgra16;
owner = "VideoOut";
break;
case BindingType::DepthTarget: return plan;
plan.swap_bgra16 = info.bgra16 && (!upload || render_target || video_out);
if (render_target) {
format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block);
}
if (video_out) {
allow_depth_tile = false;
} else if (render_target || binding == BindingType::Storage) {
allow_depth_tile = true;
}
if (upload) {
if ((render_target || video_out) &&
(info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
info.samples != 1 || image.backing.samples != 1)) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
} else {
if (binding == BindingType::DepthTarget) {
return plan;
owner = "TextureCache";
if (render_target) {
owner = "RenderTarget";
} else if (binding == BindingType::Storage) {
owner = "StorageTextureCache";
} else if (video_out) {
if (info.metadata.compression != VideoOutCompression::Uncompressed) {
EXIT("TextureCache: invalid color-attachment upload\n");
}
layers = info.resources.layers;
volume = false;
owner = "VideoOut";
}
format = binding == BindingType::RenderTarget
? ImageOps::RenderTargetTransferFormat(info.bytes_per_block)
: info.guest_format;
allow_depth_tile = binding == BindingType::Storage || binding == BindingType::RenderTarget;
plan.swap_bgra16 = info.bgra16;
}
plan.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height,
@@ -903,8 +895,7 @@ TextureCache::BuildTextureTransfer(const Image& image, BindingType binding,
region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;
}
}
plan.tiled = plan.layout.surface.description.tile_mode != Prospero::TileMode::kLinear;
if (plan.tiled) {
if (plan.layout.surface.description.tile_mode != Prospero::TileMode::kLinear) {
if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout,
info.resources.levels, plan.tiles)) {
return plan;
@@ -959,7 +950,7 @@ void TextureCache::UploadImage(Image& image, Buffer& source, uint64_t source_off
info.resources.layers, info.samples);
}
TileManager::Result linear {source.Handle(), source_offset, info.data.size};
if (plan.tiled) {
if (!plan.tiles.empty()) {
linear = m_tiler.Detile(source.Handle(), source_offset, info.data.size,
plan.linear_size, plan.tiles);
}
@@ -1568,7 +1559,7 @@ void TextureCache::DownloadImageData(Image& image, Buffer& destination, uint64_t
auto& texture = plan.texture;
const auto transform = texture.swap_bgra16 ? TileManager::ColorTransform::SwapBgra16
: TileManager::ColorTransform::None;
if (!texture.tiled) {
if (texture.tiles.empty()) {
if (transform == TileManager::ColorTransform::SwapBgra16) {
auto linear = m_tiler.GetScratchBuffer(destination_size);
image.Download(texture.regions, linear.buffer, 0, linear.size);
@@ -1666,7 +1657,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
if (texture.regions.empty()) {
return false;
}
if (texture.tiled) {
if (!texture.tiles.empty()) {
texture.tiles.clear();
if (!TextureBuildGpuTileInfos(copy_size, texture.regions, texture.layout, levels,
texture.tiles)) {
@@ -92,15 +92,6 @@ SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t
}
}
[[nodiscard]] inline uint32_t
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
return swizzle;
}
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
}
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
if (resource.resource_class != ShaderRecompiler::IR::ImageResourceClass::Sampled) {
@@ -335,40 +335,36 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co
}
static void ValidateSampledDepthBinding(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const Image* image,
vk::Format view_format, uint64_t size) {
const bool resource_ok = IsSupportedSampledDepthResource(resource);
const bool descriptor_ok =
image != nullptr && IsSupportedSampledDepthDescriptor(descriptor, *image, resource.r128);
const bool encoding_ok =
image != nullptr && IsSupportedDepthTextureEncoding(descriptor, *image, resource.r128);
const bool format_ok =
image != nullptr && IsSupportedSampledDepthFormat(image->info.pixel_format, view_format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
const ShaderTextureResource& descriptor, const Image& image,
vk::Format view_format, uint64_t size) {
const bool resource_ok = IsSupportedSampledDepthResource(resource);
const bool descriptor_ok = IsSupportedSampledDepthDescriptor(descriptor, image, resource.r128);
const bool encoding_ok = IsSupportedDepthTextureEncoding(descriptor, image, resource.r128);
const bool view_ok =
IsSupportedSampledDepthView(image.info.pixel_format, view_format, descriptor.DstSelXYZW());
if (resource_ok && descriptor_ok && encoding_ok && view_ok) {
return;
}
const auto descriptor_pitch =
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u,
descriptor.TileMode());
EXIT("unsupported sampled depth image: resource=%d descriptor=%d encoding=%d format=%d "
EXIT("unsupported sampled depth image: resource=%d descriptor=%d encoding=%d view=%d "
"class=%u numeric=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d "
"guest_format=%u swizzle=0x%03x image_format=%d view_format=%d image_layers=%u "
"descriptor_type=%u base_array=%u depth=%u descriptor_pitch=%u target_pitch=%u "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
" dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
resource_ok, descriptor_ok, encoding_ok, format_ok,
resource_ok, descriptor_ok, encoding_ok, view_ok,
static_cast<uint32_t>(resource.resource_class),
static_cast<uint32_t>(resource.numeric_class), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written, resource.atomic,
resource.depth_compare, static_cast<uint32_t>(descriptor.Format()),
descriptor.DstSelXYZW(),
image == nullptr ? static_cast<int>(vk::Format::eUndefined)
: static_cast<int>(image->info.pixel_format),
static_cast<int>(view_format), image == nullptr ? 0u : image->info.resources.layers,
descriptor.DstSelXYZW(), static_cast<int>(image.info.pixel_format),
static_cast<int>(view_format), image.info.resources.layers,
static_cast<uint32_t>(descriptor.Type()), descriptor.BaseArray5(), descriptor.Depth(),
descriptor_pitch, image == nullptr ? 0u : image->info.pitch, descriptor.Base40(), size,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
descriptor_pitch, image.info.pitch, descriptor.Base40(), size, descriptor.fields[0],
descriptor.fields[1], descriptor.fields[2], descriptor.fields[3], descriptor.fields[4],
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
@@ -747,10 +743,12 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
ValidateStorageTexture(resource, descriptor, size.size);
}
const auto* depth_format = resource.depth_compare ? FindGuestDepthFormatPolicy(format) : nullptr;
EXIT_NOT_IMPLEMENTED(resource.depth_compare && depth_format == nullptr);
const auto pixel_format =
depth_format != nullptr ? depth_format->depth_attachment_format : surface_format.vk_format;
auto pixel_format = surface_format.vk_format;
if (resource.depth_compare) {
const auto* depth_format = FindGuestDepthFormatPolicy(format);
EXIT_NOT_IMPLEMENTED(depth_format == nullptr);
pixel_format = depth_format->depth_attachment_format;
}
const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt
? vk::Format::eR32Uint
: SrgbStorageViewFormat(pixel_format);
@@ -789,9 +787,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
if (storage) {
EXIT("depth target cannot be bound as a storage image\n");
}
ValidateSampledDepthBinding(resource, descriptor, image, pixel_format, size.size);
(void)SelectSampledDepthView(image->info.pixel_format, pixel_format,
descriptor.DstSelXYZW());
ValidateSampledDepthBinding(resource, descriptor, *image, pixel_format, size.size);
} else if (storage) {
ValidateStorageColorView(image->info.pixel_format, view_format, descriptor.DstSelXYZW());
} else {
+1 -1
View File
@@ -219,7 +219,7 @@ private:
friend struct RenderExecutorTestAccess;
};
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
[[nodiscard]] bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& descriptor,
uint32_t& packed_clear, uint64_t& size);
@@ -39,9 +39,6 @@ namespace Libs::Graphics {
static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
const uint64_t records = descriptor.NumRecords();
const uint64_t stride = descriptor.Stride();
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("compute buffer descriptor footprint overflow\n");
}
return stride == 0 ? records : records * stride;
}
@@ -51,9 +48,8 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
if (i == output_buffer) continue;
const auto source = DecodeNativeDescriptor<ShaderBufferResource>(sources[i]);
const auto bytes = BufferDescriptorSize(source);
if (source.Base48() > UINT64_MAX - bytes ||
(source.Base48() < destination.End() &&
destination.address < source.Base48() + bytes)) return false;
if (source.Base48() < destination.End() && destination.address < source.Base48() + bytes)
return false;
}
return true;
}
@@ -91,30 +87,23 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
return false;
}
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode,
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
uint64_t& resolved_size) {
const auto& program = *input.stage.program;
const auto& resources = input.stage.resources;
const auto& fill = resources.uniform_fill;
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer || fill.words == 0 ||
fill.words > 4 || fill.resource >= resources.buffers.size() ||
resources.buffers.size() != program.info.buffers.size()) {
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
return false;
}
const auto element_size = fill.words * sizeof(uint32_t);
const auto& resource = program.info.buffers[fill.resource];
const auto& raw = resources.buffers[fill.resource];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
const auto element_size = fill.words * sizeof(uint32_t);
const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[fill.resource]);
constexpr std::array formats {
Prospero::BufferFormat::k32UInt, Prospero::BufferFormat::k32_32UInt,
Prospero::BufferFormat::k32_32_32UInt, Prospero::BufferFormat::k32_32_32_32UInt};
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
resource.scalar || descriptor.Stride() != element_size ||
descriptor.Format() != formats[fill.words - 1] || descriptor.SwizzleEnabled() ||
descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
if (descriptor.Stride() != element_size || descriptor.Format() != formats[fill.words - 1] ||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
descriptor.Base48() == 0) {
return false;
}
@@ -128,7 +117,6 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
: static_cast<uint64_t>(group_x) * input.threads_num[0];
const auto size = BufferDescriptorSize(descriptor);
if (invocations != descriptor.NumRecords() || size == 0 || size > UINT32_MAX ||
descriptor.Base48() > UINT64_MAX - size ||
(input.dispatch_thread_dimensions &&
(group_x % input.threads_num[0] != 0 || input.dispatch_threads_num[0] != group_x ||
input.dispatch_threads_num[1] != 1 || input.dispatch_threads_num[2] != 1))) {
@@ -150,23 +138,17 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
const auto& fill = resources.uniform_fill;
auto& cache = command.GetContext().GetTextureCache();
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
if (mode != 0x41u || input.dispatch_thread_dimensions || fill.words != 1 || fill.value > 255 ||
resources.images.size() != 1 || program.info.images.size() != 1 || fill.resource != 0 ||
resources.images[0].dword_count != 8 || input.threads_num[2] != 1 ||
fill.group_stride[2] != 1) return false;
if (mode != 0x41u || input.dispatch_thread_dimensions || fill.value > 255 ||
input.threads_num[2] != 1)
return false;
const auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(resources.images[0]);
const auto& resource = program.info.images[0];
if (descriptor.IsNull() || descriptor.Format() != Prospero::BufferFormat::k8UInt ||
descriptor.Type() != Prospero::ImageType::kColor2DArray ||
descriptor.MetaCompress() || descriptor.WriteCompress() ||
descriptor.BaseLevel() > descriptor.LastLevel() ||
descriptor.Type() != Prospero::ImageType::kColor2DArray || descriptor.MetaCompress() ||
descriptor.WriteCompress() || descriptor.BaseLevel() > descriptor.LastLevel() ||
descriptor.BaseLevel() > descriptor.MaxMip() ||
descriptor.BaseArray5() > descriptor.Depth() ||
descriptor.DstSelX() != 4 || (resource.shader_swizzle & 7u) != 4 ||
resource.numeric_class != Prospero::TextureNumericClass::Uint ||
resource.dimension != ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
resource.read || resource.atomic || !resource.written ||
resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::None) return false;
descriptor.BaseArray5() > descriptor.Depth() || descriptor.DstSelX() != 4)
return false;
const std::array extents {
std::max(1u, (descriptor.Width5() + 1u) >> descriptor.BaseLevel()),
std::max(1u, (descriptor.Height5() + 1u) >> descriptor.BaseLevel()),
@@ -202,7 +184,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
ShaderBufferResource descriptor;
uint32_t packed_clear = 0;
uint64_t size = 0;
if (!ResolveComputeImageClear(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
if (!ResolveComputeBufferFill(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
size)) {
return false;
}
@@ -219,10 +201,7 @@ bool RenderExecutor::TryConsumeComputeImageClear(const ShaderComputeInputInfo& i
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
descriptor.Base48(), size, packed_clear);
}
if (registered_metadata) {
return true;
}
return false;
return registered_metadata;
}
static std::atomic<uint32_t> logged_clears {0};
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
+60 -68
View File
@@ -5398,11 +5398,15 @@ public:
const auto count = copy.imageExtent.width * copy.imageExtent.height;
const auto *bytes = stencil_bytes + copy.bufferOffset;
const bool depth = sub.aspectMask == vk::ImageAspectFlagBits::eDepth;
uint32_t expected = 0x91u;
if (depth) {
expected = std::bit_cast<uint32_t>(0.75f);
} else if (sub.mipLevel == 1 && sub.baseArrayLayer == 1) {
expected = 0x35u;
}
for (uint32_t pixel = 0; pixel < count; pixel++) {
uint32_t actual = bytes[pixel];
if (depth) std::memcpy(&actual, bytes + pixel * 4, 4);
const uint32_t expected = depth ? std::bit_cast<uint32_t>(0.75f)
: sub.mipLevel == 1 && sub.baseArrayLayer == 1 ? 0x35u : 0x91u;
Require(name, "stencil clear preserves depth and neighbors", actual == expected,
"selected stencil clear changed depth, another mip, or another layer");
}
@@ -22760,29 +22764,18 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
auto result = ShaderRecompiler::CompileProgram(
std::move(translated), options, specialization);
ValidateSpirv("Ps5GameExampleImageClear", result.spirv);
return CompiledShader{std::move(result.spirv), std::move(result.program),
std::move(resources), {}};
return resources;
};
const auto code = MakeCode();
auto positive = Compile(code);
ShaderRecompiler::IR::CompiledShaderInfo program{};
program.stage = positive.program.stage;
program.shader_hash = positive.program.shader_hash;
program.wave_size = positive.program.wave_size;
program.user_data_base = positive.program.user_data_base;
program.user_data_count = positive.program.user_data_count;
program.scratch_dwords = positive.program.scratch_dwords;
program.info = std::move(positive.program.info);
program.bindings = std::move(positive.program.bindings);
compute.stage.program = &program;
compute.stage.resources = positive.resources;
compute.stage.resources = positive;
ShaderBufferResource descriptor{};
u32 packed_clear = 0;
uint64_t size = 0;
Require("Ps5GameExampleImageClear", "runtime shape",
ResolveComputeImageClear(compute, 64, 1, 1, 0x61u, descriptor,
ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
packed_clear, size) &&
descriptor.Base48() == 0x10000u && size == 64u * 16u &&
packed_clear == 0xff000000u,
@@ -22790,22 +22783,22 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
auto wrong_index = code;
wrong_index[0] = (wrong_index[0] & ~(0x3ffu << 16u)) | (0x347u << 16u);
compute.stage.resources = Compile(wrong_index).resources;
compute.stage.resources = Compile(wrong_index);
Require("Ps5GameExampleImageClear", "add-before-shift address",
!ResolveComputeImageClear(compute, 64, 1, 1, 0x61u, descriptor,
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
packed_clear, size),
"V_ADD_LSHL was mistaken for V_LSHL_ADD contiguous coverage");
user_data[7] ^= 1u;
compute.stage.resources = Compile(code).resources;
compute.stage.resources = Compile(code);
Require("Ps5GameExampleImageClear", "non-repeated clear",
!ResolveComputeImageClear(compute, 64, 1, 1, 0x61u, descriptor,
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
packed_clear, size),
"non-uniform uint4 data was replaced with a color clear");
compute.stage.resources = positive.resources;
compute.stage.resources = positive;
compute.dispatch_threads_num[0] = 32;
Require("Ps5GameExampleImageClear", "partial dispatch",
!ResolveComputeImageClear(compute, 32, 1, 1, 0x61u, descriptor,
!ResolveComputeBufferFill(compute, 32, 1, 1, 0x61u, descriptor,
packed_clear, size),
"partial buffer coverage was classified as a complete clear");
@@ -22821,46 +22814,43 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
scalar_code.push_back(EncodeMubuf1(1, 0, 0));
AppendEnd(&scalar_code);
auto scalar = Compile(scalar_code);
program.info = scalar.program.info;
compute.stage.resources = scalar.resources;
compute.stage.resources = scalar;
Require("Ps5GameExampleImageClear", "inline scalar fill",
ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size) &&
descriptor.Base48() == 0x200000u && size == 256u &&
packed_clear == 0u,
"GTA3's actual scalar store was not recognized from IR");
Require("Ps5GameExampleImageClear", "excess dispatch",
!ResolveComputeImageClear(compute, 2, 1, 1, 0x41u, descriptor,
!ResolveComputeBufferFill(compute, 2, 1, 1, 0x41u, descriptor,
packed_clear, size),
"excess invocation coverage was accepted as a fill");
auto alias = scalar.resources.buffers.front();
auto alias = scalar.buffers.front();
compute.stage.resources.buffers.push_back(alias);
program.info.buffers.push_back({.read = true, .scalar = true});
Require("Ps5GameExampleImageClear", "aliased scalar input",
!ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size),
"a scalar read aliasing the destination was accepted as uniform");
program.info = scalar.program.info;
compute.stage.resources = scalar.resources;
compute.stage.resources = scalar;
compute.stage.resources.buffers[0].dwords[3] =
(static_cast<u32>(Prospero::BufferFormat::k32Float) << 12u) | 0x204u;
Require("Ps5GameExampleImageClear", "wrong store format",
!ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size),
"a format that changes the stored value was accepted as a uint fill");
// A body change preserving every descriptor and dispatch field must change
// the result.
scalar_code[2] = EncodeVop1(0x01u, 1, InlineU32(7));
compute.stage.resources = Compile(scalar_code).resources;
compute.stage.resources = Compile(scalar_code);
Require("Ps5GameExampleImageClear", "changed store value",
ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size) &&
packed_clear == 7u,
"clear recognition ignored the shader's actual stored value");
scalar_code[2] = EncodeVop1(0x01u, 1, Vgpr(0));
compute.stage.resources = Compile(scalar_code).resources;
compute.stage.resources = Compile(scalar_code);
Require("Ps5GameExampleImageClear", "varying store value",
!ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size),
"a varying store with identical resources was treated as a fill");
@@ -22880,17 +22870,16 @@ void CheckPs5GameExampleImageClearRuntimeShape() {
AppendEnd(&scalar_code);
clean_scalar = true;
auto loaded = Compile(scalar_code);
program.info = loaded.program.info;
compute.stage.resources = loaded.resources;
compute.stage.resources = loaded;
Require("Ps5GameExampleImageClear", "scalar-loaded fill",
ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size) &&
packed_clear == scalar_clear && descriptor.Base48() == 0x200000u,
"GTA3's scalar input was confused with its destination");
clean_scalar = false;
compute.stage.resources = Compile(scalar_code).resources;
compute.stage.resources = Compile(scalar_code);
Require("Ps5GameExampleImageClear", "unavailable clean scalar",
!ResolveComputeImageClear(compute, 1, 1, 1, 0x41u, descriptor,
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
packed_clear, size),
"a scalar without a clean reader was replaced by a fill");
std::printf("[host] %-32s ok\n", "Ps5GameExampleImageClear");
@@ -23178,12 +23167,6 @@ void CheckRenderTargetFormatContract() {
(void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
vk::Format::eR8G8B8A8Unorm,
DstSel(7, 6, 5, 3));
} else if (std::strcmp(kind, "sampled-depth-format") == 0) {
(void)SelectSampledDepthView(vk::Format::eD24UnormS8Uint,
vk::Format::eR8Unorm, DstSel(4, 4, 4, 4));
} else if (std::strcmp(kind, "sampled-depth-swizzle") == 0) {
(void)SelectSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat, DstSel(4, 5, 6, 7));
} else if (std::strcmp(kind, "storage-incompatible-format") == 0) {
ValidateStorageColorView(vk::Format::eR8G8B8A8Srgb,
vk::Format::eR16G16B16A16Unorm,
@@ -23397,34 +23380,44 @@ void CheckSampledColorViews() {
vk::Format::eR8G8B8A8Unorm,
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7),
"compatible integer render-target sampled view was rejected");
Require("SampledColorViews", "invalid depth format",
!IsSupportedSampledDepthView(vk::Format::eD24UnormS8Uint,
vk::Format::eR8Unorm,
DstSel(4, 4, 4, 4)),
"incompatible sampled depth format was accepted");
Require("SampledColorViews", "invalid depth swizzle",
!IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat,
DstSel(4, 5, 6, 7)),
"incompatible sampled depth swizzle was accepted");
Require("SampledColorViews", "D32 depth target",
SelectSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat,
DstSel(4, 4, 4, 4)) == DstSel(4, 4, 4, 4),
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat,
DstSel(4, 4, 4, 4)),
"D32 depth target did not select its depth-aspect view");
Require("SampledColorViews", "D16 R000 depth target",
SelectSampledDepthView(vk::Format::eD16Unorm, vk::Format::eR16Unorm,
DstSel(4, 0, 0, 0)) == DstSel(4, 0, 0, 0),
IsSupportedSampledDepthView(
vk::Format::eD16Unorm, vk::Format::eR16Unorm, DstSel(4, 0, 0, 0)),
"D16 depth target did not select its R000 depth-aspect view");
Require("SampledColorViews", "D16S8 R001 depth target",
SelectSampledDepthView(vk::Format::eD16UnormS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)) == DstSel(4, 0, 0, 1),
IsSupportedSampledDepthView(vk::Format::eD16UnormS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)),
"D16S8 depth target did not select its R001 depth-aspect view");
Require("SampledColorViews", "promoted D24S8 R001 depth target",
SelectSampledDepthView(vk::Format::eD24UnormS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)) == DstSel(4, 0, 0, 1),
IsSupportedSampledDepthView(vk::Format::eD24UnormS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)),
"D24S8 host fallback did not preserve the guest R16 depth view");
Require("SampledColorViews", "promoted D32S8 R001 depth target",
SelectSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)) == DstSel(4, 0, 0, 1),
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR16Unorm,
DstSel(4, 0, 0, 1)),
"D32S8 host fallback did not preserve the guest R16 depth view");
Require("SampledColorViews", "D32S8 R001 depth target",
SelectSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat,
DstSel(4, 0, 0, 1)) == DstSel(4, 0, 0, 1),
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
vk::Format::eR32Sfloat,
DstSel(4, 0, 0, 1)),
"D32S8 depth target did not select its R001 depth-aspect view");
ShaderRecompiler::IR::ImageResource storage_resource{};
storage_resource.resource_class =
@@ -23470,10 +23463,9 @@ void CheckSampledColorViews() {
"GetModuleFileName failed");
for (const char *kind :
{"sampled-invalid-selector", "sampled-incompatible-format",
"sampled-invalid-high", "sampled-depth-format", "sampled-depth-swizzle",
"storage-incompatible-format", "storage-kind", "storage-no-write",
"storage-nonuint-atomic", "storage-compare", "storage-dimension",
"volume-mip-count", "volume-slice-range"}) {
"sampled-invalid-high", "storage-incompatible-format", "storage-kind",
"storage-no-write", "storage-nonuint-atomic", "storage-compare",
"storage-dimension", "volume-mip-count", "volume-slice-range"}) {
std::string command =
std::string("\"") + path + "\" --image-view-death " + kind;
std::vector<char> mutable_command(command.begin(), command.end());