mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
graphics: bind unbound textures as native null descriptors
This commit is contained in:
@@ -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<u32> 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<u32>(0.5f));
|
||||
AppendVMovLiteral(&reader, 24, std::bit_cast<u32>(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<u32> 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<u32>(-1000000.0f));
|
||||
writer.insert(writer.end(), {EncodeMimg0(0x08, 0xf), EncodeMimg1(1, 21)});
|
||||
AppendEnd(&writer);
|
||||
for (const auto *code : {&reader, &writer})
|
||||
ShaderMapUserData(reinterpret_cast<uint64_t>(code->data()),
|
||||
{.type = Prospero::ShaderBinaryType::kCs,
|
||||
.code_size_bytes = static_cast<u32>(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<void *>(base);
|
||||
Require(name, "map", LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, size, 0x3, 0x10, physical, size) == 0 && mapped == reinterpret_cast<void *>(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<uint64_t>(code.data()),
|
||||
.num_thread_x = wave_size, .num_thread_y = 1, .num_thread_z = 1,
|
||||
.wave_size = static_cast<uint8_t>(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<u32>(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<u32> 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();
|
||||
|
||||
Reference in New Issue
Block a user