mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-25 12:26:51 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52c8ff18d0 | |||
| f53e5d2283 | |||
| bd611bfe15 | |||
| 0bb2bdad17 | |||
| 0ec3655f1c | |||
| 190608ae20 | |||
| ebe401d052 | |||
| 0d4f993358 | |||
| ef4c5baf3a | |||
| e6cb880adb | |||
| 8fa116eeb1 |
@@ -61,8 +61,8 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true},
|
||||
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, true, false, true},
|
||||
{BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false},
|
||||
{BufferFormat::k8Srgb, 1, 0, 0, true, false},
|
||||
{BufferFormat::k8_8Srgb, 2, 0, 0, true, false},
|
||||
{BufferFormat::k8Srgb, 1, 0, 1, true, false},
|
||||
{BufferFormat::k8_8Srgb, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8_8_8Srgb, 4, 0, 4, true, false},
|
||||
{BufferFormat::k9_9_9_5Float, 4, 0, 0, true, false},
|
||||
{BufferFormat::k5_6_5UNorm, 2, 0, 2, true, false},
|
||||
|
||||
@@ -29,6 +29,7 @@ struct GraphicContext {
|
||||
bool memory_budget_ext_enabled = false;
|
||||
bool compute_subgroup_size_control_enabled = false;
|
||||
bool sample_rate_shading_enabled = false;
|
||||
bool shader_image_int64_atomics_enabled = false;
|
||||
// bool fp64_denorm_preserve = false; // Temporarily disabled.
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
|
||||
@@ -114,6 +114,9 @@ public:
|
||||
void ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
auto& bits = GetBits<source>();
|
||||
if (bits.FirstRangeFrom(start).first >= end) {
|
||||
return;
|
||||
}
|
||||
RegionBits mask(bits, start, end);
|
||||
if constexpr (clear) {
|
||||
bits.UnsetRange(start, end);
|
||||
|
||||
+18
-28
@@ -791,13 +791,6 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
if (const auto depth_id = ResolveDepthOverlap(requested, binding, cached_id)) {
|
||||
return {depth_id};
|
||||
}
|
||||
if (requested.IsBlock() && !cached.info.IsBlock()) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
if (requested.data.size == cached.info.data.size &&
|
||||
(requested.IsVolume() || cached.info.IsVolume())) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
// Equal pitch does not imply equal mip placement: a changed extent can move
|
||||
// a level into or out of the mip tail. These are separate guest layouts.
|
||||
if (requested.tile_mode != cached.info.tile_mode ||
|
||||
@@ -808,6 +801,13 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
}
|
||||
return {merged_id};
|
||||
}
|
||||
if (requested.IsBlock() && !cached.info.IsBlock()) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
if (requested.data.size == cached.info.data.size &&
|
||||
(requested.IsVolume() || cached.info.IsVolume())) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
// PPSA08394
|
||||
// A view cannot change the native image type or grow its extent.
|
||||
if (requested.data.size == cached.info.data.size &&
|
||||
@@ -878,9 +878,7 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
|
||||
RefreshCopySource(source_id);
|
||||
const auto expanded_id = InsertImage(info);
|
||||
auto& expanded = m_slot_images[expanded_id];
|
||||
auto& source = m_slot_images[source_id];
|
||||
expanded.usage = source.usage;
|
||||
if (source.binding.is_bound || source.binding.is_target) {
|
||||
source.binding.needs_rebind = true;
|
||||
}
|
||||
@@ -926,22 +924,12 @@ TextureCache::BuildTextureTransfer(const Image& image, BindingType binding,
|
||||
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 = upload;
|
||||
const char* owner = "TextureCache readback";
|
||||
|
||||
TextureTransfer transfer;
|
||||
transfer.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;
|
||||
}
|
||||
transfer.swap_bgra16 = info.bgra16;
|
||||
if (upload) {
|
||||
if ((render_target || video_out) &&
|
||||
(info.resources.layers == 0 || info.data.size % info.resources.layers != 0 ||
|
||||
@@ -963,9 +951,9 @@ TextureCache::BuildTextureTransfer(const Image& image, BindingType binding,
|
||||
}
|
||||
}
|
||||
|
||||
transfer.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height,
|
||||
info.resources.levels, layers, info.tile_mode,
|
||||
info.data.size, allow_depth_tile, volume, owner);
|
||||
transfer.layout = TextureCalcUploadLayout(info.guest_format, info.extent.width,
|
||||
info.extent.height, info.resources.levels, layers,
|
||||
info.tile_mode, info.data.size, volume, owner);
|
||||
transfer.regions = TextureBuildImageCopies(transfer.layout);
|
||||
if (info.IsDepth()) {
|
||||
for (auto& region: transfer.regions) {
|
||||
@@ -1133,7 +1121,7 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// Native color metadata must not retain a reused HTile/CMask/FMask clear flag.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
|
||||
if (range.size == 0 || desc.info.resources.levels != 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1181,10 +1169,12 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
{vk::ImageAspectFlagBits::eColor, view.base_level, view.level_count,
|
||||
image_first + slice, 1}, clear);
|
||||
}
|
||||
// Native expanded keys own consumption. Existing buffer tracking publishes this CPU
|
||||
// write to future GPU readers; FillBuffer can fault and must run outside the texture lock.
|
||||
// Publish the conversion's expanded keys without treating them as guest writes
|
||||
// to overlapping image data. Invalidate the buffer before updating its backing.
|
||||
if (desc.type != BindingType::VideoOut) {
|
||||
m_buffer_cache.FillBuffer(address, slice_size, UINT32_MAX, false);
|
||||
std::fill(bytes.begin(), bytes.end(), uint8_t {0xff});
|
||||
m_buffer_cache.InvalidateMemory(address, slice_size);
|
||||
LibKernel::Memory::WriteBacking(address, bytes.data(), bytes.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1850,7 +1840,7 @@ void TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (!image.Overlaps(address, size)) {
|
||||
if (image.info.data.address != address) {
|
||||
continue;
|
||||
}
|
||||
if (image.IsGpuModified()) {
|
||||
|
||||
@@ -358,10 +358,8 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
}
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage {};
|
||||
usage.usage = image.usage;
|
||||
if (!is_storage) {
|
||||
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
usage.usage = is_storage ? vk::ImageUsageFlagBits::eStorage
|
||||
: image.usage & ~vk::ImageUsageFlagBits::eStorage;
|
||||
vk::ImageViewMinLodCreateInfoEXT min_lod {};
|
||||
if (normalized.min_lod != 0) {
|
||||
min_lod.minLod = static_cast<float>(normalized.base_level) +
|
||||
|
||||
@@ -90,8 +90,11 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
|
||||
return false;
|
||||
}
|
||||
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic;
|
||||
return ((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
resource.mip_count == 1u) ||
|
||||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic &&
|
||||
resource.mip_count != 0u)) &&
|
||||
resource.read && !resource.written && !resource.atomic;
|
||||
}
|
||||
|
||||
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
|
||||
@@ -114,7 +117,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
resource.mip_count == 1u) ||
|
||||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage &&
|
||||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic &&
|
||||
resource.mip_count != 0u)) &&
|
||||
resource.written &&
|
||||
(!resource.atomic ||
|
||||
|
||||
@@ -175,8 +175,7 @@ SurfaceFormatInfo TextureGetSurfaceFormatInfo(Prospero::BufferFormat format) {
|
||||
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
|
||||
uint32_t height, uint32_t levels, uint32_t depth,
|
||||
Prospero::TileMode tile_mode, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner) {
|
||||
bool volume_texture, const char* owner) {
|
||||
TextureUploadLayout layout {};
|
||||
layout.surface.description = {
|
||||
format,
|
||||
@@ -204,8 +203,7 @@ TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint3
|
||||
static_cast<uint32_t>(format));
|
||||
}
|
||||
} else {
|
||||
if ((tile_mode == Prospero::TileMode::kDepth && !allow_depth_tile) ||
|
||||
!TileGetTiledTextureLayout(description, layout.surface)) {
|
||||
if (!TileGetTiledTextureLayout(description, layout.surface)) {
|
||||
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
|
||||
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
|
||||
owner, static_cast<uint32_t>(format), static_cast<uint32_t>(tile_mode),
|
||||
|
||||
@@ -47,8 +47,7 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layo
|
||||
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
|
||||
uint32_t height, uint32_t levels, uint32_t depth,
|
||||
Prospero::TileMode tile_mode, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
bool volume_texture, const char* owner);
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
|
||||
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t levels,
|
||||
|
||||
@@ -312,13 +312,14 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
||||
resource.atomic;
|
||||
resource.atomic && !resource.atomic64;
|
||||
const bool format_ok =
|
||||
raw_sint_storage || raw_float_atomic ||
|
||||
(numeric_class != Prospero::TextureNumericClass::Unsupported &&
|
||||
numeric_class != Prospero::TextureNumericClass::Sint &&
|
||||
uint_resource == (numeric_class == Prospero::TextureNumericClass::Uint) &&
|
||||
(!resource.atomic || format == Prospero::BufferFormat::k32UInt));
|
||||
(!resource.atomic || format == (resource.atomic64 ? Prospero::BufferFormat::k32_32UInt
|
||||
: Prospero::BufferFormat::k32UInt)));
|
||||
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
|
||||
return;
|
||||
}
|
||||
@@ -354,7 +355,8 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
|
||||
desc.info.guest_format = Prospero::BufferFormat::k32Float;
|
||||
break;
|
||||
case Prospero::TextureNumericClass::Uint:
|
||||
desc.info.guest_format = Prospero::BufferFormat::k32UInt;
|
||||
desc.info.guest_format = resource.atomic64 ? Prospero::BufferFormat::k32_32UInt
|
||||
: Prospero::BufferFormat::k32UInt;
|
||||
break;
|
||||
case Prospero::TextureNumericClass::Sint:
|
||||
desc.info.guest_format = Prospero::BufferFormat::k32SInt;
|
||||
@@ -365,10 +367,10 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {1, 1, 1};
|
||||
desc.info.resources = {1, 1};
|
||||
desc.info.bytes_per_block = 4;
|
||||
desc.info.bytes_per_block = Prospero::NumBytesPerElement(desc.info.guest_format);
|
||||
desc.info.samples = 1;
|
||||
desc.info.mip_layout[0] = {0, 0, 1, 1};
|
||||
desc.view_info.format = desc.info.pixel_format;
|
||||
desc.view_info.format = resource.atomic64 ? vk::Format::eR64Uint : desc.info.pixel_format;
|
||||
desc.view_info.type = vk::ImageViewType::e2D;
|
||||
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
||||
desc.view_info.usage = binding == TextureCache::BindingType::Storage
|
||||
@@ -538,6 +540,9 @@ static bool ResolveTextureMipView(const TileSurfaceDescription& description, boo
|
||||
|
||||
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
if (resource.atomic64 && !m_context.GetGraphics().shader_image_int64_atomics_enabled) {
|
||||
EXIT("64-bit image atomics require shaderImageInt64Atomics\n");
|
||||
}
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(value);
|
||||
const bool storage = resource.written;
|
||||
if (storage) {
|
||||
@@ -563,7 +568,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const auto physical_levels = multisampled ? 1u : static_cast<uint32_t>(max_mip) + 1u;
|
||||
// IMAGE_STORE addresses BASE_LEVEL; only IMAGE_STORE_MIP selects other view mips.
|
||||
const bool single_storage_mip =
|
||||
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
storage && resource.mip_mode != ShaderRecompiler::IR::ImageMipMode::Dynamic;
|
||||
const auto view_levels = multisampled || single_storage_mip
|
||||
? 1u
|
||||
: static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
@@ -650,8 +655,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
pixel_format = depth_format->depth_attachment_format;
|
||||
}
|
||||
}
|
||||
const auto storage_view_format = storage && (resource.atomic ||
|
||||
format == Prospero::BufferFormat::k32SInt)
|
||||
const auto storage_view_format = resource.atomic64 ? vk::Format::eR64Uint
|
||||
: storage && (resource.atomic ||
|
||||
format == Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||
@@ -773,8 +779,6 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
|
||||
prepared.flattened_srt = {};
|
||||
prepared.shader_data_buffer = {};
|
||||
prepared.buffer_sources.clear();
|
||||
prepared.buffers.clear();
|
||||
prepared.images.resize(program.info.images.size());
|
||||
prepared.samplers.clear();
|
||||
prepared.shader_data.clear();
|
||||
@@ -887,7 +891,7 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto& binding = images[i];
|
||||
binding.mip_views.clear();
|
||||
const auto& resource = program.info.images[i];
|
||||
if (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage) {
|
||||
if (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::Dynamic) {
|
||||
EXIT_IF(resource.mip_count == 0u ||
|
||||
resource.mip_count != binding.desc.view_info.level_count);
|
||||
binding.mip_views.reserve(resource.mip_count);
|
||||
@@ -895,6 +899,8 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
auto desc = binding.desc;
|
||||
desc.view_info.base_level += mip;
|
||||
desc.view_info.level_count = 1;
|
||||
// The shader selects the mip after applying the guest minimum LOD.
|
||||
desc.view_info.min_lod = 0;
|
||||
binding.mip_views.push_back(texture_cache.FindTexture(binding.image_id, desc));
|
||||
}
|
||||
binding.image_view = binding.mip_views.front();
|
||||
|
||||
@@ -658,6 +658,19 @@ ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
input_info.host_subgroup_size = m_graphics.SupportsComputeWave64() ? 64u : 32u;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
// Use one effective size for the cache key, LDS declaration, and access bounds.
|
||||
const auto max_lds_dwords =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxComputeSharedMemorySize / 4u;
|
||||
if (input_info.lds_size_dwords > max_lds_dwords) {
|
||||
static std::atomic_bool warned = false;
|
||||
if (!warned.exchange(true, std::memory_order_relaxed)) {
|
||||
PipelineCacheLog("GPU warning: game compute shader requests {} bytes of LDS, but "
|
||||
"the Vulkan device limit is {} bytes. Clamping LDS; rendering may "
|
||||
"be incorrect.",
|
||||
input_info.lds_size_dwords * 4u, max_lds_dwords * 4u);
|
||||
}
|
||||
}
|
||||
input_info.lds_size_dwords = std::min(input_info.lds_size_dwords, max_lds_dwords);
|
||||
uint32_t push_data_cursor = 0;
|
||||
return m_program_cache->Get(params, input_info, push_data_cursor);
|
||||
}
|
||||
|
||||
@@ -460,7 +460,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
provoking_vertex.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &provoking_vertex;
|
||||
}
|
||||
const bool image_atomic_int64_extension =
|
||||
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
|
||||
if (image_atomic_int64_extension) {
|
||||
image_atomic_int64.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &image_atomic_int64;
|
||||
}
|
||||
physical_device.getFeatures2(&supported_features2);
|
||||
graphics.shader_image_int64_atomics_enabled = image_atomic_int64.shaderImageInt64Atomics;
|
||||
|
||||
auto features12 = WindowContext::RequiredVulkan12Features();
|
||||
features12.shaderSharedInt64Atomics = supported_features12.shaderSharedInt64Atomics;
|
||||
@@ -605,6 +613,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
provoking_vertex.transformFeedbackPreservesProvokingVertex = VK_FALSE;
|
||||
create_info.pNext = &provoking_vertex;
|
||||
}
|
||||
if (graphics.shader_image_int64_atomics_enabled) {
|
||||
image_atomic_int64.pNext = const_cast<void*>(create_info.pNext);
|
||||
image_atomic_int64.sparseImageInt64Atomics = VK_FALSE;
|
||||
create_info.pNext = &image_atomic_int64;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -969,6 +982,7 @@ void WindowContext::CreateVulkan() {
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||
|
||||
@@ -306,14 +306,20 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
|
||||
|
||||
if (f.added) {
|
||||
auto* pad = SDL_OpenGamepad(f.id);
|
||||
EXIT_NOT_IMPLEMENTED(pad == nullptr);
|
||||
if (pad == nullptr) {
|
||||
LOGF("Controller: ignoring gamepad %d that could not be opened: %s\n", f.id,
|
||||
SDL_GetError());
|
||||
return;
|
||||
}
|
||||
int id = SDL_GetJoystickID(SDL_GetGamepadJoystick(pad));
|
||||
Controller::Connect(id);
|
||||
}
|
||||
|
||||
if (f.removed) {
|
||||
Controller::Disconnect(f.id);
|
||||
SDL_CloseGamepad(SDL_GetGamepadFromID(f.id));
|
||||
if (auto* pad = SDL_GetGamepadFromID(f.id); pad != nullptr) {
|
||||
Controller::Disconnect(f.id);
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
|
||||
if (f.down || f.up) {
|
||||
|
||||
@@ -41,7 +41,7 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(*kind)] = true;
|
||||
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage;
|
||||
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::Dynamic;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
|
||||
@@ -124,13 +124,21 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
sampled = 2;
|
||||
if (image.atomic) {
|
||||
EXIT_IF(image.numeric_class != Prospero::TextureNumericClass::Uint);
|
||||
format = spv::ImageFormatR32ui;
|
||||
format = image.atomic64 ? spv::ImageFormatR64ui : spv::ImageFormatR32ui;
|
||||
if (image.atomic64) {
|
||||
state.builder.RequireExtension("SPV_EXT_shader_image_int64");
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityInt64Atomics);
|
||||
state.builder.RequireCapability(spv::CapabilityInt64ImageEXT);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
return state.builder.Type(spv::OpTypeImage, ImageScalarType(state, image.numeric_class),
|
||||
const auto scalar_type = image.atomic64 ? TypeScalarU64(state)
|
||||
: ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
info.spirv_dimension, image.depth_compare ? 1u : 0u, info.arrayed,
|
||||
info.multisampled, sampled, format);
|
||||
}
|
||||
@@ -144,20 +152,11 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto kind = IR::DescriptorBindingForImage(image_resource);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image_resource));
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"sampled image descriptor array was not emitted");
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip);
|
||||
const auto image = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, ImageType(state, image_resource), image, pointer);
|
||||
state.builder.AddFunction(spv::OpLoad, ImageType(state, state.program.info.images.at(resource)),
|
||||
image, pointer);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -175,10 +174,10 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler) {
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id, uint32_t mip) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
const auto image = LoadSampledImageDescriptor(state, resource);
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
state.builder.Type(spv::OpTypeSampledImage, ImageType(state, image_resource));
|
||||
@@ -186,17 +185,17 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) {
|
||||
uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t mip) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
EXIT_IF(mip >= image.mip_count);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource) + mip;
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassUniformConstant, ImageType(state, image));
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
@@ -206,46 +205,15 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto image_type = ImageType(state, image);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, image_type);
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto LoadAt = [&](uint32_t array_index) {
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
const auto descriptor = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, image_type, descriptor, pointer);
|
||||
return descriptor;
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip),
|
||||
coord, texel);
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index), coord, texel);
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
return;
|
||||
}
|
||||
if (image.mip_count == 0u) {
|
||||
ExitDescriptorBindingFailure(state, *kind, resource,
|
||||
"dynamic storage image has no mip descriptors");
|
||||
}
|
||||
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
std::vector<uint32_t> labels(image.mip_count);
|
||||
std::vector<uint32_t> words {spv::OpSwitch, mip_lod, merge_label};
|
||||
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
|
||||
labels[mip] = state.builder.AllocateId();
|
||||
words.push_back(mip);
|
||||
words.push_back(labels[mip]);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(words);
|
||||
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
|
||||
EmitLabel(state, labels[mip]);
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadAt(array_index + mip), coord, texel);
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
EmitImageMipSwitch(state, mip_lod, image.mip_count, 0, EmitWrite);
|
||||
}
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage) {
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -86,6 +84,46 @@ uint32_t ZeroF32(EmitterState& state) {
|
||||
return ConstantF32(state, 0);
|
||||
}
|
||||
|
||||
uint32_t GatherMip(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const IR::Inst& address, const ImageSampleLayout& layout, uint32_t mip_count) {
|
||||
auto& state = ctx.state;
|
||||
const auto Extract = [&](uint32_t word, uint32_t first, uint32_t count) {
|
||||
return EmitBitFieldUExtract(state, word, ConstantU32(state, first),
|
||||
ConstantU32(state, count));
|
||||
};
|
||||
const auto LodFixed = [&](uint32_t word, uint32_t first) {
|
||||
return Binary(state, spv::OpFMul, TypeF32(state),
|
||||
Unary(state, spv::OpConvertUToF, TypeF32(state), Extract(word, first, 12)),
|
||||
ConstantF32Value(state, 1.0f / 256.0f));
|
||||
};
|
||||
const auto sampler_control = ctx.Arg(inst, 6);
|
||||
const auto preclamp = Binary(state, spv::OpINotEqual, TypeBool(state),
|
||||
Extract(sampler_control, 28, 1), ConstantU32(state, 0));
|
||||
const auto half = ConstantF32Value(state, 0.5f);
|
||||
const auto zero = ZeroF32(state);
|
||||
const auto base = Unary(state, spv::OpConvertUToF, TypeF32(state),
|
||||
Extract(ctx.Arg(inst, 4), 12, 4));
|
||||
const auto minimum = Binary(state, spv::OpFSub, TypeF32(state),
|
||||
LodFixed(ctx.Arg(inst, 3), 8), base);
|
||||
const auto last = ConstantF32Value(state, static_cast<float>(mip_count - 1u));
|
||||
auto lod = Binary(state, spv::OpFAdd, TypeF32(state),
|
||||
AddressF32(ctx, mem, address, layout.lod),
|
||||
Select(state, TypeF32(state), preclamp, half, zero));
|
||||
lod = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(
|
||||
state, lod, LodFixed(ctx.Arg(inst, 5), 0), LodFixed(ctx.Arg(inst, 5), 12));
|
||||
// T# minimum LOD is in physical mip space and applies after the S# view-relative clamp.
|
||||
lod = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(state, lod, minimum);
|
||||
lod = EmitGlsl<GLSLstd450FClamp, IR::Type::F32>(state, lod, zero, last);
|
||||
lod = Binary(state, spv::OpFAdd, TypeF32(state), lod,
|
||||
Select(state, TypeF32(state), preclamp, zero, half));
|
||||
// The clamped value is nonnegative, so conversion performs the integer mip rounding.
|
||||
const auto mip = EmitUMin32(state, Unary(state, spv::OpConvertFToU, TypeU32(state), lod),
|
||||
ConstantU32(state, mip_count - 1u));
|
||||
const auto mip_none = Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
Extract(sampler_control, 26, 2), ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), mip_none, ConstantU32(state, 0), mip);
|
||||
}
|
||||
|
||||
uint32_t CubeAxis(EmitterState& state, uint32_t value) {
|
||||
return Binary(state, spv::OpFSub, TypeF32(state), value, ConstantF32(state, 0x3f800000u));
|
||||
}
|
||||
@@ -252,7 +290,7 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const IR::Inst& address) {
|
||||
const auto dimension = ctx.state.program.info.images.at(mem.resource).dimension;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource);
|
||||
const auto image = LoadImageDescriptor(ctx.state, mem.resource);
|
||||
const auto size = ctx.state.builder.AllocateId();
|
||||
if (info.multisampled != 0u) {
|
||||
ctx.state.builder.AddFunction(spv::OpImageQuerySize,
|
||||
@@ -433,7 +471,7 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
uint32_t coord, Prospero::TextureNumericClass numeric_class) {
|
||||
auto& state = ctx.state;
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto image = LoadSampledImageDescriptor(state, mem.resource);
|
||||
const auto image = LoadImageDescriptor(state, mem.resource);
|
||||
const auto width = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpImageQuerySizeLod, TypeU32(state), width, image,
|
||||
ConstantU32(state, 0));
|
||||
@@ -446,7 +484,8 @@ uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
Binary(state, spv::OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u)));
|
||||
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto vector_type = ImageVectorType(state, numeric_class, 4);
|
||||
const auto scalar_type = ImageScalarType(state, numeric_class);
|
||||
const auto component =
|
||||
@@ -558,7 +597,8 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto dimension = image.dimension;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
@@ -588,7 +628,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
EmitValueOrDefaultIfCondition(
|
||||
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
|
||||
[&]() {
|
||||
const auto descriptor = LoadSampledImageDescriptor(state, mem.resource);
|
||||
const auto descriptor = LoadImageDescriptor(state, mem.resource);
|
||||
const auto color = state.builder.AllocateId();
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
@@ -612,7 +652,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto dimension = image.dimension;
|
||||
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto mip_lod =
|
||||
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage
|
||||
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, dimension)
|
||||
: 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
@@ -635,13 +675,6 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
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)) {
|
||||
std::fputs("Warning: approximating IMAGE_GATHER4_L at mip level 0; explicit LOD is ignored.\n",
|
||||
stderr);
|
||||
}
|
||||
}
|
||||
if (dimension == ImageDimension::Dim1D) {
|
||||
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
|
||||
@@ -658,44 +691,48 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "has an unsupported 1D-array gather");
|
||||
return;
|
||||
}
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words;
|
||||
const auto result_numeric_class = dref ? Prospero::TextureNumericClass::Float
|
||||
: numeric_class;
|
||||
const auto result_type = ImageVectorType(state, result_numeric_class, 4);
|
||||
uint32_t component_or_dref;
|
||||
if (dref) {
|
||||
auto dref_value = ZeroF32(state);
|
||||
if (layout.dref != NoImageComponent) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
words = {spv::OpImageDrefGather,
|
||||
TypeF32Vector(state, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
dref_value};
|
||||
component_or_dref = layout.dref != NoImageComponent
|
||||
? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state);
|
||||
} else {
|
||||
uint32_t component = 0;
|
||||
if (ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid) {
|
||||
component = ImageGatherComponent(mem.dmask);
|
||||
}
|
||||
words = {spv::OpImageGather,
|
||||
ImageVectorType(state, numeric_class, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
ConstantU32(state, component)};
|
||||
component_or_dref = ConstantU32(
|
||||
state, ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask) : 0u);
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
uint32_t offset = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
words.push_back(spv::ImageOperandsConstOffsetsMask);
|
||||
words.push_back(HorizontalOffsets(state, dimension));
|
||||
operand_mask = spv::ImageOperandsConstOffsetsMask;
|
||||
offset = HorizontalOffsets(state, dimension);
|
||||
} else if (layout.offset != NoImageComponent) {
|
||||
words.push_back(spv::ImageOperandsOffsetMask);
|
||||
words.push_back(PackedOffset(ctx, mem, *address, layout, dimension));
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
auto result_numeric_class = numeric_class;
|
||||
if (dref) {
|
||||
result_numeric_class = Prospero::TextureNumericClass::Float;
|
||||
operand_mask = spv::ImageOperandsOffsetMask;
|
||||
offset = PackedOffset(ctx, mem, *address, layout, dimension);
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto EmitGather = [&](uint32_t mip) {
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, sampler_id, mip);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {
|
||||
static_cast<uint32_t>(dref ? spv::OpImageDrefGather : spv::OpImageGather), result_type,
|
||||
sample, sampled, coord, component_or_dref};
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.push_back(offset);
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
return sample;
|
||||
};
|
||||
const auto sample = image.mip_mode == IR::ImageMipMode::Dynamic && image.mip_count > 1u
|
||||
? EmitImageMipSwitch(
|
||||
state, GatherMip(ctx, inst, mem, *address, layout,
|
||||
image.mip_count),
|
||||
image.mip_count, result_type, EmitGather)
|
||||
: EmitGather(0);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
|
||||
result_numeric_class, false, mem, true));
|
||||
return;
|
||||
@@ -738,13 +775,14 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operand_mask |= spv::ImageOperandsBiasMask;
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
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 sampled = MakeSampledImage(state, resource, sampler_id);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> sample_operands {result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
@@ -865,12 +903,16 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
if (image_info.access == IR::ImageAccess::Atomic) {
|
||||
const auto dimension = image.dimension;
|
||||
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto result_type = TypeId(state, inst.GetType());
|
||||
const auto zero = image.atomic64 ? ConstantU64(state, 0) : ConstantU32(state, 0);
|
||||
ctx.Define(inst, EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), result_type, zero, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto pointer_type = state.builder.Type(
|
||||
spv::OpTypePointer, spv::StorageClassImage, TypeU32(state));
|
||||
spv::OpTypePointer, spv::StorageClassImage,
|
||||
image.atomic64 ? TypeScalarU64(state) : TypeU32(state));
|
||||
state.builder.AddFunction(spv::OpImageTexelPointer, pointer_type, pointer,
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
ImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
ConstantU32(state, 0));
|
||||
if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
|
||||
|
||||
@@ -317,6 +317,7 @@ inline constexpr auto EmitImageAtomicSMin32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicUMin32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicSMax32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicUMax64 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <map>
|
||||
#include <spirv/unified1/GLSL.std.450.h>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -314,13 +315,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image);
|
||||
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension);
|
||||
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource);
|
||||
uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t mip = 0);
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip = 0);
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler);
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource);
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id,
|
||||
uint32_t mip = 0);
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel);
|
||||
@@ -514,6 +516,45 @@ uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value);
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high);
|
||||
|
||||
// These templates accept local lambdas from several emitter translation units.
|
||||
template <typename Fn>
|
||||
auto EmitImageMipSwitch(EmitterState& state, uint32_t mip_lod, uint32_t mip_count,
|
||||
uint32_t result_type, Fn&& emit) {
|
||||
constexpr bool has_result = !std::is_void_v<std::invoke_result_t<Fn, uint32_t>>;
|
||||
EXIT_IF(mip_count == 0u);
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
std::vector<uint32_t> labels(mip_count);
|
||||
std::vector<uint32_t> words {spv::OpSwitch, mip_lod, merge_label};
|
||||
for (uint32_t mip = 0; mip < mip_count; mip++) {
|
||||
labels[mip] = state.builder.AllocateId();
|
||||
words.push_back(mip);
|
||||
words.push_back(labels[mip]);
|
||||
}
|
||||
if constexpr (has_result) {
|
||||
words[2] = labels.front();
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSelectionMerge, merge_label, spv::SelectionControlMaskNone);
|
||||
state.builder.AddFunction(words);
|
||||
std::vector<uint32_t> phi;
|
||||
if constexpr (has_result) {
|
||||
phi = {spv::OpPhi, result_type, state.builder.AllocateId()};
|
||||
}
|
||||
for (uint32_t mip = 0; mip < mip_count; mip++) {
|
||||
EmitLabel(state, labels[mip]);
|
||||
if constexpr (has_result) {
|
||||
phi.push_back(emit(mip));
|
||||
phi.push_back(state.current_label);
|
||||
} else {
|
||||
emit(mip);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpBranch, merge_label);
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
if constexpr (has_result) {
|
||||
state.builder.AddFunction(phi);
|
||||
return phi[2];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
|
||||
const auto then_label = state.builder.AllocateId();
|
||||
|
||||
@@ -1044,17 +1044,11 @@ uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, EmitMemoryElementInBounds(state, resource, index), TypeU64(state),
|
||||
ConstantU64(state, 0), [&]() {
|
||||
const auto value = Unary(state, spv::OpBitcast, TypeScalarU64(state),
|
||||
ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
|
||||
EmitStorageBufferElementPointer(state, resource, index,
|
||||
TypeStorageBufferU64ElementPointer(state)),
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
ConstantU32(state, spv::MemorySemanticsMaskNone), value);
|
||||
const auto pointer = EmitStorageBufferElementPointer(
|
||||
state, resource, index, TypeStorageBufferU64ElementPointer(state));
|
||||
const auto old = EmitAtomicOperation(ctx, inst, pointer, spv::ScopeDevice);
|
||||
EmitAtomicMemoryBarrier(state, mem.kind);
|
||||
return Unary(state, spv::OpBitcast, TypeU64(state), old);
|
||||
return old;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,6 +330,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicSMax32:
|
||||
case IR::ValueOpcode::SharedAtomicSMax32: return spv::OpAtomicSMax;
|
||||
case IR::ValueOpcode::ImageAtomicUMax32:
|
||||
case IR::ValueOpcode::ImageAtomicUMax64:
|
||||
case IR::ValueOpcode::BufferAtomicUMax32:
|
||||
case IR::ValueOpcode::SharedAtomicUMax32: return spv::OpAtomicUMax;
|
||||
case IR::ValueOpcode::ImageAtomicAnd32:
|
||||
@@ -352,6 +353,8 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
uint32_t scope) {
|
||||
const auto opcode = SpirvAtomicOpcode(inst.GetOpcode());
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
const bool wide = inst.GetType() == IR::Type::U64;
|
||||
const auto type = wide ? TypeScalarU64(ctx.state) : TypeU32(ctx.state);
|
||||
if (opcode == spv::OpAtomicCompareExchange) {
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
@@ -360,12 +363,13 @@ uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, spv::MemorySemanticsMaskNone),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), desired, comparator);
|
||||
} else {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(opcode, TypeU32(ctx.state), old, pointer,
|
||||
auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
if (wide) value = Unary(ctx.state, spv::OpBitcast, type, value);
|
||||
ctx.state.builder.AddFunction(opcode, type, old, pointer,
|
||||
ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, spv::MemorySemanticsMaskNone), value);
|
||||
}
|
||||
return old;
|
||||
return wide ? Unary(ctx.state, spv::OpBitcast, TypeU64(ctx.state), old) : old;
|
||||
}
|
||||
|
||||
void EmitAtomicMemoryBarrier(EmitterState& state, IR::ResourceKind kind) {
|
||||
|
||||
@@ -360,9 +360,19 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
SetUnsupported(inst, Family::MIMG, opcode,
|
||||
"MIMG image gather requires exactly one dmask bit");
|
||||
}
|
||||
if (inst.opcode == Opcode::IMAGE_ATOMIC_CMPSWAP && inst.dmask != 0x3u) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode,
|
||||
"MIMG image compare-and-swap requires 32-bit DMASK 0x3");
|
||||
if (atomic != nullptr) {
|
||||
const bool compare_swap = inst.opcode == Opcode::IMAGE_ATOMIC_CMPSWAP;
|
||||
const auto mask32 = compare_swap ? 0x3u : 0x1u;
|
||||
const auto mask64 = compare_swap ? 0xfu : 0x3u;
|
||||
if (inst.dmask != mask32 && inst.dmask != mask64) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode, "MIMG image atomic has invalid DMASK");
|
||||
} else if (inst.dmask == mask64) {
|
||||
inst.data_bits = 64u;
|
||||
if (inst.opcode != Opcode::IMAGE_ATOMIC_UMAX) {
|
||||
SetUnsupported(inst, Family::MIMG, opcode,
|
||||
"MIMG 64-bit image atomic opcode is not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool supports_d16 = sample != nullptr || gather != nullptr || opcode == 0x00u ||
|
||||
opcode == 0x01u || opcode == 0x08u || opcode == 0x09u;
|
||||
|
||||
@@ -568,7 +568,9 @@ void Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
ir.GetExec()},
|
||||
flags);
|
||||
} else {
|
||||
result = ir.Emit(opcode, {resource, address, ReadU32(data), ir.GetExec()}, flags);
|
||||
const IR::Value value =
|
||||
memory.data_bits == 64u ? IR::Value(ReadU64(data)) : IR::Value(ReadU32(data));
|
||||
result = ir.Emit(opcode, {resource, address, value, ir.GetExec()}, flags);
|
||||
}
|
||||
if (inst.glc) {
|
||||
WriteOperand(inst.dst, result);
|
||||
@@ -706,8 +708,16 @@ void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto sampler = GetSamplerResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageGatherRaw, {resource, sampler, address},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
const bool has_lod = (memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u;
|
||||
// LOD selection consumes these words on the GPU after descriptor handles are stripped.
|
||||
const auto result = ir.Emit(
|
||||
IR::ValueOpcode::ImageGatherRaw,
|
||||
{resource, sampler, address,
|
||||
has_lod ? resource.Instruction()->Arg(1) : IR::Value(0u),
|
||||
has_lod ? resource.Instruction()->Arg(3) : IR::Value(0u),
|
||||
has_lod ? sampler.Instruction()->Arg(1) : IR::Value(0u),
|
||||
has_lod ? sampler.Instruction()->Arg(2) : IR::Value(0u)},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
for (uint32_t index = 0; index < memory.data_dwords; index++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)}));
|
||||
@@ -1052,7 +1062,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_SMAX:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicSMax32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_UMAX:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicUMax32);
|
||||
return IMAGE_ATOMIC(inst, inst.data_bits == 64u ? IR::ValueOpcode::ImageAtomicUMax64
|
||||
: IR::ValueOpcode::ImageAtomicUMax32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_AND:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicAnd32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_OR:
|
||||
|
||||
@@ -114,7 +114,7 @@ struct BufferResource {
|
||||
bool operator==(const BufferResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class ImageMipMode { None, DynamicStorage };
|
||||
enum class ImageMipMode { None, Dynamic };
|
||||
|
||||
constexpr uint32_t ShaderImageIdentitySwizzle = 0x00000facu;
|
||||
|
||||
@@ -133,6 +133,7 @@ struct ImageResource {
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
bool atomic64 = false;
|
||||
bool depth_compare = false;
|
||||
bool cube = false;
|
||||
bool r128 = false;
|
||||
@@ -150,6 +151,7 @@ struct SamplerResource {
|
||||
bool force_point_filtering = false;
|
||||
bool depth_compare = false;
|
||||
bool integer_border = false;
|
||||
bool gather_lod = false;
|
||||
|
||||
bool operator==(const SamplerResource& other) const = default;
|
||||
};
|
||||
@@ -278,7 +280,7 @@ struct StageOutput {
|
||||
inline constexpr uint32_t FirstImageBinding = 1u;
|
||||
inline constexpr uint32_t FirstComparisonImageBinding = 22u;
|
||||
inline constexpr uint32_t FirstStorageImageBinding = 29u;
|
||||
inline constexpr uint32_t ImageBindingCount = 43u;
|
||||
inline constexpr uint32_t ImageBindingCount = 48u;
|
||||
|
||||
enum class DescriptorBindingKind : uint32_t {
|
||||
Buffers = 0u,
|
||||
@@ -291,8 +293,8 @@ enum class DescriptorBindingKind : uint32_t {
|
||||
Count,
|
||||
};
|
||||
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 44u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 50u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 55u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
@@ -369,7 +371,7 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
if (image.numeric_class != Prospero::TextureNumericClass::Uint) {
|
||||
return std::nullopt;
|
||||
}
|
||||
base = AtomicUintBinding;
|
||||
base = AtomicUintBinding + (image.atomic64 ? 5u : 0u);
|
||||
} else {
|
||||
switch (image.numeric_class) {
|
||||
case Prospero::TextureNumericClass::Float: base = StorageFloatBinding; break;
|
||||
|
||||
@@ -227,6 +227,7 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::ImageAtomicUMin32:
|
||||
case ValueOpcode::ImageAtomicSMax32:
|
||||
case ValueOpcode::ImageAtomicUMax32:
|
||||
case ValueOpcode::ImageAtomicUMax64:
|
||||
case ValueOpcode::ImageAtomicAnd32:
|
||||
case ValueOpcode::ImageAtomicOr32:
|
||||
case ValueOpcode::ImageAtomicXor32:
|
||||
|
||||
@@ -297,7 +297,7 @@ VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
|
||||
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
|
||||
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
|
||||
VALUE_OPCODE(ImageSampleRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
|
||||
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress)
|
||||
VALUE_OPCODE(ImageGatherRaw, U32x4, ImageResource, SamplerResource, ImageAddress, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(ImageAtomicCompareSwap32, U32, ImageResource, ImageAddress, U32, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicSwap32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicIAdd32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
@@ -305,6 +305,7 @@ VALUE_OPCODE(ImageAtomicSMin32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicUMin32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicSMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicUMax64, U64, ImageResource, ImageAddress, U64, U1)
|
||||
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
|
||||
@@ -126,7 +126,7 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
EXIT("shader binding layout failed: image %u has an unmapped binding class", i);
|
||||
}
|
||||
auto& resources = image_groups[group];
|
||||
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::DynamicStorage;
|
||||
const auto dynamic = program.info.images[i].mip_mode == ImageMipMode::Dynamic;
|
||||
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
|
||||
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
|
||||
EXIT("shader binding layout failed: image %u has invalid specialized mip count %u", i,
|
||||
|
||||
@@ -124,8 +124,8 @@ bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
Prospero::ImageType::kCube;
|
||||
}
|
||||
|
||||
uint32_t StorageMipCount(const ImageResource& image, const DescriptorValue& descriptor) {
|
||||
if (image.mip_mode != ImageMipMode::DynamicStorage || NullImageDescriptor(descriptor)) {
|
||||
uint32_t ImageMipCount(const ImageResource& image, const DescriptorValue& descriptor) {
|
||||
if (image.mip_mode != ImageMipMode::Dynamic || NullImageDescriptor(descriptor)) {
|
||||
return 1;
|
||||
}
|
||||
const auto base = (descriptor.dwords[3] >> 12u) & 0xfu;
|
||||
@@ -435,10 +435,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
(base.atomic && base.resource_class != ImageResourceClass::Storage)) {
|
||||
return SpecializationFail(fmt::format("image resource {} has an invalid class", i));
|
||||
}
|
||||
image.mip_count = StorageMipCount(base, descriptor);
|
||||
image.mip_count = ImageMipCount(base, descriptor);
|
||||
if (image.mip_count == 0u) {
|
||||
return SpecializationFail(
|
||||
fmt::format("storage image descriptor {} has an invalid mip range", i));
|
||||
fmt::format("image descriptor {} has an invalid mip range", i));
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
image.numeric_class = base.atomic ? Prospero::TextureNumericClass::Uint
|
||||
@@ -460,8 +460,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt &&
|
||||
format != Prospero::BufferFormat::k32Float) {
|
||||
if (base.atomic &&
|
||||
(base.atomic64 ? format != Prospero::BufferFormat::k32_32UInt
|
||||
: format != Prospero::BufferFormat::k32UInt &&
|
||||
format != Prospero::BufferFormat::k32Float)) {
|
||||
return SpecializationFail(
|
||||
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
@@ -1075,6 +1077,16 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) {
|
||||
return false;
|
||||
}
|
||||
if (program.info.samplers[i].gather_lod) {
|
||||
const auto control = snapshot.samplers[i].dwords[2];
|
||||
const auto filter = (control >> 26u) & 3u;
|
||||
// MipNone always selects the base level. Explicit point gathers currently require
|
||||
// encoded-zero primary and secondary bias; linear primary-mip selection is unsupported.
|
||||
if (filter > 1u || (filter == 1u && (control & 0xfffffu) != 0u)) {
|
||||
return SpecializationFail(
|
||||
"explicit-LOD gather requires mip filtering None or Point with zero LOD biases");
|
||||
}
|
||||
}
|
||||
}
|
||||
snapshot.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
return BuildResourceSpecialization(program, snapshot, specialization);
|
||||
|
||||
@@ -1725,15 +1725,20 @@ private:
|
||||
|
||||
uint32_t AddImage(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
||||
const auto resource_class = ImageOpcodeInfoOf(op).resource_class;
|
||||
const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip
|
||||
? ImageMipMode::DynamicStorage
|
||||
: ImageMipMode::None;
|
||||
const bool atomic64 = ImageOpcodeInfoOf(op).access == ImageAccess::Atomic &&
|
||||
memory.data_bits == 64u;
|
||||
const bool dynamic_mip =
|
||||
(resource_class == ImageResourceClass::Storage && memory.image_has_mip) ||
|
||||
(op == ValueOpcode::ImageGatherRaw &&
|
||||
(memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u);
|
||||
const auto mip = dynamic_mip ? ImageMipMode::Dynamic : ImageMipMode::None;
|
||||
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
|
||||
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
||||
auto& image = m_info.images[i];
|
||||
if (image.source == source && image.resource_class == resource_class &&
|
||||
image.dimension == memory.image_dimension && image.mip_mode == mip &&
|
||||
image.depth_compare == depth && image.r128 == memory.image_r128) {
|
||||
image.depth_compare == depth && image.r128 == memory.image_r128 &&
|
||||
image.atomic64 == atomic64) {
|
||||
Merge(image, op, pc);
|
||||
return i;
|
||||
}
|
||||
@@ -1749,6 +1754,7 @@ private:
|
||||
image.mip_mode = mip;
|
||||
image.depth_compare = depth;
|
||||
image.r128 = memory.image_r128;
|
||||
image.atomic64 = atomic64;
|
||||
Merge(image, op, pc);
|
||||
m_info.images.push_back(image);
|
||||
return static_cast<uint32_t>(m_info.images.size() - 1);
|
||||
@@ -1935,6 +1941,9 @@ private:
|
||||
if (sampler == UINT32_MAX) {
|
||||
Fail(flags.pc, "sampler resource limit exceeded");
|
||||
}
|
||||
m_info.samplers[sampler].gather_lod |=
|
||||
op == ValueOpcode::ImageGatherRaw &&
|
||||
(memory.image_sample_flags & Decoder::ImageSampleFlagLod) != 0u;
|
||||
AddHandlePatch(sampler_handle, sampler, flags.pc);
|
||||
AddSampledPair(resource, sampler, flags.pc);
|
||||
}
|
||||
|
||||
@@ -402,6 +402,100 @@ void TestCpuDirtyUpload() {
|
||||
Release(memory);
|
||||
}
|
||||
|
||||
void TestCleanUploadPreservesOwnership() {
|
||||
constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE;
|
||||
constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE;
|
||||
TrackerHarness harness;
|
||||
auto &tracker = harness.tracker;
|
||||
auto *memory = AllocateFixedGuestRange(region_size * 2, region_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
tracker.ForEachUploadRange(address, region_size * 2, false,
|
||||
[](uint64_t, uint64_t) noexcept {},
|
||||
[]() noexcept {});
|
||||
|
||||
for (const auto start : {address + 63 * page_size,
|
||||
address + region_size - page_size}) {
|
||||
const auto before = start - page_size;
|
||||
const auto after = start + 2 * page_size;
|
||||
tracker.MarkRegionAsCpuModified(before, page_size);
|
||||
tracker.MarkRegionAsCpuModified(after, page_size);
|
||||
uint32_t ranges = 0;
|
||||
uint32_t completions = 0;
|
||||
ResetProtectionLog();
|
||||
tracker.ForEachUploadRange(
|
||||
start, 2 * page_size, false,
|
||||
[&](uint64_t, uint64_t) noexcept { ranges++; },
|
||||
[&]() noexcept { completions++; });
|
||||
Check(ranges == 0 && completions == 1 && g_protection_calls == 0 &&
|
||||
!tracker.IsRegionCpuModified(start, 2 * page_size),
|
||||
"clean read-only upload changed dirty state or protection");
|
||||
|
||||
tracker.ForEachUploadRange(
|
||||
start, 2 * page_size, true,
|
||||
[&](uint64_t, uint64_t) noexcept { ranges++; },
|
||||
[&]() noexcept { completions++; });
|
||||
Check(ranges == 0 && completions == 2 &&
|
||||
tracker.IsRegionGpuModified(start, page_size) &&
|
||||
tracker.IsRegionGpuModified(start + page_size, page_size) &&
|
||||
Protection(reinterpret_cast<void *>(start)) == PAGE_NOACCESS &&
|
||||
Protection(reinterpret_cast<void *>(start + page_size)) ==
|
||||
PAGE_NOACCESS &&
|
||||
tracker.IsRegionCpuModified(before, page_size) &&
|
||||
tracker.IsRegionCpuModified(after, page_size) &&
|
||||
!tracker.IsRegionGpuModified(before, page_size) &&
|
||||
!tracker.IsRegionGpuModified(after, page_size) &&
|
||||
IsWritable(reinterpret_cast<void *>(before)) &&
|
||||
IsWritable(reinterpret_cast<void *>(after)),
|
||||
"clean written upload lost GPU ownership or changed dirty neighbors");
|
||||
tracker.UnmarkRegionAsGpuModified(start, 2 * page_size);
|
||||
}
|
||||
tracker.UntrackMemory(address, region_size * 2);
|
||||
Release(memory);
|
||||
}
|
||||
|
||||
void BenchmarkCleanUploads() {
|
||||
constexpr uint64_t size = 256ull * 1024 * 1024;
|
||||
TrackerHarness harness;
|
||||
auto &tracker = harness.tracker;
|
||||
auto *memory = AllocateFixedGuestRange(size, 0);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
tracker.ForEachUploadRange(address, size, false,
|
||||
[](uint64_t, uint64_t) noexcept {},
|
||||
[]() noexcept {});
|
||||
std::puts("chunk_bytes,sweeps,calls,elapsed_ns,ns_per_sweep,ns_per_call");
|
||||
for (const uint64_t chunk : {size, Libs::Graphics::TRACKER_REGION_SIZE,
|
||||
uint64_t{64 * 1024}}) {
|
||||
uint64_t ranges = 0;
|
||||
uint64_t completions = 0;
|
||||
uint64_t sweeps = 0;
|
||||
ResetProtectionLog();
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
std::chrono::nanoseconds elapsed{};
|
||||
do {
|
||||
for (uint64_t offset = 0; offset < size; offset += chunk) {
|
||||
tracker.ForEachUploadRange(
|
||||
address + offset, chunk, false,
|
||||
[&](uint64_t, uint64_t) noexcept { ranges++; },
|
||||
[&]() noexcept { completions++; });
|
||||
}
|
||||
sweeps++;
|
||||
elapsed = std::chrono::steady_clock::now() - start;
|
||||
} while (elapsed < std::chrono::milliseconds(250));
|
||||
Check(ranges == 0 && completions == sweeps * (size / chunk) &&
|
||||
g_protection_calls == 0,
|
||||
"clean upload benchmark changed ranges, completion, or protection");
|
||||
std::printf("%llu,%llu,%llu,%lld,%.2f,%.2f\n",
|
||||
static_cast<unsigned long long>(chunk),
|
||||
static_cast<unsigned long long>(sweeps),
|
||||
static_cast<unsigned long long>(completions),
|
||||
static_cast<long long>(elapsed.count()),
|
||||
static_cast<double>(elapsed.count()) / sweeps,
|
||||
static_cast<double>(elapsed.count()) / completions);
|
||||
}
|
||||
tracker.UntrackMemory(address, size);
|
||||
Release(memory);
|
||||
}
|
||||
|
||||
void TestRangeInvalidation() {
|
||||
TrackerHarness harness;
|
||||
auto &tracker = harness.tracker;
|
||||
@@ -1027,11 +1121,16 @@ int main(int argc, char **argv) {
|
||||
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
|
||||
RunDeathCase(argv[2]);
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--benchmark-clean-upload") == 0) {
|
||||
BenchmarkCleanUploads();
|
||||
return 0;
|
||||
}
|
||||
TestGuestRange();
|
||||
TestRangeSet();
|
||||
TestQueriesDoNotRequireMappedOwnership();
|
||||
TestConcurrentRegionPublication();
|
||||
TestCpuDirtyUpload();
|
||||
TestCleanUploadPreservesOwnership();
|
||||
TestRangeInvalidation();
|
||||
TestGpuReacquisitionAfterInvalidation();
|
||||
TestGpuDirtyBits();
|
||||
|
||||
@@ -2032,6 +2032,58 @@ void TestFmaskLoadSpecialization() {
|
||||
"rebinding FMASK as a texture reused the metadata specialization");
|
||||
}
|
||||
|
||||
void TestGatherLodSamplerValidation() {
|
||||
for (const bool explicit_lod : {false, true}) {
|
||||
for (const bool reverse : {false, true}) {
|
||||
Fixture fixture;
|
||||
std::array<Value, 8> image_words;
|
||||
std::array<Value, 4> sampler_words;
|
||||
for (uint32_t i = 0; i < image_words.size(); i++)
|
||||
image_words[i] = fixture.UserData(i);
|
||||
for (uint32_t i = 0; i < sampler_words.size(); i++)
|
||||
sampler_words[i] = fixture.UserData(i + 8);
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
const bool lod = explicit_lod && i == (reverse ? 0u : 1u);
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.image_dimension = Decoder::ImageDimension::Dim2D;
|
||||
memory.image_sample_flags = lod ? Decoder::ImageSampleFlagLod
|
||||
: Decoder::ImageSampleFlagLevelZero;
|
||||
fixture.Emit(ValueOpcode::ImageGatherRaw,
|
||||
{fixture.Image(image_words), fixture.Sampler(sampler_words),
|
||||
fixture.ImageAddress(), image_words[1], image_words[3],
|
||||
sampler_words[1], sampler_words[2]},
|
||||
fixture.AddMemory(memory, i * 4));
|
||||
}
|
||||
fixture.PlanAndTrack();
|
||||
auto plan = ExtractResourcePlan(fixture.program);
|
||||
Check(plan.info.samplers.size() == 1 &&
|
||||
plan.info.samplers[0].gather_lod == explicit_lod,
|
||||
"shared sampler lost explicit gather validation or restricted LZ gather");
|
||||
std::array<uint32_t, 12> user_data{};
|
||||
user_data[0] = 0x1000u;
|
||||
user_data[1] = static_cast<uint32_t>(
|
||||
Libs::Graphics::Prospero::BufferFormat::k32Float) << 20u;
|
||||
user_data[3] = Libs::Graphics::DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Libs::Graphics::Prospero::ImageType::kColor2D) << 28u);
|
||||
user_data[9] = 0xfff000u;
|
||||
constexpr std::array sampler_controls{
|
||||
0u, 0x4001u, 1u << 26u, (1u << 26u) | 256u,
|
||||
(1u << 26u) | (1u << 14u), 2u << 26u, 3u << 26u};
|
||||
for (const auto control : sampler_controls) {
|
||||
user_data[10] = control;
|
||||
ResourceSnapshot snapshot;
|
||||
ResourceSpecialization specialization;
|
||||
const bool supported = !explicit_lod || (control >> 26u) == 0 ||
|
||||
control == (1u << 26u);
|
||||
Check(MaterializeResources(plan, {.user_data = user_data}, snapshot,
|
||||
specialization) == supported,
|
||||
"explicit gather accepted an unsupported sampler or rejected a valid one");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestDynamicStorageMipTracking() {
|
||||
Fixture fixture;
|
||||
std::array<Value, 8> image_words;
|
||||
@@ -2067,7 +2119,7 @@ void TestDynamicStorageMipTracking() {
|
||||
const auto &images = fixture.program.info.images;
|
||||
Check(images.size() == 2 && images[0].mip_mode == ImageMipMode::None &&
|
||||
images[0].mip_count == 1 &&
|
||||
images[1].mip_mode == ImageMipMode::DynamicStorage &&
|
||||
images[1].mip_mode == ImageMipMode::Dynamic &&
|
||||
images[1].mip_count == 1,
|
||||
"storage mip writes did not share one dynamic logical resource");
|
||||
Check(plain.first.Instruction()->Flags<uint32_t>() == 0 &&
|
||||
@@ -3139,15 +3191,15 @@ void TestShaderInfoAndBindingLayout() {
|
||||
void TestImageBindingAbi() {
|
||||
using NumericClass = Libs::Graphics::Prospero::TextureNumericClass;
|
||||
|
||||
Check(ImageBindingCount == 43u &&
|
||||
Check(ImageBindingCount == 48u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Buffers) == 0u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 44u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 45u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 46u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 47u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 48u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 49u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 50u,
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Gds) == 50u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::BdaPagetable) == 51u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FaultBuffer) == 52u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::FlattenedSrt) == 53u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::ShaderData) == 54u &&
|
||||
static_cast<uint32_t>(DescriptorBindingKind::Count) == 55u,
|
||||
"native descriptor binding anchors changed");
|
||||
|
||||
const std::array sampled_dimensions{
|
||||
@@ -3170,12 +3222,13 @@ void TestImageBindingAbi() {
|
||||
uint32_t index = 0;
|
||||
const auto CheckBinding =
|
||||
[&](ImageResourceClass resource_class, NumericClass numeric_class,
|
||||
Decoder::ImageDimension dimension, bool atomic, bool comparison = false) {
|
||||
Decoder::ImageDimension dimension, bool atomic, bool comparison = false, bool atomic64 = false) {
|
||||
ImageResource image;
|
||||
image.resource_class = resource_class;
|
||||
image.numeric_class = numeric_class;
|
||||
image.dimension = dimension;
|
||||
image.atomic = atomic;
|
||||
image.atomic64 = atomic64;
|
||||
image.depth_compare = comparison;
|
||||
const auto kind = DescriptorBindingForImage(image);
|
||||
Check(kind.has_value() &&
|
||||
@@ -3210,6 +3263,10 @@ void TestImageBindingAbi() {
|
||||
CheckBinding(ImageResourceClass::Storage, NumericClass::Uint, dimension,
|
||||
true);
|
||||
}
|
||||
for (const auto dimension : storage_dimensions) {
|
||||
CheckBinding(ImageResourceClass::Storage, NumericClass::Uint, dimension,
|
||||
true, false, true);
|
||||
}
|
||||
Check(index == ImageBindingCount, "image descriptor ABI case count changed");
|
||||
|
||||
const auto Invalid = [](ImageResource image) {
|
||||
@@ -3407,6 +3464,7 @@ int main() {
|
||||
Run("images and samplers", TestImagesSamplersAndAliases);
|
||||
Run("SampleAdjust sampler scratch", TestSampleAdjustSamplerScratch);
|
||||
Run("FMASK load specialization", TestFmaskLoadSpecialization);
|
||||
Run("gather LOD sampler validation", TestGatherLodSamplerValidation);
|
||||
Run("dynamic storage mips", TestDynamicStorageMipTracking);
|
||||
Run("invariant indirect images", TestInvariantIndirectImageMaterialization);
|
||||
Run("guarded direct image table", TestGuardedDirectImageTable);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1309,7 +1309,8 @@ void TestSpirvRequirementsAnalysis() {
|
||||
block->AppendNewInst(ValueOpcode::ImageQueryLod,
|
||||
{Value(0u), Value(0u), Value(0u)});
|
||||
block->AppendNewInst(ValueOpcode::ImageGatherRaw,
|
||||
{Value(0u), Value(0u), Value(0u)});
|
||||
{Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u), Value(0u), Value(0u)});
|
||||
auto &shared = block->AppendNewInst(ValueOpcode::LoadSharedU32,
|
||||
{Value(0u), Value(true)});
|
||||
shared.SetFlags(MemoryFlags{.index = 0});
|
||||
@@ -4269,6 +4270,38 @@ void TestScalarAshrI64Decoder() {
|
||||
"S_ASHR_I64 is missing from the decoded dump");
|
||||
}
|
||||
|
||||
void TestImageAtomicWidthDecoder() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
// Captured PPSA29343 pixel shader: image_atomic_umax v[2:3], v[0:1], s[0:7].
|
||||
const uint32_t captured[] = {0xf05c0308u, 0x00000200u};
|
||||
Instruction decoded;
|
||||
DecodeInstruction(captured, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::IMAGE_ATOMIC_UMAX &&
|
||||
decoded.data_bits == 64u && decoded.data_dwords == 2u &&
|
||||
decoded.dst.reg == 2u && decoded.dmask == 3u && !decoded.glc,
|
||||
"captured image atomic lost its 64-bit data width");
|
||||
|
||||
for (const auto opcode : {0x10u, 0x17u}) {
|
||||
for (uint32_t mask = 0; mask < 16u; ++mask) {
|
||||
const uint32_t words[] = {EncodeMimg0(opcode, mask), captured[1]};
|
||||
DecodeInstruction(words, 0, decoded);
|
||||
const bool compare_swap = opcode == 0x10u;
|
||||
const bool supported = mask == (compare_swap ? 3u : 1u) ||
|
||||
(!compare_swap && mask == 3u);
|
||||
Check((decoded.opcode != Opcode::UNSUPPORTED) == supported,
|
||||
"image atomic accepted an invalid or unsupported width mask");
|
||||
if (supported) {
|
||||
Check(decoded.data_bits == (!compare_swap && mask == 3u ? 64u : 32u),
|
||||
"image atomic DMASK selected the wrong data width");
|
||||
}
|
||||
}
|
||||
}
|
||||
const uint32_t unsupported[] = {EncodeMimg0(0x11u, 3u), captured[1]};
|
||||
DecodeInstruction(unsupported, 0, decoded);
|
||||
Check(decoded.opcode == Opcode::UNSUPPORTED && decoded.data_bits == 64u,
|
||||
"unsupported 64-bit image atomic silently decoded as 32-bit");
|
||||
}
|
||||
|
||||
void TestNewShaderDecoderArchitecture() {
|
||||
using namespace ShaderRecompiler::Decoder;
|
||||
|
||||
@@ -14301,6 +14334,7 @@ int main() {
|
||||
// ShaderRecompilerComputeTests; keep the distinct decoder contract checks
|
||||
// here.
|
||||
TestScalarAshrI64Decoder();
|
||||
TestImageAtomicWidthDecoder();
|
||||
TestNewShaderDecoderArchitecture();
|
||||
TestImageAddressOperands();
|
||||
TestSopkCompareImmediateExtension();
|
||||
|
||||
Reference in New Issue
Block a user