mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
graphics: share signed storage conversion and support R16 image writes
Co-authored-by: fxpw <fxpw@outlook.com>
This commit is contained in:
1 parent
f3c017b168
commit
c88f05f7b5
7 files changed
+227
-45
No files matched your search
@@ -308,9 +308,8 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
|||||||
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
|
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
|
||||||
const bool encoding_ok = IsSupportedStorageTextureEncoding(resource, descriptor);
|
const bool encoding_ok = IsSupportedStorageTextureEncoding(resource, descriptor);
|
||||||
const bool uint_resource = resource.numeric_class == Prospero::TextureNumericClass::Uint;
|
const bool uint_resource = resource.numeric_class == Prospero::TextureNumericClass::Uint;
|
||||||
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
|
const bool raw_sint_storage = uint_resource &&
|
||||||
resource.written &&
|
ShaderRecompiler::IR::RawSignedStorageFormat(resource, format) != Prospero::BufferFormat::kInvalid;
|
||||||
(resource.atomic ? !resource.atomic64 : !resource.read);
|
|
||||||
const auto numeric_class =
|
const auto numeric_class =
|
||||||
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
||||||
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
||||||
@@ -588,10 +587,11 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
|||||||
pixel_format = depth_format->depth_attachment_format;
|
pixel_format = depth_format->depth_attachment_format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto raw_sint_format = ShaderRecompiler::IR::RawSignedStorageFormat(resource, format);
|
||||||
const auto storage_view_format = resource.atomic64 ? vk::Format::eR64Uint
|
const auto storage_view_format = resource.atomic64 ? vk::Format::eR64Uint
|
||||||
: storage && (resource.atomic ||
|
: storage && resource.atomic ? vk::Format::eR32Uint
|
||||||
format == Prospero::BufferFormat::k32SInt)
|
: raw_sint_format != Prospero::BufferFormat::kInvalid
|
||||||
? vk::Format::eR32Uint
|
? VulkanFormat(raw_sint_format)
|
||||||
: SrgbStorageViewFormat(pixel_format);
|
: SrgbStorageViewFormat(pixel_format);
|
||||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||||
? storage_view_format
|
? storage_view_format
|
||||||
|
|||||||
@@ -385,9 +385,10 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
|||||||
const auto format = image.conversion_format;
|
const auto format = image.conversion_format;
|
||||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||||
const auto info = Format::GetFormatInfo(format);
|
const auto info = Format::GetFormatInfo(format);
|
||||||
EXIT_IF(Prospero::RemapTextureFormat(format) == format || info.component_count == 0u ||
|
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||||
|
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||||
info.component_count > 4u);
|
info.component_count > 4u);
|
||||||
EXIT_IF(info.type != Format::ComponentType::Uscaled &&
|
EXIT_IF(!raw_signed && info.type != Format::ComponentType::Uscaled &&
|
||||||
((info.type != Format::ComponentType::Uint &&
|
((info.type != Format::ComponentType::Uint &&
|
||||||
info.type != Format::ComponentType::Unorm) || !info.packed_bitfield ||
|
info.type != Format::ComponentType::Unorm) || !info.packed_bitfield ||
|
||||||
info.byte_size != sizeof(uint32_t)));
|
info.byte_size != sizeof(uint32_t)));
|
||||||
@@ -610,15 +611,16 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
|||||||
uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) {
|
uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) {
|
||||||
const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle;
|
const auto swizzle = ctx.state.program.info.images[mem.resource].shader_swizzle;
|
||||||
const auto info = ImageConversionFormat(ctx.state, mem);
|
const auto info = ImageConversionFormat(ctx.state, mem);
|
||||||
const bool packed = info.format != Prospero::BufferFormat::kInvalid;
|
const bool converted = info.format != Prospero::BufferFormat::kInvalid;
|
||||||
EXIT_IF(packed && (!info.packed_bitfield ||
|
const bool packed = info.packed_bitfield;
|
||||||
(info.type != Format::ComponentType::Uint &&
|
EXIT_IF(converted && info.type != Format::ComponentType::Sint &&
|
||||||
info.type != Format::ComponentType::Unorm)));
|
(!packed || (info.type != Format::ComponentType::Uint &&
|
||||||
|
info.type != Format::ComponentType::Unorm)));
|
||||||
const auto zero = ConstantU32(ctx.state, 0);
|
const auto zero = ConstantU32(ctx.state, 0);
|
||||||
uint32_t values[4] {zero, zero, zero, zero};
|
uint32_t values[4] {zero, zero, zero, zero};
|
||||||
auto packed_value = zero;
|
auto packed_value = zero;
|
||||||
const auto dmask = mem.dmask != 0u ? mem.dmask : 1u;
|
const auto dmask = mem.dmask != 0u ? mem.dmask : 1u;
|
||||||
for (uint32_t component = 0; component < (packed ? info.component_count : 4u); component++) {
|
for (uint32_t component = 0; component < (converted ? info.component_count : 4u); component++) {
|
||||||
const auto source = InverseSwizzle(swizzle, component);
|
const auto source = InverseSwizzle(swizzle, component);
|
||||||
const bool selected = source < 4u && ((dmask >> source) & 1u) != 0u;
|
const bool selected = source < 4u && ((dmask >> source) & 1u) != 0u;
|
||||||
uint32_t raw = zero;
|
uint32_t raw = zero;
|
||||||
@@ -638,9 +640,13 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (packed) {
|
if (converted) {
|
||||||
if (!selected) continue;
|
if (!selected) continue;
|
||||||
auto value = EncodeFormattedStoreComponent(ctx.state, info, component, raw, mem.data_bits);
|
auto value = EncodeFormattedStoreComponent(ctx.state, info, component, raw, mem.data_bits);
|
||||||
|
if (!packed) {
|
||||||
|
values[component] = value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (info.type == Format::ComponentType::Uint) {
|
if (info.type == Format::ComponentType::Uint) {
|
||||||
value = EmitUMin32(ctx.state, value,
|
value = EmitUMin32(ctx.state, value,
|
||||||
ConstantU32(ctx.state, (1u << info.component_bits[component]) - 1u));
|
ConstantU32(ctx.state, (1u << info.component_bits[component]) - 1u));
|
||||||
|
|||||||
@@ -435,6 +435,13 @@ uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::Buffer
|
|||||||
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (info.type == Format::ComponentType::Sint && bits < 32u) {
|
||||||
|
const auto maximum = (1u << (bits - 1u)) - 1u;
|
||||||
|
data = EmitSMax32(state, data, ConstantU32(state, ~maximum));
|
||||||
|
data = EmitSMin32(state, data, ConstantU32(state, maximum));
|
||||||
|
return Binary(state, spv::OpBitwiseAnd, TypeU32(state), data,
|
||||||
|
ConstantU32(state, (1u << bits) - 1u));
|
||||||
|
}
|
||||||
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
const bool normalized = info.type == Format::ComponentType::Unorm ||
|
||||||
info.type == Format::ComponentType::Snorm;
|
info.type == Format::ComponentType::Snorm;
|
||||||
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
const bool scaled = info.type == Format::ComponentType::Uscaled ||
|
||||||
|
|||||||
@@ -169,6 +169,17 @@ struct ImageResource {
|
|||||||
bool operator==(const ImageResource& other) const = default;
|
bool operator==(const ImageResource& other) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Prospero::BufferFormat
|
||||||
|
RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format) {
|
||||||
|
if (image.resource_class != ImageResourceClass::Storage || !image.written)
|
||||||
|
return Prospero::BufferFormat::kInvalid;
|
||||||
|
if (format == Prospero::BufferFormat::k32SInt && (image.atomic ? !image.atomic64 : !image.read))
|
||||||
|
return Prospero::BufferFormat::k32UInt;
|
||||||
|
if (format == Prospero::BufferFormat::k16SInt && !image.read && !image.atomic)
|
||||||
|
return Prospero::BufferFormat::k16UInt;
|
||||||
|
return Prospero::BufferFormat::kInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
struct SamplerResource {
|
struct SamplerResource {
|
||||||
uint32_t source = 0;
|
uint32_t source = 0;
|
||||||
uint32_t first_use_pc = 0;
|
uint32_t first_use_pc = 0;
|
||||||
|
|||||||
@@ -755,8 +755,9 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
|||||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||||
}
|
}
|
||||||
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
|
const auto raw_sint_format = RawSignedStorageFormat(base, format);
|
||||||
base.written && (base.atomic ? !base.atomic64 : !base.read);
|
const bool raw_sint_storage = raw_sint_format != Prospero::BufferFormat::kInvalid;
|
||||||
|
if (raw_sint_format == Prospero::BufferFormat::k16UInt) image.conversion_format = format;
|
||||||
image.numeric_class = Prospero::SampledTextureNumericClass(
|
image.numeric_class = Prospero::SampledTextureNumericClass(
|
||||||
Prospero::RemapTextureFormat(format));
|
Prospero::RemapTextureFormat(format));
|
||||||
if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) {
|
if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||||
|
|||||||
@@ -1164,6 +1164,82 @@ void TestMixedSamplerVariantsShareRuntimeDescriptor() {
|
|||||||
"sampler variants retained stale or duplicated descriptors after refresh");
|
"sampler variants retained stale or duplicated descriptors after refresh");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestSignedStorageSpecialization() {
|
||||||
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||||
|
using F = Libs::Graphics::Prospero::BufferFormat;
|
||||||
|
using N = Libs::Graphics::Prospero::TextureNumericClass;
|
||||||
|
Program program;
|
||||||
|
program.stage = Libs::Graphics::ShaderType::Compute;
|
||||||
|
program.srt_plan_complete = program.resource_tracking_complete = true;
|
||||||
|
program.user_data_count = 1u;
|
||||||
|
auto &block = AddValueBlock(program);
|
||||||
|
DescriptorSource source;
|
||||||
|
source.dword_count = 8u;
|
||||||
|
source.dwords.fill(Value(0u));
|
||||||
|
source.dwords[0] = Value(0x100u);
|
||||||
|
source.dwords[1] = Value(&block.AppendNewInst(ValueOpcode::GetUserData, {Value(ScalarReg(0))}));
|
||||||
|
constexpr auto swizzle = Libs::Graphics::DstSel(5, 4, 1, 0);
|
||||||
|
source.dwords[3] = Value(swizzle |
|
||||||
|
(uint32_t(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u));
|
||||||
|
program.descriptor_sources.push_back(source);
|
||||||
|
program.info.images.push_back({.source = 0u, .resource_class = ImageResourceClass::Storage,
|
||||||
|
.dimension = Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D, .written = true});
|
||||||
|
auto plan = ExtractResourcePlan(program);
|
||||||
|
std::array<uint32_t, 1> data{};
|
||||||
|
const SrtRuntime runtime{.user_data = data};
|
||||||
|
ResourceSnapshot snapshot;
|
||||||
|
ResourceSpecialization specialization, signed16;
|
||||||
|
for (const auto format : {F::k16SInt, F::k32SInt, F::k16SInt}) {
|
||||||
|
data[0] = uint32_t(format) << 20u;
|
||||||
|
Check(MaterializeResources(plan, runtime, snapshot, specialization),
|
||||||
|
"write-only signed storage image failed materialization");
|
||||||
|
const auto conversion = format == F::k16SInt ? format : F::kInvalid;
|
||||||
|
const auto view = format == F::k16SInt ? F::k16UInt : F::k32UInt;
|
||||||
|
Check(specialization.images[0].numeric_class == N::Uint &&
|
||||||
|
specialization.images[0].conversion_format == conversion &&
|
||||||
|
specialization.images[0].shader_swizzle == swizzle &&
|
||||||
|
snapshot.images[0].dwords[1] == data[0] &&
|
||||||
|
RawSignedStorageFormat(plan.info.images[0], format) == view,
|
||||||
|
"signed storage specialization lost guest format, conversion, swizzle or raw view width");
|
||||||
|
if (format == F::k16SInt) {
|
||||||
|
if (signed16.images.empty()) signed16 = specialization;
|
||||||
|
else Check(specialization == signed16, "signed storage format refresh retained a stale specialization");
|
||||||
|
} else {
|
||||||
|
Check(specialization != signed16, "signed 16/32-bit stores shared incompatible conversion state");
|
||||||
|
}
|
||||||
|
ApplyResourceSpecialization(program, specialization);
|
||||||
|
const auto binding = DescriptorBindingForImage(program.info.images[0]);
|
||||||
|
Check(binding && ImageBindingResourceClass(*binding) == ImageResourceClass::Storage,
|
||||||
|
"signed storage conversion did not reuse the unsigned storage binding");
|
||||||
|
}
|
||||||
|
for (const auto format : {F::k16SInt, F::k32SInt}) {
|
||||||
|
data[0] = uint32_t(format) << 20u;
|
||||||
|
auto &image = plan.info.images[0];
|
||||||
|
image.read = true;
|
||||||
|
Check(RawSignedStorageFormat(image, format) == F::kInvalid &&
|
||||||
|
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||||
|
"signed raw storage view accepted a non-atomic read/write image");
|
||||||
|
image.atomic = true;
|
||||||
|
Check((RawSignedStorageFormat(image, format) != F::kInvalid) == (format == F::k32SInt) &&
|
||||||
|
MaterializeResources(plan, runtime, snapshot, specialization) == (format == F::k32SInt),
|
||||||
|
"signed storage conversion changed atomic format eligibility");
|
||||||
|
image.atomic64 = true;
|
||||||
|
Check(RawSignedStorageFormat(image, format) == F::kInvalid &&
|
||||||
|
!MaterializeResources(plan, runtime, snapshot, specialization),
|
||||||
|
"signed 16/32-bit image acquired a 64-bit atomic raw view");
|
||||||
|
image.resource_class = ImageResourceClass::Sampled;
|
||||||
|
image.written = image.atomic = image.atomic64 = false;
|
||||||
|
Check(MaterializeResources(plan, runtime, snapshot, specialization) &&
|
||||||
|
specialization.images[0].numeric_class == N::Sint &&
|
||||||
|
specialization.images[0].conversion_format == F::kInvalid &&
|
||||||
|
RawSignedStorageFormat(image, format) == F::kInvalid,
|
||||||
|
"storage conversion changed ordinary signed image reads");
|
||||||
|
image.resource_class = ImageResourceClass::Storage;
|
||||||
|
image.written = true;
|
||||||
|
image.read = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestIndirectImageSamplerClasses(bool mixed) {
|
void TestIndirectImageSamplerClasses(bool mixed) {
|
||||||
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
using namespace Libs::Graphics::ShaderRecompiler::IR;
|
||||||
using Libs::Graphics::Prospero::BufferFormat;
|
using Libs::Graphics::Prospero::BufferFormat;
|
||||||
@@ -1256,6 +1332,7 @@ int main() {
|
|||||||
TestCyclicControlPredicatesRemainUnknown();
|
TestCyclicControlPredicatesRemainUnknown();
|
||||||
TestEmptyScalarBufferSpecialization();
|
TestEmptyScalarBufferSpecialization();
|
||||||
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
TestMixedSamplerVariantsShareRuntimeDescriptor();
|
||||||
|
TestSignedStorageSpecialization();
|
||||||
for (const bool mixed : {false, true}) TestIndirectImageSamplerClasses(mixed);
|
for (const bool mixed : {false, true}) TestIndirectImageSamplerClasses(mixed);
|
||||||
std::puts("ResourceMaterializationTests: all cases passed");
|
std::puts("ResourceMaterializationTests: all cases passed");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||||
#include "graphics/host_gpu/renderer/sync.h"
|
#include "graphics/host_gpu/renderer/sync.h"
|
||||||
#include "graphics/host_gpu/vulkanCommon.h"
|
#include "graphics/host_gpu/vulkanCommon.h"
|
||||||
|
#include <vulkan/vulkan_format_traits.hpp>
|
||||||
#include "graphics/presentation/window/windowInternal.h"
|
#include "graphics/presentation/window/windowInternal.h"
|
||||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
@@ -2009,7 +2010,6 @@ public:
|
|||||||
u32 height = 0;
|
u32 height = 0;
|
||||||
u32 layers = 1;
|
u32 layers = 1;
|
||||||
u32 mip_levels = 1;
|
u32 mip_levels = 1;
|
||||||
u32 dwords_per_pixel = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] vk::Device Device() const { return m_device; }
|
[[nodiscard]] vk::Device Device() const { return m_device; }
|
||||||
@@ -15749,11 +15749,12 @@ public:
|
|||||||
return std::max(value, 1u);
|
return std::max(value, 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t ImageMipDwordCount(u32 width, u32 height, u32 dwords_per_pixel,
|
static size_t ImageMipDwordCount(u32 width, u32 height, vk::Format format,
|
||||||
u32 level, u32 layers = 1) {
|
u32 level, u32 layers = 1) {
|
||||||
return static_cast<size_t>(MipExtent(width, level)) *
|
const auto block = vk::blockExtent(format);
|
||||||
static_cast<size_t>(MipExtent(height, level)) * layers *
|
const auto blocks_x = (MipExtent(width, level) + block[0] - 1u) / block[0];
|
||||||
dwords_per_pixel;
|
const auto blocks_y = (MipExtent(height, level) + block[1] - 1u) / block[1];
|
||||||
|
return (size_t{blocks_x} * blocks_y * layers * vk::blockSize(format) + 3u) / 4u;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image CreateImageMips(const char *shader_name, u32 width, u32 height,
|
Image CreateImageMips(const char *shader_name, u32 width, u32 height,
|
||||||
@@ -15772,7 +15773,8 @@ public:
|
|||||||
ret.height = height;
|
ret.height = height;
|
||||||
ret.layers = layers;
|
ret.layers = layers;
|
||||||
ret.mip_levels = std::max<u32>(static_cast<u32>(initial_mips.size()), 1u);
|
ret.mip_levels = std::max<u32>(static_cast<u32>(initial_mips.size()), 1u);
|
||||||
ret.dwords_per_pixel = dwords_per_pixel;
|
Require(shader_name, "image format size", dwords_per_pixel == (vk::blockSize(format) + 3u) / 4u,
|
||||||
|
"image fixture word size disagrees with its native format");
|
||||||
|
|
||||||
vk::ImageCreateInfo image_info{};
|
vk::ImageCreateInfo image_info{};
|
||||||
image_info.sType = vk::StructureType::eImageCreateInfo;
|
image_info.sType = vk::StructureType::eImageCreateInfo;
|
||||||
@@ -15845,13 +15847,13 @@ public:
|
|||||||
size_t total_dwords = 0;
|
size_t total_dwords = 0;
|
||||||
for (u32 level = 0; level < ret.mip_levels; level++) {
|
for (u32 level = 0; level < ret.mip_levels; level++) {
|
||||||
total_dwords +=
|
total_dwords +=
|
||||||
ImageMipDwordCount(width, height, dwords_per_pixel, level, layers);
|
ImageMipDwordCount(width, height, format, level, layers);
|
||||||
}
|
}
|
||||||
std::vector<u32> contents(total_dwords, 0);
|
std::vector<u32> contents(total_dwords, 0);
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
for (u32 level = 0; level < ret.mip_levels; level++) {
|
for (u32 level = 0; level < ret.mip_levels; level++) {
|
||||||
const auto level_dwords =
|
const auto level_dwords =
|
||||||
ImageMipDwordCount(width, height, dwords_per_pixel, level, layers);
|
ImageMipDwordCount(width, height, format, level, layers);
|
||||||
const auto &src = initial_mips[level];
|
const auto &src = initial_mips[level];
|
||||||
for (size_t i = 0; i < src.size() && i < level_dwords; i++) {
|
for (size_t i = 0; i < src.size() && i < level_dwords; i++) {
|
||||||
contents[offset + i] = src[i];
|
contents[offset + i] = src[i];
|
||||||
@@ -15891,9 +15893,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u32> ReadImage(const char *shader_name, Image *image) {
|
std::vector<u32> ReadImage(const char *shader_name, Image *image) {
|
||||||
const auto dword_count = static_cast<size_t>(image->width) *
|
const auto dword_count = ImageMipDwordCount(image->width, image->height,
|
||||||
static_cast<size_t>(image->height) *
|
image->format, 0, image->layers);
|
||||||
image->layers * image->dwords_per_pixel;
|
|
||||||
auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32),
|
auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32),
|
||||||
vk::BufferUsageFlagBits::eTransferDst, {});
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
||||||
|
|
||||||
@@ -19549,7 +19550,7 @@ private:
|
|||||||
copies.push_back(copy);
|
copies.push_back(copy);
|
||||||
offset += static_cast<vk::DeviceSize>(
|
offset += static_cast<vk::DeviceSize>(
|
||||||
ImageMipDwordCount(image->width, image->height,
|
ImageMipDwordCount(image->width, image->height,
|
||||||
image->dwords_per_pixel, level, image->layers) *
|
image->format, level, image->layers) *
|
||||||
sizeof(u32));
|
sizeof(u32));
|
||||||
}
|
}
|
||||||
cmd.copyBufferToImage(staging, image->image,
|
cmd.copyBufferToImage(staging, image->image,
|
||||||
@@ -31882,23 +31883,23 @@ TestCase TBufferLoadFormatXSintSignExtendsSubDword() {
|
|||||||
|
|
||||||
TestCase TBufferStoreFormatXSintWritesSubDword() {
|
TestCase TBufferStoreFormatXSintWritesSubDword() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
TestCase test;
|
||||||
std::vector<u32> code;
|
test.name = "TBufferStoreFormatXSintWritesSubDword";
|
||||||
AppendVMovU32(&code, 20, 0);
|
constexpr std::array<u32, 6> inputs{0xffffff80u, 0xffffff7fu, 128u,
|
||||||
AppendVMovLiteral(&code, 0, 0xffffff80u);
|
0xffff8001u, 0xffff7fffu, 32768u};
|
||||||
code.push_back(EncodeMtbuf0(0x04, 6, 0));
|
constexpr std::array<u32, 6> encoded{0x80u, 0x80u, 0x7fu, 0x8001u, 0x8000u, 0x7fffu};
|
||||||
code.push_back(EncodeMtbuf1(0x04, 0, 0, 20));
|
test.initial.assign(inputs.size(), 0x11223344u);
|
||||||
AppendVMovU32(&code, 20, 4);
|
test.expected = test.initial;
|
||||||
AppendVMovLiteral(&code, 1, 0xffff8001u);
|
for (u32 index = 0; index < inputs.size(); ++index) {
|
||||||
code.push_back(EncodeMtbuf0(0x04, 12, 0));
|
AppendVMovU32(&test.code, 20, index * 4u);
|
||||||
code.push_back(EncodeMtbuf1(0x04, 1, 0, 20));
|
AppendVMovLiteral(&test.code, 0, inputs[index]);
|
||||||
AppendEnd(&code);
|
test.code.push_back(EncodeMtbuf0(0x04, index < 3u ? 6u : 12u, 0));
|
||||||
|
test.code.push_back(EncodeMtbuf1(0x04, 0, 0, 20));
|
||||||
return {"TBufferStoreFormatXSintWritesSubDword",
|
test.expected[index] = (test.expected[index] & (index < 3u ? 0xffffff00u : 0xffff0000u)) | encoded[index];
|
||||||
code,
|
}
|
||||||
{0x11223344u, 0x55667788u},
|
AppendEnd(&test.code);
|
||||||
{0x11223380u, 0x55668001u},
|
test.opcodes = {O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
||||||
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_X, O::S_ENDPGM}};
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase TBufferLoadFormatXy88IntegerComponents() {
|
TestCase TBufferLoadFormatXy88IntegerComponents() {
|
||||||
@@ -38292,6 +38293,82 @@ TestCase ImageStoreR32FloatUsesFormatlessStorageImage() {
|
|||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckSigned16ImageStore(VulkanHarness &vulkan) {
|
||||||
|
constexpr std::array<int32_t, 8> values{INT32_MIN, -32769, -32768, -1, 0, 32767, 32768, INT32_MAX};
|
||||||
|
const u32 alignment = vulkan.RuntimeContext().GetPhysicalDeviceProperties()
|
||||||
|
.limits.minStorageBufferOffsetAlignment;
|
||||||
|
for (const u32 wave : {32u, 64u}) {
|
||||||
|
for (const bool d16 : {false, true}) {
|
||||||
|
for (const u32 component : {0u, 2u}) {
|
||||||
|
TestCase test;
|
||||||
|
test.name = "Signed16ImageAndBufferStore";
|
||||||
|
const u32 width = wave - 2u;
|
||||||
|
const u32 output = (wave * 4u + alignment - 1u) / alignment * alignment;
|
||||||
|
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k16SInt);
|
||||||
|
test.user_data[1] |= ((width - 1u) & 3u) << 30u;
|
||||||
|
test.user_data[2] = ((width - 1u) >> 2u) | (1u << 14u);
|
||||||
|
test.has_user_data = test.has_compute_info = true;
|
||||||
|
test.image_descriptor_swizzle = component == 0u ? DstSel(4, 0, 0, 1) : DstSel(0, 0, 4, 1);
|
||||||
|
const std::array<u32, 8> descriptors{0x1000u, 4u << 16u, wave, 0x5204u,
|
||||||
|
0x2000u, 2u << 16u, wave, (u32(Prospero::BufferFormat::k16SInt) << 12u) | 4u};
|
||||||
|
std::copy(descriptors.begin(), descriptors.end(), test.user_data.begin() + 8u);
|
||||||
|
test.storage_buffer_ranges = {{0u, wave * 4u}, {output, wave * 2u}};
|
||||||
|
test.compute_info.wave_size = wave;
|
||||||
|
test.compute_info.threads_num[0] = wave;
|
||||||
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1u;
|
||||||
|
test.compute_info.thread_ids_num = 1u;
|
||||||
|
test.compute_info.lds_size_dwords = 0u;
|
||||||
|
test.initial.assign(output / 4u + wave / 2u, 0xdeadbeefu);
|
||||||
|
for (u32 lane = 0; lane < wave; ++lane)
|
||||||
|
test.initial[lane] = d16 ? 0x7a5b0000u | (u32(values[lane % values.size()]) & 0xffffu)
|
||||||
|
: u32(values[lane % values.size()]);
|
||||||
|
test.expected = test.initial;
|
||||||
|
std::vector<u32> pixels(width, 0x13571357u), expected = pixels;
|
||||||
|
for (u32 lane = 0; lane < wave; ++lane) {
|
||||||
|
const auto input = values[lane % values.size()];
|
||||||
|
const auto value = d16 ? int32_t(std::bit_cast<int16_t>(uint16_t(input))) : input;
|
||||||
|
const u32 encoded = uint16_t(std::clamp(value, -32768, 32767));
|
||||||
|
const auto set_half = [&](std::vector<u32> &words, u32 index) {
|
||||||
|
const auto shift = (index & 1u) * 16u;
|
||||||
|
words[index / 2u] = (words[index / 2u] & ~(0xffffu << shift)) | (encoded << shift);
|
||||||
|
};
|
||||||
|
set_half(test.expected, output / 2u + lane);
|
||||||
|
if (lane < width) {
|
||||||
|
set_half(expected, lane);
|
||||||
|
if (lane & 1u) set_half(expected, width + lane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto &code = test.code;
|
||||||
|
code = {EncodeMubuf0(0x0c, 0, true, false), EncodeMubuf1(4, 2, 0),
|
||||||
|
EncodeSopp(0x0c, 0), EncodeMubuf0(d16 ? 0x84 : 0x04, 0, true, false),
|
||||||
|
EncodeMubuf1(4, 3, 0), EncodeVop1(0x01, 20, Vgpr(0))};
|
||||||
|
AppendVMovU32(&code, 21, 0u);
|
||||||
|
code.insert(code.end(), {EncodeMimg0(0x08, 1u << component),
|
||||||
|
EncodeMimg1(4, 20, 0, 0, false, d16)});
|
||||||
|
AppendVMovU32(&code, 21, 1u);
|
||||||
|
AppendSMovLiteral(&code, 126, 0xaaaaaaaau);
|
||||||
|
AppendSMovLiteral(&code, 127, wave == 64u ? 0xaaaaaaaau : 0u);
|
||||||
|
code.insert(code.end(), {EncodeMimg0(0x08, 1u << component),
|
||||||
|
EncodeMimg1(4, 20, 0, 0, false, d16)});
|
||||||
|
AppendEnd(&code);
|
||||||
|
test.opcodes = {ShaderOpcode::IMAGE_STORE};
|
||||||
|
const auto compiled = CompileCase(test, vulkan.SubgroupSize());
|
||||||
|
auto buffer = vulkan.CreateStorageBuffer(test.name, test.initial, test.initial.size());
|
||||||
|
auto image = vulkan.CreateImage2D(test.name, width, 2u, vk::Format::eR16Sint,
|
||||||
|
vk::ImageUsageFlagBits::eStorage, pixels, 1u, vk::ImageLayout::eGeneral, vk::Format::eR16Uint);
|
||||||
|
vulkan.Dispatch(test, compiled, buffer, nullptr, nullptr, nullptr, &image);
|
||||||
|
CompareWords(test, "signed image saturation, sparse channels, masked lanes and OOB", expected,
|
||||||
|
vulkan.ReadImage(test.name, &image));
|
||||||
|
CompareWords(test, "shared buffer conversion and adjacent halfwords", test.expected,
|
||||||
|
vulkan.ReadBuffer(test.name, buffer, test.expected.size()));
|
||||||
|
vulkan.DestroyImage(&image);
|
||||||
|
vulkan.DestroyBuffer(&buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("[compute] %-32s ok\n", "Signed16ImageAndBufferStore");
|
||||||
|
}
|
||||||
|
|
||||||
TestCase ImageStoreR32SintUsesRawUintView() {
|
TestCase ImageStoreR32SintUsesRawUintView() {
|
||||||
using O = ShaderOpcode;
|
using O = ShaderOpcode;
|
||||||
|
|
||||||
@@ -45517,6 +45594,7 @@ int main(int argc, char **argv) {
|
|||||||
RunCase(&vulkan, BufferStoreFormatXRejectsPartialRecord());
|
RunCase(&vulkan, BufferStoreFormatXRejectsPartialRecord());
|
||||||
RunCase(&vulkan, BufferStoreFormatXyRejectsPartialRecord());
|
RunCase(&vulkan, BufferStoreFormatXyRejectsPartialRecord());
|
||||||
RunCase(&vulkan, BufferFormatStoreVariants());
|
RunCase(&vulkan, BufferFormatStoreVariants());
|
||||||
|
RunCase(&vulkan, TBufferStoreFormatXSintWritesSubDword());
|
||||||
RunCase(&vulkan, BufferStoreByteD16Hi(false));
|
RunCase(&vulkan, BufferStoreByteD16Hi(false));
|
||||||
RunCase(&vulkan, BufferStoreByteD16Hi(true));
|
RunCase(&vulkan, BufferStoreByteD16Hi(true));
|
||||||
return 0;
|
return 0;
|
||||||
@@ -45634,6 +45712,7 @@ int main(int argc, char **argv) {
|
|||||||
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
vulkan.CheckPackedTextureComponents();
|
vulkan.CheckPackedTextureComponents();
|
||||||
|
CheckSigned16ImageStore(vulkan);
|
||||||
vulkan.CheckBgra16Readback();
|
vulkan.CheckBgra16Readback();
|
||||||
RunCase(&vulkan, ImageLoadPackedUintUnpacksAndSwizzles());
|
RunCase(&vulkan, ImageLoadPackedUintUnpacksAndSwizzles());
|
||||||
RunCase(&vulkan, ImageLoadPackedUnorm<false, false>());
|
RunCase(&vulkan, ImageLoadPackedUnorm<false, false>());
|
||||||
@@ -46414,6 +46493,7 @@ int main(int argc, char **argv) {
|
|||||||
CheckTessellationPrograms();
|
CheckTessellationPrograms();
|
||||||
CheckPixelParameterAliases();
|
CheckPixelParameterAliases();
|
||||||
CheckRectListShaders();
|
CheckRectListShaders();
|
||||||
|
CheckSigned16ImageStore(vulkan);
|
||||||
CheckBoundedFormattedBufferCopy(vulkan);
|
CheckBoundedFormattedBufferCopy(vulkan);
|
||||||
CheckIndirectBufferStore(vulkan);
|
CheckIndirectBufferStore(vulkan);
|
||||||
CheckBoundedRecordBufferAccess(vulkan);
|
CheckBoundedRecordBufferAccess(vulkan);
|
||||||
|
|||||||
Reference in new issue
Block a user