mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
Emit coordinates for each indirect image candidate
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
This commit is contained in:
@@ -63,10 +63,10 @@ uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||
}
|
||||
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
ImageSampleLayout layout;
|
||||
uint32_t cursor = 0;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
const auto& info = ImageDimensionInfoFor(mem.image_dimension);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
|
||||
@@ -104,8 +104,7 @@ uint32_t CubeLayer(EmitterState& state, uint32_t value) {
|
||||
}
|
||||
|
||||
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t first, uint32_t components) {
|
||||
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
|
||||
uint32_t first, uint32_t components, bool cube = false) {
|
||||
auto x = AddressF32(ctx, mem, address, first);
|
||||
if (components == 1u) return x;
|
||||
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
|
||||
@@ -576,7 +575,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components));
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -637,7 +637,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto layout = Layout(mem, dimension);
|
||||
const auto layout = Layout(mem);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
@@ -645,9 +645,9 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "uses depth comparison with a packed integer image");
|
||||
return;
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto coord = CoordF32(ctx, mem, *address, layout.coord,
|
||||
dimension_info.coordinate_components, image.cube);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
@@ -752,6 +752,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
const auto EmitSample = [&](uint32_t resource) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
|
||||
@@ -453,6 +453,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
return SpecializationFail("indirect image specialization has no typed candidate");
|
||||
}
|
||||
const auto& image_class = specialization.images[exemplar];
|
||||
const auto is_2d = [](Decoder::ImageDimension dimension) {
|
||||
return dimension == Decoder::ImageDimension::Dim2D ||
|
||||
dimension == Decoder::ImageDimension::Dim2DArray;
|
||||
};
|
||||
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
|
||||
auto& image = specialization.images[candidate];
|
||||
if (image.indirect_root != root_index) {
|
||||
@@ -466,12 +470,13 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
image.shader_swizzle = image_class.shader_swizzle;
|
||||
image.cube = image_class.cube;
|
||||
}
|
||||
const bool same_coordinates = image.dimension == image_class.dimension &&
|
||||
image.cube == image_class.cube;
|
||||
if (image.numeric_class != image_class.numeric_class ||
|
||||
image.dimension != image_class.dimension ||
|
||||
(!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension))) ||
|
||||
image.mip_count != image_class.mip_count ||
|
||||
image.conversion_format != image_class.conversion_format ||
|
||||
image.shader_swizzle != image_class.shader_swizzle ||
|
||||
image.cube != image_class.cube) {
|
||||
image.shader_swizzle != image_class.shader_swizzle) {
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
program.info.images[root_index].first_use_pc));
|
||||
|
||||
@@ -352,6 +352,40 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
dynamic_snapshot.images.size() == 2 &&
|
||||
dynamic_specialization.images.size() == 2,
|
||||
"dynamic indirect image table did not materialize");
|
||||
const auto second_image = (0x2020u - memory.base) / 4u;
|
||||
memory.words[second_image + 1u] |= 3u << 30u;
|
||||
memory.words[second_image + 2u] = 3u << 14u;
|
||||
memory.words[second_image + 3u] =
|
||||
Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kCube) << 28u);
|
||||
memory.words[second_image + 4u] = 11u;
|
||||
ResourceSnapshot mixed_snapshot;
|
||||
ResourceSpecialization mixed_specialization;
|
||||
Check(MaterializeResources(resource_plan, runtime, mixed_snapshot,
|
||||
mixed_specialization) &&
|
||||
mixed_snapshot.images.size() == 2 &&
|
||||
mixed_specialization.images.size() == 2 &&
|
||||
mixed_specialization.images[0].dimension ==
|
||||
Decoder::ImageDimension::Dim2D &&
|
||||
!mixed_specialization.images[0].cube &&
|
||||
mixed_specialization.images[1].dimension ==
|
||||
Decoder::ImageDimension::Dim2DArray &&
|
||||
mixed_specialization.images[1].cube &&
|
||||
std::equal(mixed_snapshot.images[1].dwords.begin(),
|
||||
mixed_snapshot.images[1].dwords.end(),
|
||||
memory.words.begin() + second_image),
|
||||
"mixed 2D and cube candidates were rejected or discarded");
|
||||
memory.words[second_image + 1u] =
|
||||
static_cast<uint32_t>(
|
||||
Libs::Graphics::Prospero::BufferFormat::k32_32_32_32UInt)
|
||||
<< 20u |
|
||||
(3u << 30u);
|
||||
Check(!MaterializeResources(resource_plan, runtime, mixed_snapshot,
|
||||
mixed_specialization),
|
||||
"indirect images with different numeric classes were accepted");
|
||||
for (const auto dword : {1u, 2u, 3u, 4u}) {
|
||||
memory.words[second_image + dword] = image_descriptor[dword];
|
||||
}
|
||||
ApplyResourceSpecialization(fixture->program, dynamic_specialization);
|
||||
Check(fixture->program.info.images.size() == 2 &&
|
||||
fixture->program.info.images[0].indirect_root == 0 &&
|
||||
|
||||
@@ -26174,6 +26174,48 @@ TestCase ImageSampleAndGather() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageCubeGradientsPreserveDerivatives() {
|
||||
using O = ShaderOpcode;
|
||||
TestCase test;
|
||||
test.name = "ImageCubeGradientsPreserveDerivatives";
|
||||
for (u32 component = 0; component < 7u; component++) {
|
||||
constexpr std::array values{0.125f, 0.0f, 0.0f, 0.125f,
|
||||
1.375f, 1.625f, 10.0f};
|
||||
AppendVMovLiteral(&test.code, 20u + component,
|
||||
std::bit_cast<u32>(values[component]));
|
||||
}
|
||||
test.code.push_back(EncodeMimg0(0x22, 0x1, 0, false, 3));
|
||||
test.code.push_back(EncodeMimg1(0, 20));
|
||||
AppendStoreVgpr(&test.code, 0, 0);
|
||||
AppendEnd(&test.code);
|
||||
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
|
||||
O::S_ENDPGM};
|
||||
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32_32_32_32Float);
|
||||
test.user_data[1] |= 3u << 30u;
|
||||
test.user_data[2] = 3u << 14u;
|
||||
test.user_data[3] =
|
||||
(static_cast<u32>(Prospero::ImageType::kCube) << 28u) | (2u << 16u);
|
||||
test.user_data[4] = 11u;
|
||||
test.user_data[5] = 2u << 4u;
|
||||
test.user_data[50] = sizeof(u32);
|
||||
test.has_user_data = true;
|
||||
test.sampled_image_view_type = vk::ImageViewType::e2DArray;
|
||||
test.sampled_image_layers = 12;
|
||||
for (u32 mip = 0; mip < 3u; mip++) {
|
||||
const auto layer_dwords = (4u >> mip) * (4u >> mip) * 4u;
|
||||
auto &pixels = test.sampled_image_rgba_mips.emplace_back(layer_dwords * 12u);
|
||||
for (u32 layer = 0; layer < 12u; layer++) {
|
||||
std::fill_n(pixels.begin() + layer * layer_dwords, layer_dwords,
|
||||
std::bit_cast<u32>(static_cast<float>(1u + layer + 100u * mip)));
|
||||
}
|
||||
}
|
||||
// Guest face 10 is host layer 8. Unmodified gradients select mip 0;
|
||||
// applying the cube coordinate bias to gradients instead selects mip 2.
|
||||
test.expected = {std::bit_cast<u32>(9.0f)};
|
||||
test.required_spirv = {"OpImageSampleExplicitLod", "Grad"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase ImageGatherLodApproximatesLevelZero() {
|
||||
using O = ShaderOpcode;
|
||||
std::vector<u32> code;
|
||||
@@ -26549,6 +26591,63 @@ void CheckIndirectImageKeySwitch() {
|
||||
CountText(text, "OpImageSampleExplicitLod") == 2 &&
|
||||
CountText(text, "OpIEqual") == 11,
|
||||
"dynamic image key did not use a compact two-sample switch");
|
||||
|
||||
program.memory_info[0].image_dimension =
|
||||
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
program.memory_info[0].image_address_components = 4;
|
||||
for (u32 component = 0; component < 4u; component++) {
|
||||
constexpr std::array values{1.375f, 1.625f, 10.0f, 2.0f};
|
||||
address.SetArg(component, Value(std::bit_cast<u32>(values[component])));
|
||||
}
|
||||
root.indirect_resources = {0u, 1u, 2u};
|
||||
program.info.images = {root, candidate, candidate};
|
||||
for (const bool cube_first : {true, false}) {
|
||||
for (u32 resource = 0; resource < 3u; resource++) {
|
||||
auto &image_resource = program.info.images[resource];
|
||||
image_resource.cube = resource == (cube_first ? 0u : 1u);
|
||||
image_resource.dimension =
|
||||
resource == (cube_first ? 1u : 0u)
|
||||
? ShaderRecompiler::Decoder::ImageDimension::Dim2D
|
||||
: ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
}
|
||||
program.binding_layout_complete = false;
|
||||
AllocateBindings(program);
|
||||
spirv = ShaderRecompiler::Spirv::EmitProgram(program, {.compute = &compute});
|
||||
ValidateSpirv(name, spirv);
|
||||
Require(name, "mixed SPIR-V disassembly", tools.Disassemble(spirv, &text),
|
||||
"failed to disassemble mixed indirect image shader");
|
||||
Require(name, "mixed key switch",
|
||||
CountText(text, "OpImageSampleExplicitLod") == 3 &&
|
||||
CountText(text, "OpFSub") == 2,
|
||||
"mixed candidates lost a sample or shared cube coordinate conversion");
|
||||
|
||||
std::vector<std::span<const u32>> definitions(spirv[3]);
|
||||
u32 samples = 0;
|
||||
for (size_t offset = 5; offset < spirv.size();) {
|
||||
const auto words = std::span<const u32>(spirv).subspan(offset, spirv[offset] >> 16u);
|
||||
const auto opcode = static_cast<spv::Op>(words[0] & 0xffffu);
|
||||
if (opcode == spv::OpTypeVector) {
|
||||
definitions[words[1]] = words;
|
||||
} else if (opcode == spv::OpCompositeConstruct || opcode == spv::OpBitcast ||
|
||||
opcode == spv::OpConstant) {
|
||||
definitions[words[2]] = words;
|
||||
} else if (opcode == spv::OpImageSampleExplicitLod) {
|
||||
const auto coord = definitions[words[4]];
|
||||
const auto lod = definitions[words[6]];
|
||||
const auto components = samples == (cube_first ? 1u : 0u) ? 2u : 3u;
|
||||
Require(name, "mixed coordinate and LOD layout",
|
||||
!coord.empty() && definitions[coord[1]][3] == components &&
|
||||
words[5] == spv::ImageOperandsLodMask && lod.size() == 4u &&
|
||||
definitions[lod[3]].size() == 4u &&
|
||||
definitions[lod[3]][3] == std::bit_cast<u32>(2.0f),
|
||||
"candidate coordinates changed the instruction's LOD operand");
|
||||
samples++;
|
||||
}
|
||||
offset += words.size();
|
||||
}
|
||||
Require(name, "mixed sample count", samples == 3u,
|
||||
"mixed candidate switch did not retain every image");
|
||||
}
|
||||
}
|
||||
|
||||
TestCase ImageStoreMipSelectsPpsa01340Descriptor() {
|
||||
@@ -27795,6 +27894,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(ImageGetResinfoDmaskWidthHeight);
|
||||
AddCase(ImageGetResinfoDmaskMipLevels);
|
||||
AddCase(ImageSampleAndGather);
|
||||
AddCase(ImageCubeGradientsPreserveDerivatives);
|
||||
AddCase(ImageGatherLodApproximatesLevelZero);
|
||||
AddCase(ImageD16GatherPacksHalfPairs);
|
||||
AddCase(ImageSampleA16SamplerCoordsOnGpu);
|
||||
@@ -32727,6 +32827,13 @@ int main(int argc, char **argv) {
|
||||
RunCase(nullptr, ImageSampleA16CompareBiasRdna2AddressOrder());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) {
|
||||
CheckImageSamplerSpecialization();
|
||||
CheckIndirectImageKeySwitch();
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, ImageCubeGradientsPreserveDerivatives());
|
||||
return 0;
|
||||
}
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) {
|
||||
RunReverseRenderTargetDeathCase();
|
||||
@@ -32762,11 +32869,6 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, ImageStoreBgraUsesInverseSwizzle());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) {
|
||||
CheckImageSamplerSpecialization();
|
||||
CheckIndirectImageKeySwitch();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--storage-mip-host-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
||||
|
||||
Reference in New Issue
Block a user