implement dynamic

This commit is contained in:
nmzik
2026-08-02 10:50:28 +02:00
parent 59b8fad341
commit 35a89d616a
16 changed files with 470 additions and 117 deletions
@@ -237,8 +237,14 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
if (!ImageBinding(program.info.images[i], kind)) {
return Fail(error, "native shader plan has an invalid image class");
}
const auto bindings = program.info.images[i].NumBindings();
if (bindings == 0 || bindings > IR::ImageResource::MaxMipLevels) {
return Fail(error, "native shader plan has an invalid image descriptor count");
}
present[static_cast<size_t>(kind)] = true;
expected[static_cast<size_t>(kind)].push_back(i);
for (uint32_t binding = 0; binding < bindings; binding++) {
expected[static_cast<size_t>(kind)].push_back(i);
}
}
if (!program.info.samplers.empty()) {
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
@@ -316,6 +322,11 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
inst.memory.image_dimension)) {
return Fail(error, "image instruction has an invalid dense resource");
}
if (inst.op == IR::Opcode::ImageStore &&
((program.info.images[inst.memory.resource].mip_mode ==
IR::ImageMipMode::DynamicStorage) != inst.memory.image_has_mip)) {
return Fail(error, "storage image mip mode does not match the instruction");
}
const bool address =
inst.op == IR::Opcode::SLoadDword || memory == IR::ResourceKind::Flat ||
memory == IR::ResourceKind::Global || memory == IR::ResourceKind::Scratch;
@@ -560,12 +560,21 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name) {
return DescriptorElementPointerId(state, result_ptr_type, variable_id,
ConstantU32(state, array_index), kind, resource,
variable_name);
}
uint32_t DescriptorElementPointerId(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index_id,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name) {
if (variable_id == 0) {
ExitDescriptorBindingFailure(state, kind, resource, variable_name);
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, result_ptr_type, pointer, variable_id, ConstantU32(state, array_index)});
{OpAccessChain, result_ptr_type, pointer, variable_id, array_index_id});
return pointer;
}
@@ -33,13 +33,13 @@ uint32_t ConstantImageGatherHorizontalOffsets(EmitterState& state, ImageViewKind
}
uint32_t LoadStorageImageDescriptorAtIndex(EmitterState& state, uint32_t resource,
uint32_t array_index, bool uint_image,
uint32_t array_index_id, bool uint_image,
ImageViewKind view) {
const auto kind = StorageBindingKind(uint_image, view);
const auto& descriptors = state.storage_images[StorageImageIndex(uint_image, view)];
const auto pointer =
DescriptorElementPointer(state, descriptors.pointer_type, descriptors.variable, array_index,
kind, resource, "storage image descriptor array was not emitted");
const auto pointer = DescriptorElementPointerId(
state, descriptors.pointer_type, descriptors.variable, array_index_id, kind, resource,
"storage image descriptor array was not emitted");
const auto image = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, descriptors.image_type, image, pointer});
return image;
@@ -168,12 +168,38 @@ void EmitImageStore(EmitterState& state, const IR::Instruction& inst) {
const auto view = StorageImageViewKind(state, inst.memory, uint_image, inst.pc);
const auto binding =
ResourceForDescriptor(state, StorageBindingKind(uint_image, view), inst.memory.resource);
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
binding.array_index, uint_image, view);
const auto emit_write = [&](uint32_t descriptor_index, bool non_uniform) {
if (non_uniform) {
state.builder.AddAnnotation({OpDecorate, descriptor_index, DecorationNonUniform});
}
const auto image = LoadStorageImageDescriptorAtIndex(state, inst.memory.resource,
descriptor_index, uint_image, view);
if (non_uniform) {
state.builder.AddAnnotation({OpDecorate, image, DecorationNonUniform});
}
state.builder.AddFunction({OpImageWrite, image, EmitImageCoordU32(state, inst, view),
uint_image ? EmitImageStoreTexelU32(state, inst)
: EmitImageStoreTexelF32(state, inst)});
};
if (!inst.memory.image_has_mip) {
emit_write(ConstantU32(state, binding.array_index), false);
return;
}
const auto& resource = state.program.info.images[inst.memory.resource];
const auto mip = EmitImageMipLodU32(state, inst, inst.src[1], view);
const auto in_range = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageWrite, image, EmitImageCoordU32(state, inst, view),
uint_image ? EmitImageStoreTexelU32(state, inst) : EmitImageStoreTexelF32(state, inst)});
{OpULessThan, state.bool_type, in_range, mip, ConstantU32(state, resource.mip_levels)});
EmitIfCondition(state, in_range, [&] {
auto descriptor_index = mip;
if (binding.array_index != 0) {
descriptor_index = state.builder.AllocateId();
state.builder.AddFunction({OpIAdd, state.uint_type, descriptor_index,
ConstantU32(state, binding.array_index), mip});
}
emit_write(descriptor_index, true);
});
}
void EmitImageSampleResult(EmitterState& state, const IR::Instruction& inst, uint32_t sample,
@@ -23,38 +23,40 @@
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
enum : uint32_t {
ExecutionModelVertex = 0,
ExecutionModelFragment = 4,
ExecutionModelGLCompute = 5,
ExecutionModeOriginUpperLeft = 7,
ExecutionModeEarlyFragmentTests = 9,
ExecutionModeDepthReplacing = 12,
ExecutionModeLocalSize = 17,
ExecutionModeDerivativeGroupQuadsKHR = 5289,
AddressingModelLogical = 0,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
CapabilityImageGatherExtended = 25,
CapabilitySampled1D = 43,
CapabilityImage1D = 44,
CapabilityImageQuery = 50,
CapabilityStorageImageReadWithoutFormat = 55,
CapabilityStorageImageWriteWithoutFormat = 56,
CapabilityGroupNonUniform = 61,
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
StorageClassOutput = 3,
StorageClassWorkgroup = 4,
StorageClassFunction = 7,
StorageClassPushConstant = 9,
StorageClassImage = 11,
StorageClassStorageBuffer = 12,
FunctionControlNone = 0,
SelectionControlNone = 0,
LoopControlNone = 0,
ExecutionModelVertex = 0,
ExecutionModelFragment = 4,
ExecutionModelGLCompute = 5,
ExecutionModeOriginUpperLeft = 7,
ExecutionModeEarlyFragmentTests = 9,
ExecutionModeDepthReplacing = 12,
ExecutionModeLocalSize = 17,
ExecutionModeDerivativeGroupQuadsKHR = 5289,
AddressingModelLogical = 0,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
CapabilityImageGatherExtended = 25,
CapabilitySampled1D = 43,
CapabilityImage1D = 44,
CapabilityImageQuery = 50,
CapabilityStorageImageReadWithoutFormat = 55,
CapabilityStorageImageWriteWithoutFormat = 56,
CapabilityGroupNonUniform = 61,
CapabilityGroupNonUniformBallot = 64,
CapabilityGroupNonUniformShuffle = 65,
CapabilityShaderNonUniform = 5301,
CapabilityStorageImageArrayNonUniformIndexing = 5309,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
StorageClassOutput = 3,
StorageClassWorkgroup = 4,
StorageClassFunction = 7,
StorageClassPushConstant = 9,
StorageClassImage = 11,
StorageClassStorageBuffer = 12,
FunctionControlNone = 0,
SelectionControlNone = 0,
LoopControlNone = 0,
};
enum : uint32_t {
@@ -67,6 +69,7 @@ enum : uint32_t {
DecorationBinding = 33,
DecorationDescriptorSet = 34,
DecorationOffset = 35,
DecorationNonUniform = 5300,
};
enum : uint32_t {
@@ -679,6 +682,10 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name);
uint32_t DescriptorElementPointerId(EmitterState& state, uint32_t result_ptr_type,
uint32_t variable_id, uint32_t array_index_id,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* variable_name);
ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc);
@@ -484,6 +484,13 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.needs_image_gather_extended) {
state.builder.AddCapability({CapabilityImageGatherExtended});
}
if (std::any_of(
state.program.info.images.begin(), state.program.info.images.end(),
[](const auto& image) { return image.mip_mode == IR::ImageMipMode::DynamicStorage; })) {
state.builder.AddCapability({CapabilityShaderNonUniform});
state.builder.AddCapability({CapabilityStorageImageArrayNonUniformIndexing});
state.builder.AddExtension("SPV_EXT_descriptor_indexing");
}
if (std::any_of(state.storage_images.begin(),
state.storage_images.begin() + StorageImageViewKindCount,
[](const auto& image) { return image.variable != 0; })) {
@@ -269,7 +269,10 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
}
return false;
}
image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())].push_back(i);
auto& resources = image_groups[static_cast<size_t>(group - ImageBindingKinds.begin())];
for (uint32_t binding = 0; binding < program.info.images[i].NumBindings(); binding++) {
resources.push_back(i);
}
}
for (uint32_t i = 0; i < image_groups.size(); i++) {
if (!image_groups[i].empty()) {
@@ -71,6 +71,17 @@ bool DescriptorIsCube(const DescriptorValue& descriptor) {
Prospero::ImageType::kCube;
}
bool DescriptorMipRange(const DescriptorValue& descriptor, uint32_t& count) {
const auto base_level = (descriptor.dwords[3] >> 12u) & 0xfu;
const auto last_level = (descriptor.dwords[3] >> 16u) & 0xfu;
const auto max_mip = (descriptor.dwords[5] >> 4u) & 0xfu;
if (base_level > last_level || last_level > max_mip) {
return false;
}
count = last_level - base_level + 1u;
return true;
}
bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResource& result) {
if (descriptor.dword_count != std::size(result.fields)) {
return false;
@@ -196,16 +207,31 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
}
continue;
}
if (image.mip_mode == ImageMipMode::DynamicStorage) {
uint32_t mip_levels = 0;
if (!DescriptorMipRange(descriptor, mip_levels) || mip_levels != image.mip_levels) {
if (error != nullptr) {
*error = fmt::format(
"image descriptor {} no longer matches specialized storage mip count", i);
}
return false;
}
} else if (image.mip_levels != 1u) {
if (error != nullptr) {
*error = fmt::format("image descriptor {} has invalid non-storage mip count", i);
}
return false;
}
const auto dimension = DescriptorDimension(descriptor, image.dimension);
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
DescriptorIsCube(descriptor) != image.cube) {
if (error != nullptr) {
*error = fmt::format(
"image descriptor {} no longer matches specialized dimension: "
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
descriptor.dwords[6], descriptor.dwords[7]);
*error =
fmt::format("image descriptor {} no longer matches specialized dimension: "
"{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x},{:08x}",
i, descriptor.dwords[0], descriptor.dwords[1], descriptor.dwords[2],
descriptor.dwords[3], descriptor.dwords[4], descriptor.dwords[5],
descriptor.dwords[6], descriptor.dwords[7]);
}
return false;
}
@@ -224,10 +250,9 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
const bool raw_sint_storage =
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt) &&
!image.read && !image.atomic;
const bool uint_descriptor =
Prospero::IsUintTextureFormat(format) || raw_sint_storage;
const auto uint_program = image.kind == ResourceKind::ImageUint ||
image.kind == ResourceKind::StorageImageUint;
const bool uint_descriptor = Prospero::IsUintTextureFormat(format) || raw_sint_storage;
const auto uint_program = image.kind == ResourceKind::ImageUint ||
image.kind == ResourceKind::StorageImageUint;
if (uint_descriptor != uint_program && !(image.atomic && uint_program)) {
if (error != nullptr) {
*error =
@@ -380,8 +405,9 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
const auto& descriptor = snapshot.images[i];
auto& image = next.images[i];
if (NullImageDescriptor(descriptor)) {
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
image.dimension = Decoder::ImageDimension::Dim2D;
image.cube = false;
image.mip_levels = 1;
switch (image.kind) {
case ResourceKind::ImageUint: image.kind = ResourceKind::Image; break;
case ResourceKind::StorageImageUint:
@@ -393,6 +419,16 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
}
continue;
}
if (image.mip_mode == ImageMipMode::DynamicStorage) {
if (!DescriptorMipRange(descriptor, image.mip_levels)) {
if (error != nullptr) {
*error = fmt::format("image descriptor {} has invalid storage mip range", i);
}
return false;
}
} else {
image.mip_levels = 1;
}
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
if (error != nullptr) {
@@ -598,11 +598,14 @@ enum class ImageMipMode { None, DynamicStorage };
constexpr uint32_t StorageImageIdentitySwizzle = 0x00000facu;
struct ImageResource {
static constexpr uint32_t MaxMipLevels = 16;
uint32_t source = 0;
uint32_t first_use_pc = 0;
ResourceKind kind = ResourceKind::None;
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
ImageMipMode mip_mode = ImageMipMode::None;
uint32_t mip_levels = 1;
uint32_t storage_swizzle = StorageImageIdentitySwizzle;
bool read = false;
bool written = false;
@@ -610,6 +613,10 @@ struct ImageResource {
bool depth_compare = false;
bool cube = false;
[[nodiscard]] uint32_t NumBindings() const {
return mip_mode == ImageMipMode::DynamicStorage ? mip_levels : 1u;
}
bool operator==(const ImageResource& other) const = default;
};