mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
a305a6c1c6
Resolve PS5 5551 textures with alpha in bit 15 and compose host channel order with guest selectors. Share the existing render-target mapping with sampled textures so packed layouts cannot diverge. Add native GPU readback coverage for 5551 and 4444, arbitrary selectors, constants, and existing 1555 render-target mapping. Verified PPSA24515 through launch.json with Vulkan validation, advancing scenes and over 12,700 guest flips. Independent correctness and cleanliness audits passed.
29648 lines
1.3 MiB
Plaintext
29648 lines
1.3 MiB
Plaintext
#include "common/assert.h"
|
|
#include "common/emulatorConfig.h"
|
|
#include "common/hostException.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/subsystems.h"
|
|
#include "common/threads.h"
|
|
#include "gpu_test_shaders/gpu_test_ms_depth_spv.h"
|
|
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
|
|
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
|
|
#include "graphics/guest_gpu/gpu_defs.h"
|
|
#include "graphics/guest_gpu/gpu_format.h"
|
|
#include "graphics/guest_gpu/graphicsRun.h"
|
|
#include "graphics/guest_gpu/hardwareContext.h"
|
|
#include "graphics/guest_gpu/pm4.h"
|
|
#include "graphics/guest_gpu/tile.h"
|
|
#include "graphics/host_gpu/hostMemory.h"
|
|
#include "graphics/host_gpu/memoryTracker.h"
|
|
#include "graphics/host_gpu/pageManager.h"
|
|
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
|
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
|
|
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
|
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
|
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
|
#include "graphics/host_gpu/renderer/image/blitHelper.h"
|
|
#include "graphics/host_gpu/renderer/image/image.h"
|
|
#include "graphics/host_gpu/renderer/image/imageView.h"
|
|
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
|
#include "graphics/host_gpu/renderer/image/tiler.h"
|
|
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
|
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
|
#include "graphics/host_gpu/renderer/debug.h"
|
|
#include "graphics/host_gpu/renderer/render.h"
|
|
#include "graphics/host_gpu/renderer/renderContext.h"
|
|
#include "graphics/host_gpu/renderer/renderDraw.h"
|
|
#include "graphics/host_gpu/renderer/renderTarget.h"
|
|
#include "graphics/host_gpu/renderer/sync.h"
|
|
#include "graphics/host_gpu/vulkanCommon.h"
|
|
#include "graphics/presentation/window/windowInternal.h"
|
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
|
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
|
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
|
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
|
#include "graphics/shader/rectListShader.h"
|
|
#include "graphics/shader/shader.h"
|
|
#include "graphics/shader/shaderCompiler.h"
|
|
#include "kernel/eventQueue.h"
|
|
#include "kernel/memory.h"
|
|
#include "libs/agc.h"
|
|
#include "libs/dialog.h"
|
|
#include "libs/errno.h"
|
|
#include "spirv-tools/libspirv.hpp"
|
|
|
|
#if __has_include("graphics/host_gpu/renderer/renderTargetBarriers.h")
|
|
#error "legacy render-target barrier API must remain deleted"
|
|
#endif
|
|
|
|
#if __has_include("graphics/host_gpu/transfer.h")
|
|
#error "legacy transfer facade must remain deleted"
|
|
#endif
|
|
|
|
#if __has_include("graphics/host_gpu/renderer/framebufferCache.h")
|
|
#error "classic framebuffer/render-pass cache must remain deleted"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <bit>
|
|
#include <chrono>
|
|
#include <cinttypes>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <numeric>
|
|
#include <optional>
|
|
#include <semaphore>
|
|
#include <set>
|
|
#include <span>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
#include <cerrno>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#undef min
|
|
#undef max
|
|
#endif
|
|
|
|
namespace Libs::Graphics {
|
|
|
|
template <typename Cache>
|
|
concept HasGetDownloadBuffer =
|
|
requires(Cache &cache) { cache.GetDownloadBuffer(uint64_t{1}); };
|
|
static_assert(!HasGetDownloadBuffer<BufferCache>);
|
|
static_assert(std::same_as<decltype(&BufferCache::GetBuffer),
|
|
Buffer &(BufferCache::*)(BufferId)>);
|
|
static_assert(std::same_as<decltype(&TextureCache::GetImage),
|
|
Image &(TextureCache::*)(ImageId)>);
|
|
|
|
template <typename Cache>
|
|
concept HasSynchronizeImageToBuffer = requires(Cache &cache) {
|
|
cache.SynchronizeImageToBuffer(uint64_t{1}, uint64_t{1});
|
|
};
|
|
template <typename Cache>
|
|
concept HasObtainBufferForImageCopy = requires(Cache &cache) {
|
|
cache.ObtainBufferForImageCopy(uint64_t{1}, uint64_t{1});
|
|
};
|
|
template <typename Cache>
|
|
concept HasObtainBufferForImageWrite = requires(Cache &cache) {
|
|
cache.ObtainBufferForImageWrite(uint64_t{1}, uint64_t{1});
|
|
};
|
|
template <typename Cache>
|
|
concept HasDiscardGpuDirtyBytes = requires(Cache &cache) {
|
|
cache.DiscardGpuDirtyBytes(uint64_t{1}, uint64_t{1});
|
|
};
|
|
static_assert(!HasSynchronizeImageToBuffer<TextureCache>);
|
|
static_assert(!HasObtainBufferForImageCopy<BufferCache>);
|
|
static_assert(!HasObtainBufferForImageWrite<BufferCache>);
|
|
static_assert(!HasDiscardGpuDirtyBytes<BufferCache>);
|
|
|
|
template <typename Backing>
|
|
concept HasLegacyImageLayout = requires(Backing &backing) { backing.layout; };
|
|
static_assert(!HasLegacyImageLayout<VulkanImage>);
|
|
|
|
template <typename Manager>
|
|
concept HasPolicyDetileImage = requires { &Manager::DetileImage; };
|
|
template <typename Manager>
|
|
concept HasPolicyTileImageBacking = requires { &Manager::TileImageBacking; };
|
|
static_assert(!HasPolicyDetileImage<TileManager>);
|
|
static_assert(!HasPolicyTileImageBacking<TileManager>);
|
|
static_assert(BlitHelper::ColorToMsDepthLayout ==
|
|
vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
|
|
|
struct BufferCacheTestAccess {
|
|
static_assert(std::same_as<decltype(BufferCache::m_slot_buffers),
|
|
Common::SlotVector<Buffer>>);
|
|
|
|
static void SetGarbageCollectionThresholds(BufferCache &cache,
|
|
uint64_t trigger,
|
|
uint64_t critical) {
|
|
cache.m_trigger_gc_memory = trigger;
|
|
cache.m_critical_gc_memory = critical;
|
|
}
|
|
|
|
static StreamBuffer &DownloadBuffer(BufferCache &cache) {
|
|
return cache.m_download_buffer;
|
|
}
|
|
|
|
static bool SynchronizeBufferFromImage(BufferCache &cache, Buffer &buffer,
|
|
uint64_t address, uint64_t size) {
|
|
return cache.SynchronizeBufferFromImage(buffer, address, size);
|
|
}
|
|
|
|
static BufferId PageOwner(const BufferCache &cache, uint64_t address) {
|
|
const auto *owner = cache.m_page_table.Find(
|
|
address >> BufferCache::PageTable::kPageBits);
|
|
return owner == nullptr ? BufferId{} : *owner;
|
|
}
|
|
|
|
static bool IsBufferAllocated(const BufferCache &cache, BufferId id) {
|
|
return cache.m_slot_buffers.is_allocated(id);
|
|
}
|
|
};
|
|
|
|
struct StreamBufferTestAccess {
|
|
static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t &size,
|
|
uint64_t &alignment) {
|
|
return StreamBuffer::NormalizeReservation(coherent, atom, size, alignment);
|
|
}
|
|
};
|
|
|
|
struct ImageTestAccess {
|
|
static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
|
|
uint64_t capacity) {
|
|
return Image::CopyRows(row_size, rows, capacity);
|
|
}
|
|
};
|
|
|
|
struct TileManagerTestAccess {
|
|
static uint32_t ConversionRows(uint64_t offset, uint64_t row_stride,
|
|
uint64_t active, uint32_t remaining,
|
|
uint64_t alignment, uint64_t max_range,
|
|
uint32_t max_groups) {
|
|
return TileManager::ConversionRows(offset, row_stride, active, remaining,
|
|
alignment, max_range, max_groups);
|
|
}
|
|
};
|
|
|
|
struct TextureCacheTestAccess {
|
|
static_assert(std::same_as<decltype(TextureCache::m_slot_images),
|
|
Common::SlotVector<Image>>);
|
|
static_assert(TextureCache::ImagePageTable::kPageBits == 20);
|
|
static_assert(TextureCache::ImagePageTable::kAddressSpaceBits == 40);
|
|
static_assert(TextureCache::ImagePageTable::kFirstLevelBits == 10);
|
|
|
|
static std::unique_lock<TrackingSpinLock> Lock(TextureCache &cache) {
|
|
return std::unique_lock(cache.m_lock);
|
|
}
|
|
|
|
static void ClearImage(TextureCache &cache, CommandBuffer &command, ImageId id,
|
|
const vk::ImageSubresourceRange &range,
|
|
const vk::ClearValue &clear) {
|
|
auto lock = Lock(cache);
|
|
cache.ClearImage(command, id, range, clear);
|
|
}
|
|
|
|
static void ConfigureGarbageCollection(TextureCache &cache,
|
|
std::span<const ImageId> oldest,
|
|
uint64_t tick, uint64_t pressure) {
|
|
cache.m_trigger_gc_memory = 0;
|
|
cache.m_pressure_gc_memory = pressure;
|
|
cache.m_critical_gc_memory = UINT64_MAX;
|
|
cache.m_gc_tick = tick;
|
|
std::vector<ImageId> live;
|
|
cache.m_lru_cache = {};
|
|
cache.m_slot_images.ForEach([&](ImageId id, Image &image) {
|
|
if (image.registered) {
|
|
image.tick_accessed_last = cache.m_scheduler.CurrentTick();
|
|
live.push_back(id);
|
|
}
|
|
});
|
|
for (const auto id : oldest) {
|
|
const auto owner = cache.m_slot_images.try_get(id);
|
|
if (owner != nullptr && owner->registered) {
|
|
owner->tick_accessed_last = 0;
|
|
owner->lru_id = cache.m_lru_cache.Insert(id, 0);
|
|
}
|
|
}
|
|
for (const auto id : live) {
|
|
if (std::ranges::find(oldest, id) == oldest.end()) {
|
|
cache.m_slot_images[id].lru_id = cache.m_lru_cache.Insert(id, tick);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool Contains(const TextureCache &cache, ImageId id) {
|
|
const auto owner = cache.m_slot_images.try_get(id);
|
|
return owner != nullptr && owner->registered;
|
|
}
|
|
|
|
static std::vector<ImageId> FindImages(TextureCache &cache, uint64_t address,
|
|
uint64_t size, bool page_overlap) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
const auto found = cache.FindImagesInRegion(address, size, page_overlap);
|
|
std::vector<ImageId> result;
|
|
result.reserve(found.size());
|
|
for (const auto id : found) {
|
|
result.push_back(id);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static size_t PageOwnerCount(TextureCache &cache, uint64_t address) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
const auto *owners = cache.m_image_page_table.Find(static_cast<size_t>(
|
|
address >> TextureCache::ImagePageTable::kPageBits));
|
|
return owners == nullptr ? 0 : owners->size();
|
|
}
|
|
|
|
static size_t OwnedPageCount(TextureCache &cache, uint64_t address,
|
|
uint64_t size, ImageId id) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
TextureCache::ImagePageTable::PageRange pages{};
|
|
if (!TextureCache::ImagePageTable::TryGetPageRange(address, size, pages)) {
|
|
return 0;
|
|
}
|
|
size_t count = 0;
|
|
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
|
const auto *owners = cache.m_image_page_table.Find(page);
|
|
count += owners != nullptr && owners->Contains(id) ? 1 : 0;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void AddPageOwner(TextureCache &cache, uint64_t address, ImageId id) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
cache
|
|
.m_image_page_table[static_cast<size_t>(
|
|
address >> TextureCache::ImagePageTable::kPageBits)]
|
|
.push_back(id);
|
|
}
|
|
|
|
static bool RemovePageOwner(TextureCache &cache, uint64_t address,
|
|
ImageId id) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
auto *owners = cache.m_image_page_table.Find(static_cast<size_t>(
|
|
address >> TextureCache::ImagePageTable::kPageBits));
|
|
return owners != nullptr && owners->Erase(id);
|
|
}
|
|
|
|
static void SetQueryEpoch(TextureCache &cache, uint32_t epoch) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
cache.m_image_query_epoch = epoch;
|
|
}
|
|
|
|
static uint32_t QueryEpoch(TextureCache &cache) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
return cache.m_image_query_epoch;
|
|
}
|
|
|
|
static ImageId InsertImage(TextureCache &cache, const ImageInfo &info) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
return cache.InsertImage(info);
|
|
}
|
|
|
|
static void DeleteImage(TextureCache &cache, ImageId id) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
cache.DeleteImage(id);
|
|
}
|
|
|
|
static const Image *Owner(const TextureCache &cache, ImageId id) {
|
|
return cache.m_slot_images.try_get(id);
|
|
}
|
|
|
|
static bool PendingDownload(const TextureCache &cache, ImageId id) {
|
|
return cache.m_download_images.contains(id);
|
|
}
|
|
|
|
static size_t NullImageCount(const TextureCache &cache) {
|
|
return cache.m_null_images.size();
|
|
}
|
|
|
|
static void TrackDownload(TextureCache &cache, ImageId id) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
cache.TrackImageDownload(id, cache.m_slot_images[id]);
|
|
}
|
|
|
|
static void AssociateStencil(TextureCache &cache, ImageId depth,
|
|
GuestRange stencil) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
cache.AssociateStencil(depth, stencil);
|
|
}
|
|
|
|
static void SetLinearReadback(TextureCache &cache, bool enabled) {
|
|
cache.m_readback_linear_images = enabled;
|
|
}
|
|
|
|
static std::pair<uint8_t *, uint64_t>
|
|
MapDownload(TextureCache &cache, uint64_t size, uint64_t alignment) {
|
|
auto &download =
|
|
cache.m_buffer_cache.GetUtilityBuffer(MemoryUsage::Download);
|
|
auto mapping = download.Map(size, std::max<uint64_t>(alignment, 4));
|
|
if (mapping.first != nullptr) {
|
|
download.Commit();
|
|
}
|
|
return mapping;
|
|
}
|
|
|
|
static bool TryDownload(TextureCache &cache, ImageId id) {
|
|
return cache.TryDownloadImage(id);
|
|
}
|
|
|
|
static void RegisterHtileMeta(TextureCache &cache, uint64_t address) {
|
|
std::lock_guard lock(cache.m_lock);
|
|
auto &metadata = cache.m_surface_metas[address];
|
|
metadata.type = TextureCache::MetaDataInfo::Type::HTile;
|
|
metadata.clear_mask = 0;
|
|
}
|
|
|
|
static TileManager &Tiler(TextureCache &cache) { return cache.m_tiler; }
|
|
};
|
|
|
|
struct RenderExecutorTestAccess {
|
|
static bool TryConsumeComputeImageClear(RenderExecutor &executor,
|
|
const ShaderComputeInputInfo &input, CommandBuffer &command,
|
|
uint32_t x, uint32_t y, uint32_t z, uint32_t mode) {
|
|
return executor.TryConsumeComputeImageClear(input, command, x, y, z, mode);
|
|
}
|
|
|
|
static bool TryConsumeComputeMetaClear(RenderExecutor &executor,
|
|
const ShaderComputeInputInfo &input,
|
|
const CommandBuffer &buffer) {
|
|
return executor.TryConsumeComputeMetaClear(input, buffer);
|
|
}
|
|
|
|
static TextureBinding
|
|
ResolveTexture(RenderExecutor &executor,
|
|
const ShaderRecompiler::IR::ImageResource &resource,
|
|
const ShaderRecompiler::IR::DescriptorValue &value) {
|
|
return executor.ResolveTexture(resource, value);
|
|
}
|
|
|
|
static auto PrepareGraphicsBindings(RenderExecutor &executor,
|
|
const ShaderStageRuntime &vertex,
|
|
const ShaderStageRuntime &pixel,
|
|
bool pixel_active) {
|
|
return executor.PrepareGraphicsBindings(vertex, pixel, pixel_active);
|
|
}
|
|
|
|
static PipelineCache::Pipeline
|
|
CreateDescriptorPipeline(RenderExecutor &executor,
|
|
std::span<PreparedBindings *const> stages) {
|
|
std::vector<vk::DescriptorSetLayoutBinding> layout_bindings;
|
|
bool compute = false;
|
|
for (const auto *prepared : stages) {
|
|
const auto &program = *prepared->runtime->program;
|
|
compute |= program.stage == ShaderType::Compute;
|
|
const auto shader_stage = program.stage == ShaderType::Vertex
|
|
? vk::ShaderStageFlagBits::eVertex
|
|
: program.stage == ShaderType::Pixel
|
|
? vk::ShaderStageFlagBits::eFragment
|
|
: vk::ShaderStageFlagBits::eCompute;
|
|
for (const auto &binding : program.bindings.descriptors) {
|
|
layout_bindings.push_back(
|
|
{ShaderRecompiler::IR::NativeBinding(program.stage, binding.kind),
|
|
NativeDescriptorType(binding.kind), NativeDescriptorCount(binding),
|
|
shader_stage});
|
|
}
|
|
}
|
|
vk::DescriptorSetLayoutCreateInfo descriptor_info{};
|
|
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
|
descriptor_info.flags =
|
|
vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
|
descriptor_info.bindingCount =
|
|
static_cast<uint32_t>(layout_bindings.size());
|
|
descriptor_info.pBindings = layout_bindings.data();
|
|
PipelineCache::Pipeline pipeline{};
|
|
auto &device = executor.m_context.GetGraphics().device;
|
|
EXIT_IF(device.createDescriptorSetLayout(&descriptor_info, nullptr,
|
|
&pipeline.descriptor_set_layout) !=
|
|
vk::Result::eSuccess);
|
|
|
|
vk::PipelineLayoutCreateInfo pipeline_info{};
|
|
pipeline_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
|
pipeline_info.setLayoutCount = 1;
|
|
pipeline_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
|
const vk::PushConstantRange push_range {
|
|
compute ? vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute}
|
|
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eVertex |
|
|
vk::ShaderStageFlagBits::eFragment},
|
|
0, ShaderRecompiler::IR::NativePushConstantSize};
|
|
pipeline_info.pushConstantRangeCount = 1;
|
|
pipeline_info.pPushConstantRanges = &push_range;
|
|
EXIT_IF(device.createPipelineLayout(&pipeline_info, nullptr,
|
|
&pipeline.pipeline_layout) !=
|
|
vk::Result::eSuccess);
|
|
pipeline.uses_push_descriptors = true;
|
|
return pipeline;
|
|
}
|
|
|
|
static PipelineCache::Pipeline CommitBindings(RenderExecutor &executor,
|
|
CommandBuffer &buffer,
|
|
PreparedBindings &bindings) {
|
|
std::array<PreparedBindings *, 1> stages{&bindings};
|
|
auto pipeline = CreateDescriptorPipeline(executor, stages);
|
|
const auto bind_point = bindings.runtime->program->stage == ShaderType::Compute
|
|
? vk::PipelineBindPoint::eCompute
|
|
: vk::PipelineBindPoint::eGraphics;
|
|
executor.CommitBindings(buffer, bind_point, pipeline, stages);
|
|
return pipeline;
|
|
}
|
|
|
|
static PipelineCache::Pipeline CommitBindings(RenderExecutor &executor,
|
|
CommandBuffer &buffer,
|
|
PreparedBindings &vertex,
|
|
PreparedBindings &pixel) {
|
|
std::array<PreparedBindings *, 2> stages{&vertex, &pixel};
|
|
auto pipeline = CreateDescriptorPipeline(executor, stages);
|
|
executor.CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
|
|
stages);
|
|
return pipeline;
|
|
}
|
|
|
|
static void CommitBindings(RenderExecutor &executor, CommandBuffer &buffer,
|
|
const PipelineCache::Pipeline &pipeline,
|
|
PreparedBindings &vertex, PreparedBindings &pixel) {
|
|
std::array<PreparedBindings *, 2> stages{&vertex, &pixel};
|
|
executor.CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline,
|
|
stages);
|
|
}
|
|
|
|
static void DestroyDescriptorPipelines(
|
|
RenderExecutor &executor,
|
|
std::span<const PipelineCache::Pipeline> pipelines) {
|
|
auto &device = executor.m_context.GetGraphics().device;
|
|
for (const auto &pipeline : pipelines) {
|
|
device.destroyPipelineLayout(pipeline.pipeline_layout, nullptr);
|
|
device.destroyDescriptorSetLayout(pipeline.descriptor_set_layout,
|
|
nullptr);
|
|
}
|
|
}
|
|
|
|
static void ResolveRenderDepthTarget(RenderExecutor &executor,
|
|
CommandBuffer &buffer,
|
|
RenderDepthInfo &depth) {
|
|
executor.ResolveRenderDepthTarget(buffer, depth);
|
|
}
|
|
|
|
static bool DepthStencilCopy(RenderExecutor &executor, CommandBuffer &buffer) {
|
|
return executor.DepthStencilCopy(buffer);
|
|
}
|
|
|
|
static void ResolveRenderColorTarget(RenderExecutor &executor,
|
|
CommandBuffer &buffer,
|
|
RenderColorInfo &color, uint32_t slot) {
|
|
executor.ResolveRenderColorTarget(buffer, color, 0, slot);
|
|
}
|
|
|
|
static void BindRenderTarget(RenderExecutor &executor, ImageId id) {
|
|
executor.BindRenderTarget(id);
|
|
}
|
|
|
|
static RenderState AcquireRenderTargets(RenderExecutor &executor,
|
|
CommandBuffer &buffer,
|
|
RenderColorInfo *colors,
|
|
uint32_t color_count,
|
|
RenderDepthInfo &depth,
|
|
const std::optional<PreparedBindings> &pixel = std::nullopt) {
|
|
return executor.AcquireRenderTargets(buffer, colors, color_count, depth, pixel);
|
|
}
|
|
|
|
static void ResetBindings(RenderExecutor &executor) {
|
|
executor.ResetBindings();
|
|
}
|
|
|
|
static bool BoundImagesInOrder(const RenderExecutor &executor, ImageId first,
|
|
ImageId second) {
|
|
return executor.m_bound_images.size() == 2 &&
|
|
executor.m_bound_images[0] == first &&
|
|
executor.m_bound_images[1] == second;
|
|
}
|
|
};
|
|
|
|
namespace {
|
|
|
|
using u32 = uint32_t;
|
|
using BindingType = TextureCache::BindingType;
|
|
using ImageDesc = TextureCache::ImageDesc;
|
|
using ShaderOpcode = ShaderRecompiler::Decoder::Opcode;
|
|
|
|
constexpr u32 InlineU32(u32 value) { return 128u + value; }
|
|
|
|
constexpr u32 Vgpr(u32 reg) { return 256u + reg; }
|
|
|
|
constexpr u32 EncodeSMovB32(u32 dst, u32 src) {
|
|
return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) | (0x03u << 8u) |
|
|
(src & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSop1(u32 opcode, u32 dst, u32 src) {
|
|
return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) |
|
|
((opcode & 0xffu) << 8u) | (src & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSop2(u32 opcode, u32 dst, u32 src0, u32 src1) {
|
|
return 0x80000000u | ((opcode & 0x7fu) << 23u) | ((dst & 0x7fu) << 16u) |
|
|
((src1 & 0xffu) << 8u) | (src0 & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSopc(u32 opcode, u32 src0, u32 src1) {
|
|
return 0x80000000u | (0x7eu << 23u) | ((opcode & 0x7fu) << 16u) |
|
|
((src1 & 0xffu) << 8u) | (src0 & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSopp(u32 opcode, u32 simm = 0) {
|
|
return 0x80000000u | (0x7fu << 23u) | ((opcode & 0x7fu) << 16u) |
|
|
(simm & 0xffffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSopk(u32 opcode, u32 dst, u32 imm) {
|
|
return 0x80000000u | (((opcode + 0x60u) & 0x7fu) << 23u) |
|
|
((dst & 0x7fu) << 16u) | (imm & 0xffffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop1(u32 opcode, u32 dst, u32 src0) {
|
|
return (0x3fu << 25u) | ((dst & 0xffu) << 17u) | ((opcode & 0xffu) << 9u) |
|
|
(src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop1Sdwa(u32 src0, u32 dst_sel = 6, u32 dst_u = 0,
|
|
u32 src0_sel = 6, u32 src0_sext = 0,
|
|
u32 src0_neg = 0, u32 src0_abs = 0, u32 s0 = 0,
|
|
u32 clamp = 0, u32 omod = 0) {
|
|
return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
|
|
((clamp & 0x1u) << 13u) | ((omod & 0x3u) << 14u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u);
|
|
}
|
|
|
|
constexpr u32 EncodeVop1Dpp(u32 src0, u32 dpp_ctrl = 0, u32 row_mask = 0xf,
|
|
u32 bank_mask = 0xf) {
|
|
return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) |
|
|
((bank_mask & 0xfu) << 24u) | ((row_mask & 0xfu) << 28u);
|
|
}
|
|
|
|
constexpr u32 EncodeVop2(u32 opcode, u32 dst, u32 src0, u32 src1) {
|
|
return ((opcode & 0x3fu) << 25u) | ((dst & 0xffu) << 17u) |
|
|
((src1 & 0xffu) << 9u) | (src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop2Sdwa(u32 src0, u32 dst_sel = 6, u32 dst_u = 0,
|
|
u32 src0_sel = 6, u32 src1_sel = 6,
|
|
u32 src0_sext = 0, u32 src1_sext = 0,
|
|
u32 src0_neg = 0, u32 src0_abs = 0,
|
|
u32 src1_neg = 0, u32 src1_abs = 0, u32 s0 = 0,
|
|
u32 s1 = 0, u32 clamp = 0, u32 omod = 0) {
|
|
return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
|
|
((clamp & 0x1u) << 13u) | ((omod & 0x3u) << 14u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
|
|
((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) |
|
|
((src1_abs & 0x1u) << 29u) | ((s1 & 0x1u) << 31u);
|
|
}
|
|
|
|
constexpr u32 EncodeVop2Dpp(u32 src0, u32 dpp_ctrl = 0, u32 row_mask = 0xf,
|
|
u32 bank_mask = 0xf, u32 src0_neg = 0,
|
|
u32 src0_abs = 0, u32 src1_neg = 0,
|
|
u32 src1_abs = 0) {
|
|
return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((src1_neg & 0x1u) << 22u) | ((src1_abs & 0x1u) << 23u) |
|
|
((bank_mask & 0xfu) << 24u) | ((row_mask & 0xfu) << 28u);
|
|
}
|
|
|
|
constexpr u32 EncodeVop3Word0(u32 opcode, u32 dst, u32 abs = 0, u32 op_sel = 0,
|
|
bool clamp = false) {
|
|
return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | ((abs & 0x7u) << 8u) |
|
|
((op_sel & 0xfu) << 11u) | (clamp ? (1u << 15u) : 0u) | (dst & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop3BWord0(u32 opcode, u32 vdst, u32 sdst) {
|
|
return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | ((sdst & 0x7fu) << 8u) |
|
|
(vdst & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop3Word1(u32 src0, u32 src1, u32 src2 = 0, u32 omod = 0,
|
|
u32 neg = 0) {
|
|
return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u) |
|
|
((omod & 0x3u) << 27u) | ((neg & 0x7u) << 29u);
|
|
}
|
|
|
|
constexpr u32 EncodeVop3pWord0(u32 opcode, u32 dst, u32 op_sel_hi = 0,
|
|
u32 op_sel = 0, u32 neg_hi = 0,
|
|
bool clamp = false) {
|
|
return (0x33u << 26u) | ((opcode & 0x7fu) << 16u) | ((neg_hi & 0x7u) << 8u) |
|
|
((op_sel & 0x7u) << 11u) | (((op_sel_hi >> 2u) & 0x1u) << 14u) |
|
|
(clamp ? (1u << 15u) : 0u) | (dst & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVop3pWord1(u32 src0, u32 src1, u32 src2 = 0,
|
|
u32 op_sel_hi = 0, u32 neg = 0) {
|
|
return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u) |
|
|
((op_sel_hi & 0x3u) << 27u) | ((neg & 0x7u) << 29u);
|
|
}
|
|
|
|
constexpr u32 EncodeVopc(u32 opcode, u32 src0, u32 src1) {
|
|
return (0x3eu << 25u) | ((opcode & 0xffu) << 17u) | ((src1 & 0xffu) << 9u) |
|
|
(src0 & 0x1ffu);
|
|
}
|
|
|
|
constexpr u32 EncodeVopcSdwa(u32 src0, u32 sdst = 0, u32 sd = 0,
|
|
u32 src0_sel = 6, u32 src1_sel = 6,
|
|
u32 src0_sext = 0, u32 src1_sext = 0,
|
|
u32 src0_neg = 0, u32 src0_abs = 0,
|
|
u32 src1_neg = 0, u32 src1_abs = 0, u32 s0 = 0,
|
|
u32 s1 = 0) {
|
|
return (src0 & 0xffu) | ((sdst & 0x7fu) << 8u) | ((sd & 0x1u) << 15u) |
|
|
((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) |
|
|
((src0_neg & 0x1u) << 20u) | ((src0_abs & 0x1u) << 21u) |
|
|
((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
|
|
((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) |
|
|
((src1_abs & 0x1u) << 29u) | ((s1 & 0x1u) << 31u);
|
|
}
|
|
|
|
constexpr u32 EncodeMubuf0(u32 opcode, u32 offset = 0, bool idxen = false,
|
|
bool offen = true, bool glc = false) {
|
|
return (0x38u << 26u) | ((opcode & 0x7fu) << 18u) |
|
|
(offen ? (1u << 12u) : 0u) | (idxen ? (1u << 13u) : 0u) |
|
|
(glc ? (1u << 14u) : 0u) | (offset & 0xfffu);
|
|
}
|
|
|
|
constexpr u32 EncodeMubuf1(u32 vdata, u32 srsrc, u32 vaddr, u32 soffset = 128) {
|
|
return ((soffset & 0xffu) << 24u) | ((srsrc & 0x1fu) << 16u) |
|
|
((vdata & 0xffu) << 8u) | (vaddr & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeMtbuf0(u32 opcode, u32 dfmt, u32 nfmt, u32 offset = 0,
|
|
bool idxen = false, bool offen = true) {
|
|
return (0x3au << 26u) | (offset & 0xfffu) | (offen ? (1u << 12u) : 0u) |
|
|
(idxen ? (1u << 13u) : 0u) | ((opcode & 0x7u) << 16u) |
|
|
((dfmt & 0xfu) << 19u) | ((nfmt & 0x7u) << 23u);
|
|
}
|
|
|
|
constexpr u32 EncodeMtbuf1(u32 opcode, u32 vdata, u32 srsrc, u32 vaddr,
|
|
u32 soffset = 128) {
|
|
return (((opcode >> 3u) & 1u) << 21u) | ((soffset & 0xffu) << 24u) |
|
|
((srsrc & 0x1fu) << 16u) | ((vdata & 0xffu) << 8u) | (vaddr & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeSmem0(u32 opcode, u32 dst, u32 sbase = 0) {
|
|
return (0x3du << 26u) | ((opcode & 0xffu) << 18u) | ((dst & 0x7fu) << 6u) |
|
|
(sbase & 0x3fu);
|
|
}
|
|
|
|
constexpr u32 EncodeSmem1(u32 offset, u32 soffset = 0) {
|
|
return (offset & 0x1fffffu) | ((soffset & 0x7fu) << 25u);
|
|
}
|
|
|
|
constexpr u32 EncodeMimg0(u32 opcode, u32 dmask, u32 nsa_dwords = 0,
|
|
bool glc = false, u32 dim = 1, bool r128 = false) {
|
|
return (0x3cu << 26u) | ((opcode >> 7u) & 0x1u) |
|
|
((nsa_dwords & 0x3u) << 1u) | ((dim & 0x7u) << 3u) |
|
|
((dmask & 0xfu) << 8u) | (glc ? (1u << 13u) : 0u) |
|
|
(r128 ? (1u << 15u) : 0u) | ((opcode & 0x7fu) << 18u);
|
|
}
|
|
|
|
constexpr u32 EncodeMimg1(u32 vdata, u32 vaddr, u32 srsrc = 0, u32 ssamp = 0,
|
|
bool a16 = false, bool d16 = false) {
|
|
return ((ssamp & 0x1fu) << 21u) | ((srsrc & 0x1fu) << 16u) |
|
|
((vdata & 0xffu) << 8u) | (vaddr & 0xffu) | (a16 ? (1u << 30u) : 0u) |
|
|
(d16 ? (1u << 31u) : 0u);
|
|
}
|
|
|
|
constexpr u32 EncodeVintrp(u32 opcode, u32 dst, u32 attr, u32 chan, u32 src) {
|
|
return (0x32u << 26u) | ((dst & 0xffu) << 18u) | ((opcode & 0x3u) << 16u) |
|
|
((attr & 0x3fu) << 10u) | ((chan & 0x3u) << 8u) | (src & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeExp0(u32 target, u32 en, bool done = true,
|
|
bool compr = false, bool vm = false) {
|
|
return (0x3eu << 26u) | ((target & 0x3fu) << 4u) | (en & 0xfu) |
|
|
(compr ? (1u << 10u) : 0u) | (done ? (1u << 11u) : 0u) |
|
|
(vm ? (1u << 12u) : 0u);
|
|
}
|
|
|
|
constexpr u32 EncodeExp1(u32 src0, u32 src1, u32 src2, u32 src3) {
|
|
return (src0 & 0xffu) | ((src1 & 0xffu) << 8u) | ((src2 & 0xffu) << 16u) |
|
|
((src3 & 0xffu) << 24u);
|
|
}
|
|
|
|
constexpr u32 EncodeFlat0(u32 opcode, u32 segment, u32 offset = 0) {
|
|
return (0x37u << 26u) | ((opcode & 0x7fu) << 18u) |
|
|
((segment & 0x3u) << 14u) | (offset & 0xfffu);
|
|
}
|
|
|
|
constexpr u32 EncodeFlat1(u32 vdst, u32 saddr, u32 data, u32 addr) {
|
|
return ((vdst & 0xffu) << 24u) | ((saddr & 0x7fu) << 16u) |
|
|
((data & 0xffu) << 8u) | (addr & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeDs0(u32 opcode, u32 offset = 0, bool gds = false) {
|
|
return (0x36u << 26u) | ((opcode & 0xffu) << 18u) | (gds ? (1u << 17u) : 0u) |
|
|
(offset & 0xffffu);
|
|
}
|
|
|
|
constexpr u32 EncodeDs1Ex(u32 vdst, u32 data1, u32 data0, u32 addr) {
|
|
return ((vdst & 0xffu) << 24u) | ((data1 & 0xffu) << 16u) |
|
|
((data0 & 0xffu) << 8u) | (addr & 0xffu);
|
|
}
|
|
|
|
constexpr u32 EncodeDs1(u32 vdst, u32 data0, u32 addr) {
|
|
return EncodeDs1Ex(vdst, 0, data0, addr);
|
|
}
|
|
|
|
void AppendSMovLiteral(std::vector<u32> *code, u32 dst, u32 literal) {
|
|
code->push_back(EncodeSMovB32(dst, 255u));
|
|
code->push_back(literal);
|
|
}
|
|
|
|
void AppendVMovLiteral(std::vector<u32> *code, u32 dst, u32 literal) {
|
|
code->push_back(EncodeVop1(0x01, dst, 255u));
|
|
code->push_back(literal);
|
|
}
|
|
|
|
void AppendVMovU32(std::vector<u32> *code, u32 dst, u32 value) {
|
|
if (value <= 64u) {
|
|
code->push_back(EncodeVop1(0x01, dst, InlineU32(value)));
|
|
return;
|
|
}
|
|
AppendVMovLiteral(code, dst, value);
|
|
}
|
|
|
|
void AppendVop3(std::vector<u32> *code, u32 opcode, u32 dst, u32 src0, u32 src1,
|
|
u32 src2 = 0, u32 abs = 0, u32 op_sel = 0, bool clamp = false,
|
|
u32 omod = 0, u32 neg = 0) {
|
|
code->push_back(EncodeVop3Word0(opcode, dst, abs, op_sel, clamp));
|
|
code->push_back(EncodeVop3Word1(src0, src1, src2, omod, neg));
|
|
}
|
|
|
|
void AppendVop3B(std::vector<u32> *code, u32 opcode, u32 vdst, u32 sdst,
|
|
u32 src0, u32 src1, u32 src2 = 0) {
|
|
code->push_back(EncodeVop3BWord0(opcode, vdst, sdst));
|
|
code->push_back(EncodeVop3Word1(src0, src1, src2));
|
|
}
|
|
|
|
void AppendVop3p(std::vector<u32> *code, u32 opcode, u32 dst, u32 src0,
|
|
u32 src1, u32 src2 = 0, u32 op_sel_hi = 0, u32 op_sel = 0,
|
|
u32 neg_hi = 0, u32 neg = 0) {
|
|
code->push_back(EncodeVop3pWord0(opcode, dst, op_sel_hi, op_sel, neg_hi));
|
|
code->push_back(EncodeVop3pWord1(src0, src1, src2, op_sel_hi, neg));
|
|
}
|
|
|
|
void AppendBufferLoadDword(std::vector<u32> *code, u32 dst_vgpr,
|
|
u32 address_vgpr) {
|
|
code->push_back(EncodeMubuf0(0x0cu));
|
|
code->push_back(EncodeMubuf1(dst_vgpr, 0, address_vgpr));
|
|
}
|
|
|
|
void AppendBufferLoadOpcode(std::vector<u32> *code, u32 opcode, u32 dst_vgpr,
|
|
u32 address_vgpr) {
|
|
code->push_back(EncodeMubuf0(opcode));
|
|
code->push_back(EncodeMubuf1(dst_vgpr, 0, address_vgpr));
|
|
}
|
|
|
|
void AppendBufferStoreDword(std::vector<u32> *code, u32 value_vgpr,
|
|
u32 address_vgpr) {
|
|
code->push_back(EncodeMubuf0(0x1cu));
|
|
code->push_back(EncodeMubuf1(value_vgpr, 12, address_vgpr));
|
|
}
|
|
|
|
void AppendBufferStoreOpcode(std::vector<u32> *code, u32 opcode, u32 value_vgpr,
|
|
u32 address_vgpr, bool glc = false) {
|
|
code->push_back(EncodeMubuf0(opcode, 0, false, true, glc));
|
|
code->push_back(EncodeMubuf1(value_vgpr, 12, address_vgpr));
|
|
}
|
|
|
|
void AppendTBufferLoadOpcode(std::vector<u32> *code, u32 opcode, u32 dst_vgpr,
|
|
u32 address_vgpr) {
|
|
code->push_back(EncodeMtbuf0(opcode, 14, 7));
|
|
code->push_back(EncodeMtbuf1(opcode, dst_vgpr, 0, address_vgpr));
|
|
}
|
|
|
|
constexpr u32 BufferFormat(Prospero::BufferFormat format) {
|
|
return static_cast<uint32_t>(format);
|
|
}
|
|
|
|
void AppendTBufferLoadFormatOpcode(std::vector<u32> *code, u32 opcode,
|
|
u32 dst_vgpr, u32 address_vgpr,
|
|
Prospero::BufferFormat format) {
|
|
const auto value = BufferFormat(format);
|
|
code->push_back(EncodeMtbuf0(opcode, value & 0xfu, (value >> 4u) & 0x7u));
|
|
code->push_back(EncodeMtbuf1(opcode, dst_vgpr, 0, address_vgpr));
|
|
}
|
|
|
|
void AppendTBufferStoreOpcode(std::vector<u32> *code, u32 opcode,
|
|
u32 value_vgpr, u32 address_vgpr) {
|
|
code->push_back(EncodeMtbuf0(opcode, 14, 7));
|
|
code->push_back(EncodeMtbuf1(opcode, value_vgpr, 0, address_vgpr));
|
|
}
|
|
|
|
void AppendSmemLoadOpcode(std::vector<u32> *code, u32 opcode, u32 dst_sgpr,
|
|
u32 byte_offset) {
|
|
code->push_back(EncodeSmem0(opcode, dst_sgpr));
|
|
code->push_back(EncodeSmem1(byte_offset));
|
|
}
|
|
|
|
void AppendStoreVgpr(std::vector<u32> *code, u32 value_vgpr, u32 dword_index) {
|
|
AppendVMovU32(code, 31, dword_index * 4u);
|
|
AppendBufferStoreDword(code, value_vgpr, 31);
|
|
}
|
|
|
|
void AppendStoreVgprAtLaneDwordOffset(std::vector<u32> *code, u32 value_vgpr,
|
|
u32 lane_vgpr, u32 dword_offset) {
|
|
if (dword_offset == 0u) {
|
|
code->push_back(EncodeVop2(0x1a, 31, InlineU32(2), lane_vgpr));
|
|
} else {
|
|
AppendVMovU32(code, 31, dword_offset);
|
|
code->push_back(EncodeVop2(0x25, 31, Vgpr(lane_vgpr), 31));
|
|
code->push_back(EncodeVop2(0x1a, 31, InlineU32(2), 31));
|
|
}
|
|
AppendBufferStoreDword(code, value_vgpr, 31);
|
|
}
|
|
|
|
void AppendStoreSgpr(std::vector<u32> *code, u32 value_sgpr, u32 dword_index) {
|
|
code->push_back(EncodeVop1(0x01, 30, value_sgpr));
|
|
AppendStoreVgpr(code, 30, dword_index);
|
|
}
|
|
|
|
void AppendStoreSgprAtLaneDwordOffset(std::vector<u32> *code, u32 value_sgpr,
|
|
u32 lane_vgpr, u32 dword_offset) {
|
|
code->push_back(EncodeVop1(0x01, 30, value_sgpr));
|
|
AppendStoreVgprAtLaneDwordOffset(code, 30, lane_vgpr, dword_offset);
|
|
}
|
|
|
|
void AppendStoreSgprPair(std::vector<u32> *code, u32 value_sgpr,
|
|
u32 first_dword_index) {
|
|
AppendStoreSgpr(code, value_sgpr, first_dword_index);
|
|
AppendStoreSgpr(code, value_sgpr + 1u, first_dword_index + 1u);
|
|
}
|
|
|
|
void AppendEnd(std::vector<u32> *code) { code->push_back(0xbf810000u); }
|
|
|
|
std::string Hex(u32 value) {
|
|
char buffer[32] = {};
|
|
std::snprintf(buffer, sizeof(buffer), "0x%08" PRIx32, value);
|
|
return buffer;
|
|
}
|
|
|
|
std::string VulkanResultName(vk::Result result) {
|
|
return vk::to_string(result);
|
|
}
|
|
|
|
[[noreturn]] void Fail(const char *shader_name, const char *stage,
|
|
const std::string &message) {
|
|
std::fprintf(stderr, "ShaderRecompilerComputeTests: %s failed at %s: %s\n",
|
|
shader_name, stage, message.c_str());
|
|
std::abort();
|
|
}
|
|
|
|
void Require(const char *shader_name, const char *stage, bool value,
|
|
const std::string &message);
|
|
|
|
void EnsureConfigInitialized() {
|
|
static bool config_initialized = false;
|
|
if (!config_initialized) {
|
|
static Common::Subsystems subsystems;
|
|
Common::InitializeThreads();
|
|
subsystems.Initialize<Config::Lifecycle>();
|
|
Config::ConfigOptions options;
|
|
options.printf_direction = Config::OutputDirection::Silent;
|
|
Config::Load(options);
|
|
subsystems.Initialize<Log::Lifecycle>();
|
|
subsystems.Initialize<Libs::LibKernel::Memory::Lifecycle>();
|
|
config_initialized = true;
|
|
}
|
|
}
|
|
|
|
void RequireVk(const char *shader_name, const char *stage, vk::Result result,
|
|
const char *action) {
|
|
if (result != vk::Result::eSuccess) {
|
|
Fail(shader_name, stage,
|
|
std::string(action) + " returned " + VulkanResultName(result));
|
|
}
|
|
}
|
|
|
|
void Require(const char *shader_name, const char *stage, bool value,
|
|
const std::string &message) {
|
|
if (!value) {
|
|
Fail(shader_name, stage, message);
|
|
}
|
|
}
|
|
|
|
void CheckErrorDialogLifecycle() {
|
|
namespace ErrorDialog = Libs::Dialog::ErrorDialog;
|
|
struct Param {
|
|
int32_t size = 16;
|
|
int32_t error_code = static_cast<int32_t>(0x80550006u);
|
|
int32_t user_id = 1;
|
|
int32_t reserved = 0;
|
|
} param;
|
|
static_assert(sizeof(Param) == 16);
|
|
constexpr int invalid_state = static_cast<int>(0x80ed0005u);
|
|
constexpr int invalid_param = static_cast<int>(0x80ed0003u);
|
|
static std::vector<ErrorDialog::VisualState> notifications;
|
|
notifications.clear();
|
|
ErrorDialog::SetVisibilityCallback([] {
|
|
const auto visual = ErrorDialog::GetVisualState();
|
|
ErrorDialog::HostSnapshot snapshot{};
|
|
Require("ErrorDialog", "callback reentry",
|
|
ErrorDialog::GetHostSnapshot(&snapshot) == visual.active &&
|
|
(ErrorDialog::ErrorDialogGetStatus() == 2) == visual.active,
|
|
"visibility callback observed inconsistent dialog state");
|
|
notifications.push_back(visual);
|
|
});
|
|
const auto initial_revision = ErrorDialog::GetVisualState().revision;
|
|
const auto check_state = [&](const char *stage, int status,
|
|
size_t visibility_changes) {
|
|
const auto visual = ErrorDialog::GetVisualState();
|
|
ErrorDialog::HostSnapshot snapshot{};
|
|
Require("ErrorDialog", stage,
|
|
ErrorDialog::ErrorDialogGetStatus() == status &&
|
|
ErrorDialog::ErrorDialogUpdateStatus() == status &&
|
|
visual.active == (status == 2) &&
|
|
ErrorDialog::GetHostSnapshot(&snapshot) == visual.active &&
|
|
visual.revision == initial_revision + visibility_changes &&
|
|
notifications.size() == visibility_changes &&
|
|
(notifications.empty() ||
|
|
(notifications.back().active == visual.active &&
|
|
notifications.back().revision == visual.revision)),
|
|
"status, host visibility, or visibility notification changed "
|
|
"unexpectedly");
|
|
};
|
|
check_state("uninitialized", 0, 0);
|
|
Require("ErrorDialog", "uninitialized operations",
|
|
ErrorDialog::ErrorDialogTerminate() ==
|
|
static_cast<int>(0x80ed0001u) &&
|
|
ErrorDialog::ErrorDialogClose() == invalid_state,
|
|
"uninitialized dialog accepted termination or close");
|
|
Require("ErrorDialog", "initialize",
|
|
ErrorDialog::ErrorDialogInitialize() == 0 &&
|
|
ErrorDialog::ErrorDialogInitialize() ==
|
|
static_cast<int>(0x80ed0002u),
|
|
"initialization did not reject a second initialization");
|
|
Param invalid = param;
|
|
invalid.size--;
|
|
Require("ErrorDialog", "invalid open",
|
|
ErrorDialog::ErrorDialogOpen(nullptr) == invalid_param &&
|
|
ErrorDialog::ErrorDialogOpen(&invalid) == invalid_param &&
|
|
ErrorDialog::ErrorDialogClose() == invalid_state,
|
|
"invalid parameters or close were accepted before opening");
|
|
check_state("failed open preserves initialized state", 1, 0);
|
|
Require("ErrorDialog", "open", ErrorDialog::ErrorDialogOpen(¶m) == 0,
|
|
"valid error dialog did not open");
|
|
ErrorDialog::HostSnapshot first{};
|
|
Require("ErrorDialog", "copied error code",
|
|
ErrorDialog::GetHostSnapshot(&first) &&
|
|
first.error_code == param.error_code,
|
|
"host dialog did not expose the guest error code");
|
|
param.error_code = static_cast<int32_t>(0x8055000au);
|
|
for (int poll = 0; poll < 8; ++poll) {
|
|
check_state("polling waits for acknowledgement", 2, 1);
|
|
}
|
|
Require(
|
|
"ErrorDialog", "running failures",
|
|
ErrorDialog::ErrorDialogOpen(¶m) == invalid_state &&
|
|
!ErrorDialog::HostAccept(first.generation + 1),
|
|
"an invalid open or stale acknowledgement replaced the active dialog");
|
|
ErrorDialog::HostSnapshot current{};
|
|
Require("ErrorDialog", "failed open preserves active dialog",
|
|
ErrorDialog::GetHostSnapshot(¤t) &&
|
|
current.generation == first.generation &&
|
|
current.error_code == first.error_code,
|
|
"active error code or generation changed after a failed open");
|
|
check_state("failed operations preserve running state", 2, 1);
|
|
Require("ErrorDialog", "acknowledge",
|
|
ErrorDialog::HostAccept(first.generation),
|
|
"current host acknowledgement did not finish the dialog");
|
|
check_state("acknowledged", 3, 2);
|
|
Require("ErrorDialog", "finished operations",
|
|
!ErrorDialog::HostAccept(first.generation) &&
|
|
ErrorDialog::ErrorDialogClose() == invalid_state &&
|
|
ErrorDialog::ErrorDialogOpen(&invalid) == invalid_param,
|
|
"finished dialog accepted duplicate completion or an invalid open");
|
|
check_state("failed operations preserve finished state", 3, 2);
|
|
Require("ErrorDialog", "reopen and stale acknowledgement",
|
|
ErrorDialog::ErrorDialogOpen(¶m) == 0 &&
|
|
ErrorDialog::GetHostSnapshot(¤t) &&
|
|
current.generation != first.generation &&
|
|
current.error_code == param.error_code &&
|
|
!ErrorDialog::HostAccept(first.generation),
|
|
"reopened dialog accepted acknowledgement from its predecessor");
|
|
check_state("reopened", 2, 3);
|
|
Require("ErrorDialog", "guest close", ErrorDialog::ErrorDialogClose() == 0,
|
|
"guest close did not finish the running dialog");
|
|
check_state("closed", 3, 4);
|
|
Require("ErrorDialog", "terminate running dialog",
|
|
ErrorDialog::ErrorDialogOpen(¶m) == 0 &&
|
|
ErrorDialog::GetHostSnapshot(¤t) &&
|
|
ErrorDialog::ErrorDialogTerminate() == 0 &&
|
|
!ErrorDialog::HostAccept(current.generation),
|
|
"termination failed to remove the active dialog");
|
|
check_state("terminated", 0, 6);
|
|
Require("ErrorDialog", "reinitialize and stale acknowledgement",
|
|
ErrorDialog::ErrorDialogInitialize() == 0 &&
|
|
ErrorDialog::ErrorDialogOpen(¶m) == 0 &&
|
|
!ErrorDialog::HostAccept(current.generation),
|
|
"reinitialization reused an old dialog generation");
|
|
check_state("reinitialized", 2, 7);
|
|
Require("ErrorDialog", "final termination",
|
|
ErrorDialog::ErrorDialogTerminate() == 0 &&
|
|
ErrorDialog::ErrorDialogTerminate() ==
|
|
static_cast<int>(0x80ed0001u),
|
|
"termination did not restore the uninitialized state");
|
|
check_state("final state", 0, 8);
|
|
ErrorDialog::SetVisibilityCallback(nullptr);
|
|
Require("ErrorDialog", "lifecycle without a visibility listener",
|
|
ErrorDialog::ErrorDialogInitialize() == 0 &&
|
|
ErrorDialog::ErrorDialogOpen(¶m) == 0 &&
|
|
ErrorDialog::ErrorDialogClose() == 0 &&
|
|
ErrorDialog::ErrorDialogTerminate() == 0 &&
|
|
ErrorDialog::ErrorDialogGetStatus() == 0 &&
|
|
!ErrorDialog::GetVisualState().active &&
|
|
ErrorDialog::GetVisualState().revision == initial_revision + 10 &&
|
|
notifications.size() == 8,
|
|
"guest lifecycle depended on a registered host visibility listener");
|
|
std::printf("[host] %-32s ok\n", "ErrorDialogLifecycle");
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
template <typename Function>
|
|
void ExpectFatal(const char *name, Function function) {
|
|
const pid_t pid = ::fork();
|
|
Require(name, "fatal subprocess", pid >= 0,
|
|
"fork failed while starting fatal shader test");
|
|
if (pid == 0) {
|
|
function();
|
|
::_exit(0);
|
|
}
|
|
|
|
int status = 0;
|
|
pid_t waited = -1;
|
|
do {
|
|
waited = ::waitpid(pid, &status, 0);
|
|
} while (waited < 0 && errno == EINTR);
|
|
Require(name, "fatal subprocess", waited == pid,
|
|
"waitpid failed while collecting fatal shader test");
|
|
Require(name, "fatal exit",
|
|
WIFEXITED(status) && WEXITSTATUS(status) == (321 & 0xff),
|
|
"fatal shader path did not terminate with status 321");
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
#endif
|
|
|
|
void CheckLeastRecentlyUsedCacheOrdering() {
|
|
Common::LeastRecentlyUsedCache<uint32_t, uint64_t> cache;
|
|
const auto first = cache.Insert(1, 1);
|
|
const auto middle = cache.Insert(2, 2);
|
|
const auto last = cache.Insert(3, 3);
|
|
(void)first;
|
|
(void)last;
|
|
cache.Touch(middle, 4);
|
|
std::vector<uint32_t> visited;
|
|
cache.ForEachItemBelow(4, [&](uint32_t value) {
|
|
visited.push_back(value);
|
|
return visited.size() > 3;
|
|
});
|
|
Require("LeastRecentlyUsedCache", "non-tail touch traversal",
|
|
visited == std::vector<uint32_t>{1, 3, 2},
|
|
"touching a non-tail item left a cycle or changed LRU order");
|
|
std::printf("[host] %-32s ok\n", "LeastRecentlyUsedCache");
|
|
}
|
|
|
|
struct BdaMapping {
|
|
uint64_t guest_base = 0;
|
|
u32 backing_offset = 0;
|
|
};
|
|
|
|
struct TestCase {
|
|
const char *name = "";
|
|
std::vector<u32> code;
|
|
std::vector<u32> initial;
|
|
std::vector<u32> expected;
|
|
std::vector<ShaderOpcode> opcodes;
|
|
u32 image_width = 4;
|
|
u32 image_height = 4;
|
|
std::vector<u32> sampled_image_rgba;
|
|
std::vector<std::vector<u32>> sampled_image_rgba_mips;
|
|
vk::Format sampled_image_format = vk::Format::eR32G32B32A32Sfloat;
|
|
u32 sampled_image_dwords_per_pixel = 4;
|
|
vk::ImageType sampled_image_type = vk::ImageType::e2D;
|
|
vk::ImageViewType sampled_image_view_type = vk::ImageViewType::e2D;
|
|
u32 sampled_image_layers = 1;
|
|
u32 sampled_image_view_base_layer = 0;
|
|
u32 sampled_image_view_layers = 0;
|
|
std::vector<u32> storage_image_rgba;
|
|
std::vector<u32> expected_storage_image_rgba;
|
|
vk::Format storage_image_format = vk::Format::eR32G32B32A32Sfloat;
|
|
u32 storage_image_dwords_per_pixel = 4;
|
|
std::vector<u32> storage_image_r32ui;
|
|
std::vector<u32> expected_storage_image_r32ui;
|
|
std::vector<std::string> required_spirv;
|
|
std::vector<std::string> forbidden_spirv;
|
|
ShaderComputeInputInfo compute_info = [] {
|
|
ShaderComputeInputInfo info{};
|
|
info.lds_size_dwords = 1024;
|
|
return info;
|
|
}();
|
|
bool has_compute_info = false;
|
|
u32 dispatch_x = 1;
|
|
u32 dispatch_y = 1;
|
|
u32 dispatch_z = 1;
|
|
std::array<u32, 64> user_data{};
|
|
bool has_user_data = false;
|
|
u32 image_descriptor_swizzle = DstSel(4, 5, 6, 7);
|
|
bool compile_only = false;
|
|
size_t storage_buffer_range_dwords = 0;
|
|
std::vector<u32> storage_buffer_offsets;
|
|
std::vector<BdaMapping> bda_mappings;
|
|
bool expand_shader_data_storage = false;
|
|
bool expected_force_point_sampler = false;
|
|
std::vector<u32> gds_initial;
|
|
std::vector<u32> expected_gds;
|
|
std::vector<std::pair<std::string, size_t>> decoded_counts;
|
|
std::vector<std::pair<std::string, size_t>> ir_counts;
|
|
u32 expected_storage_mip_descriptors = 0;
|
|
};
|
|
|
|
struct GraphicsCase {
|
|
const char *name = "";
|
|
std::vector<u32> fragment_code;
|
|
std::vector<u32> expected_pixel;
|
|
std::vector<ShaderOpcode> opcodes;
|
|
std::array<u32, 64> user_data{};
|
|
bool has_user_data = false;
|
|
std::vector<u32> push_constants;
|
|
std::vector<u32> pixel_interpolator_settings;
|
|
bool pixel_no_perspective = false;
|
|
std::vector<u32> vertices;
|
|
bool pixel_ancillary = false;
|
|
bool pixel_front_face = false;
|
|
u32 layers = 1;
|
|
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1;
|
|
bool pixel_position_w = false;
|
|
float vertex_clip_w = 1.0f;
|
|
bool pixel_depth_export = false;
|
|
};
|
|
|
|
struct CompiledShader {
|
|
std::vector<u32> spirv;
|
|
ShaderRecompiler::IR::Program program;
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
std::vector<u32> packed_user_data;
|
|
};
|
|
|
|
std::array<u32, 64> MakeNativeUserData(const std::array<u32, 64> *source) {
|
|
std::array<u32, 64> data{};
|
|
data[50] = 1u << 20u;
|
|
if (source != nullptr) {
|
|
data = *source;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
bool ReadTestMemory(void *userdata, uint64_t address, u32 *value) {
|
|
const auto *data = static_cast<const std::vector<u32> *>(userdata);
|
|
if (data == nullptr || value == nullptr || address % 4u != 0 ||
|
|
address / 4u >= data->size()) {
|
|
return false;
|
|
}
|
|
*value = (*data)[address / 4u];
|
|
return true;
|
|
}
|
|
|
|
void ValidateSpirv(const char *shader_name, const std::vector<u32> &spirv) {
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string messages;
|
|
tools.SetMessageConsumer([&messages](spv_message_level_t, const char *,
|
|
const spv_position_t &position,
|
|
const char *message) {
|
|
char buffer[1024] = {};
|
|
std::snprintf(buffer, sizeof(buffer), "%zu:%zu: %s\n", position.line,
|
|
position.column, message);
|
|
messages += buffer;
|
|
});
|
|
|
|
if (!tools.Validate(spirv)) {
|
|
Fail(shader_name, "SPIR-V validation", messages);
|
|
}
|
|
}
|
|
|
|
size_t CountText(const std::string &text, const std::string &needle) {
|
|
size_t count = 0;
|
|
for (size_t offset = 0;
|
|
(offset = text.find(needle, offset)) != std::string::npos;
|
|
offset += needle.size()) {
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void CheckRectListShaders() {
|
|
constexpr const char *name = "RectListShaders";
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo program{};
|
|
program.info.inputs.push_back(
|
|
{ShaderRecompiler::IR::StageInputKind::Parameter, 0, 4, "in_param_0"});
|
|
program.info.inputs.push_back(
|
|
{ShaderRecompiler::IR::StageInputKind::Parameter, 1, 4, "in_param_1"});
|
|
|
|
ShaderVertexInputInfo vertex{};
|
|
ShaderRecompiler::IR::CompiledShaderInfo vertex_program{.param_export_mask = 1u};
|
|
vertex.stage.program = &vertex_program;
|
|
ShaderPixelInputInfo pixel{};
|
|
pixel.input_num = 2;
|
|
pixel.interpolator_settings[0] = 0x400u;
|
|
pixel.interpolator_settings[1] = 0;
|
|
pixel.stage.program = &program;
|
|
std::vector<uint32_t> perspective_specialization;
|
|
BuildStageStaticKey(pixel, perspective_specialization);
|
|
pixel.ps_no_perspective = true;
|
|
std::vector<uint32_t> no_perspective_specialization;
|
|
BuildStageStaticKey(pixel, no_perspective_specialization);
|
|
pixel.ps_no_perspective = false;
|
|
Require(name, "pipeline identity",
|
|
perspective_specialization != no_perspective_specialization,
|
|
"pixel interpolation mode must participate in the shader and "
|
|
"pipeline key");
|
|
|
|
const std::array<uint32_t, 2> active_inputs = {0, 1};
|
|
Require(name, "duplicate mapping",
|
|
ShaderPixelParameterLocation(pixel, active_inputs, 0) == 0 &&
|
|
ShaderPixelParameterLocation(pixel, active_inputs, 1) == 1,
|
|
"duplicate pixel mappings must receive distinct effective output "
|
|
"locations");
|
|
|
|
const auto shaders = BuildRectListShaders(vertex, &pixel);
|
|
Require(name, "SPIR-V version",
|
|
shaders.control.size() > 1 && shaders.control[1] == 0x00010500u &&
|
|
shaders.evaluation.size() > 1 &&
|
|
shaders.evaluation[1] == 0x00010500u,
|
|
"vector selection requires SPIR-V 1.5");
|
|
ValidateSpirv(name, shaders.control);
|
|
ValidateSpirv(name, shaders.evaluation);
|
|
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string control_text;
|
|
std::string evaluation_text;
|
|
Require(name, "control disassembly",
|
|
tools.Disassemble(shaders.control, &control_text),
|
|
"failed to disassemble rectangle-list tessellation control shader");
|
|
Require(
|
|
name, "evaluation disassembly",
|
|
tools.Disassemble(shaders.evaluation, &evaluation_text),
|
|
"failed to disassemble rectangle-list tessellation evaluation shader");
|
|
Require(name, "control execution mode",
|
|
control_text.find("TessellationControl") != std::string::npos &&
|
|
control_text.find("OutputVertices 4") != std::string::npos,
|
|
"rectangle-list control shader must produce four control points");
|
|
Require(name, "evaluation execution modes",
|
|
evaluation_text.find("TessellationEvaluation") != std::string::npos &&
|
|
evaluation_text.find("Quads") != std::string::npos &&
|
|
evaluation_text.find("SpacingEqual") != std::string::npos &&
|
|
evaluation_text.find("VertexOrderCw") != std::string::npos,
|
|
"rectangle-list evaluation shader has the wrong patch modes");
|
|
Require(name, "no geometry stage",
|
|
control_text.find("Geometry") == std::string::npos &&
|
|
evaluation_text.find("Geometry") == std::string::npos,
|
|
"rectangle-list expansion must not use geometry shaders");
|
|
Require(name, "flat broadcast",
|
|
CountText(control_text, "OpVectorTimesScalar") == 6 &&
|
|
CountText(control_text, "OpSelect %v4float") == 2,
|
|
"flat parameters must use guest vertex zero instead of reconstructed "
|
|
"values");
|
|
Require(name, "remapped interface",
|
|
CountText(control_text, " Location 0") == 2 &&
|
|
CountText(control_text, " Location 1") == 1 &&
|
|
CountText(evaluation_text, " Location 0") == 2 &&
|
|
CountText(evaluation_text, " Location 1") == 2,
|
|
"duplicate pixel mappings must share one vertex input and keep "
|
|
"distinct patch outputs");
|
|
|
|
const auto position_only = BuildRectListShaders(vertex, nullptr);
|
|
ValidateSpirv(name, position_only.control);
|
|
ValidateSpirv(name, position_only.evaluation);
|
|
}
|
|
|
|
void CheckSpirvText(const TestCase &test, const std::vector<u32> &spirv) {
|
|
if (test.required_spirv.empty() && test.forbidden_spirv.empty()) {
|
|
return;
|
|
}
|
|
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string text;
|
|
if (!tools.Disassemble(spirv, &text)) {
|
|
Fail(test.name, "SPIR-V disassembly",
|
|
"failed to disassemble emitted SPIR-V");
|
|
}
|
|
for (const auto &required : test.required_spirv) {
|
|
if (text.find(required) == std::string::npos) {
|
|
Fail(test.name, "SPIR-V disassembly",
|
|
std::string("missing required text: ") + required);
|
|
}
|
|
}
|
|
for (const auto &forbidden : test.forbidden_spirv) {
|
|
if (text.find(forbidden) != std::string::npos) {
|
|
Fail(test.name, "SPIR-V disassembly",
|
|
std::string("found forbidden text: ") + forbidden);
|
|
}
|
|
}
|
|
}
|
|
|
|
CompiledShader CompileCase(const TestCase &test, u32 host_subgroup_size = 64) {
|
|
auto user_data =
|
|
MakeNativeUserData(test.has_user_data ? &test.user_data : nullptr);
|
|
const auto uses_image =
|
|
std::any_of(test.opcodes.begin(), test.opcodes.end(), [](auto op) {
|
|
return op >= ShaderOpcode::IMAGE_GET_RESINFO &&
|
|
op <= ShaderOpcode::IMAGE_GATHER4H;
|
|
});
|
|
if (uses_image && ((user_data[3] >> 28u) & 0xfu) == 0) {
|
|
user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u;
|
|
}
|
|
if (uses_image) {
|
|
user_data[3] = (user_data[3] & ~0xfffu) | test.image_descriptor_swizzle;
|
|
}
|
|
if (!test.has_user_data) {
|
|
user_data[2] = static_cast<u32>(test.initial.size() * sizeof(u32));
|
|
}
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = ShaderType::Compute;
|
|
options.dump_ir = true;
|
|
auto compute_info = test.compute_info;
|
|
compute_info.host_subgroup_size = host_subgroup_size;
|
|
options.input_info.compute = &compute_info;
|
|
options.user_data = user_data;
|
|
options.scratch_dwords = test.compute_info.scratch_size_dwords;
|
|
if (test.has_compute_info) {
|
|
options.wave_size = test.compute_info.wave_size;
|
|
}
|
|
|
|
auto translated = ShaderRecompiler::TranslateProgram(test.code, options);
|
|
auto resource_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(test.code.data()),
|
|
.read_memory = ReadTestMemory,
|
|
.userdata = const_cast<std::vector<u32> *>(&test.initial),
|
|
};
|
|
Require(test.name, "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
resource_plan, runtime, resources, specialization),
|
|
"translated resources could not be materialized");
|
|
auto result = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
for (const auto &[text, expected] : test.decoded_counts) {
|
|
const auto actual = CountText(result.decoded_dump, text);
|
|
Require(test.name, "decoded RDNA2", actual == expected,
|
|
text + " count=" + std::to_string(actual) +
|
|
", expected=" + std::to_string(expected));
|
|
}
|
|
for (const auto &[text, expected] : test.ir_counts) {
|
|
const auto actual = CountText(result.ir_dump, text);
|
|
Require(test.name, "typed IR", actual == expected,
|
|
text + " count=" + std::to_string(actual) +
|
|
", expected=" + std::to_string(expected));
|
|
}
|
|
if (test.expected_storage_mip_descriptors != 0) {
|
|
const auto image = std::ranges::find_if(
|
|
result.program.info.images, [](const auto &resource) {
|
|
return resource.mip_mode ==
|
|
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
|
});
|
|
Require(test.name, "dynamic storage mip specialization",
|
|
image != result.program.info.images.end() &&
|
|
image->mip_count == test.expected_storage_mip_descriptors,
|
|
"runtime descriptor range did not specialize the mip count");
|
|
const auto resource =
|
|
static_cast<u32>(image - result.program.info.images.begin());
|
|
const auto binding = std::ranges::find_if(
|
|
result.program.bindings.descriptors, [&](const auto &group) {
|
|
return std::ranges::find(group.resources, resource) !=
|
|
group.resources.end();
|
|
});
|
|
Require(test.name, "dynamic storage mip binding",
|
|
binding != result.program.bindings.descriptors.end() &&
|
|
std::ranges::count(binding->resources, resource) ==
|
|
test.expected_storage_mip_descriptors,
|
|
"dynamic storage image did not receive one descriptor per mip");
|
|
}
|
|
if (test.expand_shader_data_storage) {
|
|
auto &block = *result.program.blocks.front();
|
|
for (u32 reg = 0; reg < 33; reg++) {
|
|
auto &user_data = block.AppendNewInst(
|
|
ShaderRecompiler::IR::ValueOpcode::GetUserData,
|
|
{ShaderRecompiler::IR::Value(
|
|
static_cast<ShaderRecompiler::IR::ScalarReg>(reg))});
|
|
block.AppendNewInst(ShaderRecompiler::IR::ValueOpcode::ReferenceU32,
|
|
{ShaderRecompiler::IR::Value(&user_data)});
|
|
}
|
|
result.program.bindings = {};
|
|
result.program.binding_layout_complete = false;
|
|
ShaderRecompiler::IR::AllocateBindings(result.program);
|
|
const auto *shader_data = ShaderRecompiler::IR::FindBinding(
|
|
result.program.bindings,
|
|
ShaderRecompiler::IR::DescriptorBindingKind::ShaderData);
|
|
Require(test.name, "binding layout",
|
|
result.program.bindings.user_data_registers.size() == 33 &&
|
|
!result.program.bindings.UsesPushData() &&
|
|
shader_data != nullptr,
|
|
"oversized shader data did not use its storage fallback");
|
|
result.spirv = ShaderRecompiler::Spirv::EmitProgram(result.program,
|
|
options.input_info);
|
|
}
|
|
Require(test.name, "SPIR-V emit", !result.spirv.empty(),
|
|
"recompiler returned empty SPIR-V");
|
|
ValidateSpirv(test.name, result.spirv);
|
|
CheckSpirvText(test, result.spirv);
|
|
std::vector<u32> packed_user_data;
|
|
for (const auto reg : result.program.bindings.user_data_registers) {
|
|
packed_user_data.push_back(
|
|
resources.user_data[reg - result.program.user_data_base]);
|
|
}
|
|
packed_user_data.resize(result.program.bindings.ShaderDataDwords());
|
|
const auto buffer_count =
|
|
static_cast<u32>(result.program.info.buffers.size());
|
|
for (u32 i = 0; i < buffer_count; i++) {
|
|
u32 offset = 0;
|
|
if (i < test.storage_buffer_offsets.size()) {
|
|
offset = test.storage_buffer_offsets[i];
|
|
}
|
|
Require(test.name, "shader data", offset % sizeof(u32) == 0 && offset < 256,
|
|
"storage buffer offset is not representable");
|
|
packed_user_data[result.program.bindings.memory_offset_dword + i / 4u] |=
|
|
offset << ((i % 4u) * 8u);
|
|
}
|
|
return {std::move(result.spirv), std::move(result.program),
|
|
std::move(resources), std::move(packed_user_data)};
|
|
}
|
|
|
|
std::array<u32, 64> MakeStructuredStorageBufferData(u32 stride_bytes,
|
|
u32 num_records,
|
|
bool add_tid = false,
|
|
u32 format = 0) {
|
|
std::array<u32, 64> data{};
|
|
data[1] = (stride_bytes & 0x3fffu) << 16u;
|
|
data[2] = num_records;
|
|
data[3] = DstSel(4, 5, 6, 7) | (1u << 24u);
|
|
if (add_tid) {
|
|
data[3] |= 1u << 23u;
|
|
}
|
|
data[3] |= (format & 0x7fu) << 12u;
|
|
return data;
|
|
}
|
|
|
|
std::array<u32, 64> MakeStorageTextureData(Prospero::BufferFormat format) {
|
|
std::array<u32, 64> data{};
|
|
data[0] = 0x1000u;
|
|
data[1] = (static_cast<uint32_t>(format) & 0x1ffu) << 20u;
|
|
data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u;
|
|
return data;
|
|
}
|
|
|
|
std::string StorageUint2DImageBindingName(bool atomic) {
|
|
ShaderRecompiler::IR::ImageResource image{};
|
|
image.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
image.atomic = atomic;
|
|
const auto binding = ShaderRecompiler::IR::DescriptorBindingForImage(image);
|
|
Require("StorageUint2DImageBindingName", "descriptor binding", binding.has_value(),
|
|
"storage image has no descriptor binding");
|
|
return "image_" + std::to_string(static_cast<uint32_t>(*binding));
|
|
}
|
|
|
|
CompiledShader CompileFragmentCase(const GraphicsCase &test) {
|
|
const auto user_data =
|
|
MakeNativeUserData(test.has_user_data ? &test.user_data : nullptr);
|
|
ShaderPixelInputInfo pixel_info{};
|
|
pixel_info.input_num =
|
|
test.pixel_interpolator_settings.empty()
|
|
? 1u
|
|
: static_cast<u32>(test.pixel_interpolator_settings.size());
|
|
pixel_info.ps_no_perspective = test.pixel_no_perspective;
|
|
pixel_info.ps_ancillary = test.pixel_ancillary;
|
|
pixel_info.ps_front_face = test.pixel_front_face;
|
|
pixel_info.ps_pos_w = test.pixel_position_w;
|
|
pixel_info.ps_depth_export_enable = test.pixel_depth_export;
|
|
pixel_info.ps_system_input_base = 2;
|
|
for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) {
|
|
pixel_info.interpolator_settings[i] = i;
|
|
}
|
|
for (u32 i = 0; i < test.pixel_interpolator_settings.size() &&
|
|
i < std::size(pixel_info.interpolator_settings);
|
|
i++) {
|
|
pixel_info.interpolator_settings[i] = test.pixel_interpolator_settings[i];
|
|
}
|
|
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = ShaderType::Pixel;
|
|
options.dump_ir = false;
|
|
options.input_info.pixel = &pixel_info;
|
|
options.user_data = user_data;
|
|
|
|
auto translated =
|
|
ShaderRecompiler::TranslateProgram(test.fragment_code, options);
|
|
auto resource_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(test.fragment_code.data()),
|
|
};
|
|
Require(test.name, "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
resource_plan, runtime, resources, specialization),
|
|
"translated resources could not be materialized");
|
|
auto result = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
Require(test.name, "SPIR-V emit", !result.spirv.empty(),
|
|
"recompiler returned empty SPIR-V");
|
|
ValidateSpirv(test.name, result.spirv);
|
|
std::vector<u32> packed_user_data;
|
|
for (const auto reg : result.program.bindings.user_data_registers) {
|
|
packed_user_data.push_back(
|
|
resources.user_data[reg - result.program.user_data_base]);
|
|
}
|
|
packed_user_data.resize(result.program.bindings.ShaderDataDwords());
|
|
return {std::move(result.spirv), std::move(result.program),
|
|
std::move(resources), std::move(packed_user_data)};
|
|
}
|
|
|
|
std::array<u32, 64> MakeSampledTextureData(Prospero::BufferFormat format) {
|
|
return MakeStorageTextureData(format);
|
|
}
|
|
|
|
namespace TestSpv {
|
|
|
|
enum : u32 {
|
|
ExecutionModelVertex = 0,
|
|
AddressingModelLogical = 0,
|
|
MemoryModelGLSL450 = 1,
|
|
CapabilityShader = 1,
|
|
StorageClassInput = 1,
|
|
StorageClassOutput = 3,
|
|
FunctionControlNone = 0,
|
|
DecorationBlock = 2,
|
|
DecorationBuiltIn = 11,
|
|
DecorationLocation = 30,
|
|
BuiltInPosition = 0,
|
|
OpTypeVoid = 19,
|
|
OpTypeInt = 21,
|
|
OpTypeFloat = 22,
|
|
OpTypeVector = 23,
|
|
OpTypeStruct = 30,
|
|
OpTypePointer = 32,
|
|
OpTypeFunction = 33,
|
|
OpConstant = 43,
|
|
OpFunction = 54,
|
|
OpFunctionEnd = 56,
|
|
OpVariable = 59,
|
|
OpLoad = 61,
|
|
OpStore = 62,
|
|
OpAccessChain = 65,
|
|
OpDecorate = 71,
|
|
OpMemberDecorate = 72,
|
|
OpCompositeConstruct = 80,
|
|
OpCompositeExtract = 81,
|
|
OpLabel = 248,
|
|
OpReturn = 253,
|
|
};
|
|
|
|
std::vector<u32> MakePassthroughVertexSpirv(bool layered, float clip_w = 1.0f) {
|
|
using ShaderRecompiler::Spirv::Builder;
|
|
|
|
Builder b;
|
|
const auto void_type = b.Type(OpTypeVoid);
|
|
const auto uint_type = b.Type(OpTypeInt, {32, 0});
|
|
const auto int_type = b.Type(OpTypeInt, {32, 1});
|
|
const auto float_type = b.Type(OpTypeFloat, {32});
|
|
const auto vec2_type = b.Type(OpTypeVector, {float_type, 2});
|
|
const auto vec4_type = b.Type(OpTypeVector, {float_type, 4});
|
|
const auto per_vertex_type = b.DecoratedType(
|
|
OpTypeStruct, {vec4_type},
|
|
{{OpDecorate, {DecorationBlock}},
|
|
{OpMemberDecorate, {0, DecorationBuiltIn, BuiltInPosition}}});
|
|
const auto ptr_input_vec2 =
|
|
b.Type(OpTypePointer, {StorageClassInput, vec2_type});
|
|
const auto ptr_input_vec4 =
|
|
b.Type(OpTypePointer, {StorageClassInput, vec4_type});
|
|
const auto ptr_output_vec4 =
|
|
b.Type(OpTypePointer, {StorageClassOutput, vec4_type});
|
|
const auto ptr_output_per_vertex =
|
|
b.Type(OpTypePointer, {StorageClassOutput, per_vertex_type});
|
|
const auto func_type = b.Type(OpTypeFunction, {void_type});
|
|
const auto const_u32_0 = b.Constant(OpConstant, uint_type, {0});
|
|
const auto const_f32_0 = b.Constant(OpConstant, float_type, {0x00000000u});
|
|
const auto const_f32_w =
|
|
b.Constant(OpConstant, float_type, {std::bit_cast<u32>(clip_w)});
|
|
const auto in_pos = b.DefineGlobalVariable(ptr_input_vec2, StorageClassInput);
|
|
const auto in_color =
|
|
b.DefineGlobalVariable(ptr_input_vec4, StorageClassInput);
|
|
const auto out_color =
|
|
b.DefineGlobalVariable(ptr_output_vec4, StorageClassOutput);
|
|
const auto per_vertex =
|
|
b.DefineGlobalVariable(ptr_output_per_vertex, StorageClassOutput);
|
|
u32 instance = 0;
|
|
u32 layer = 0;
|
|
std::vector<u32> interfaces = {in_pos, in_color, per_vertex, out_color};
|
|
if (layered) {
|
|
instance = b.DefineGlobalVariable(
|
|
b.Type(OpTypePointer, {StorageClassInput, int_type}), StorageClassInput);
|
|
layer = b.DefineGlobalVariable(
|
|
b.Type(OpTypePointer, {StorageClassOutput, int_type}), StorageClassOutput);
|
|
b.AddAnnotation({OpDecorate, instance, DecorationBuiltIn, 43}); // InstanceIndex
|
|
b.AddAnnotation({OpDecorate, layer, DecorationBuiltIn, 9}); // Layer
|
|
b.RequireVersion(0x00010500u);
|
|
b.RequireCapability(69); // ShaderLayer
|
|
interfaces.insert(interfaces.end(), {instance, layer});
|
|
}
|
|
const auto main = b.AllocateId();
|
|
const auto label = b.AllocateId();
|
|
const auto pos2 = b.AllocateId();
|
|
const auto color4 = b.AllocateId();
|
|
const auto pos_x = b.AllocateId();
|
|
const auto pos_y = b.AllocateId();
|
|
const auto position = b.AllocateId();
|
|
const auto position_ptr = b.AllocateId();
|
|
|
|
b.RequireCapability(CapabilityShader);
|
|
b.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
|
|
b.AddEntryPoint(ExecutionModelVertex, main, "main", interfaces);
|
|
b.AddAnnotation({OpDecorate, in_pos, DecorationLocation, 0});
|
|
b.AddAnnotation({OpDecorate, in_color, DecorationLocation, 1});
|
|
b.AddAnnotation({OpDecorate, out_color, DecorationLocation, 0});
|
|
|
|
b.AddFunction({OpFunction, void_type, main, FunctionControlNone, func_type});
|
|
b.AddFunction({OpLabel, label});
|
|
b.AddFunction({OpLoad, vec2_type, pos2, in_pos});
|
|
b.AddFunction({OpLoad, vec4_type, color4, in_color});
|
|
b.AddFunction({OpCompositeExtract, float_type, pos_x, pos2, 0});
|
|
b.AddFunction({OpCompositeExtract, float_type, pos_y, pos2, 1});
|
|
b.AddFunction({OpCompositeConstruct, vec4_type, position, pos_x, pos_y,
|
|
const_f32_0, const_f32_w});
|
|
b.AddFunction(
|
|
{OpAccessChain, ptr_output_vec4, position_ptr, per_vertex, const_u32_0});
|
|
b.AddFunction({OpStore, position_ptr, position});
|
|
b.AddFunction({OpStore, out_color, color4});
|
|
if (layered) {
|
|
const auto index = b.AllocateId();
|
|
b.AddFunction({OpLoad, int_type, index, instance});
|
|
b.AddFunction({OpStore, layer, index});
|
|
}
|
|
b.AddFunction({OpReturn});
|
|
b.AddFunction({OpFunctionEnd});
|
|
return b.Build();
|
|
}
|
|
|
|
} // namespace TestSpv
|
|
|
|
class VulkanHarness {
|
|
public:
|
|
VulkanHarness() { Init(); }
|
|
~VulkanHarness() { Destroy(); }
|
|
|
|
VulkanHarness(const VulkanHarness &) = delete;
|
|
VulkanHarness &operator=(const VulkanHarness &) = delete;
|
|
|
|
struct Buffer {
|
|
vk::Buffer buffer = nullptr;
|
|
vk::DeviceMemory memory = nullptr;
|
|
vk::DeviceAddress device_address = 0;
|
|
vk::DeviceSize size = 0;
|
|
bool coherent = false;
|
|
};
|
|
|
|
struct Image {
|
|
vk::Image image = nullptr;
|
|
vk::DeviceMemory memory = nullptr;
|
|
vk::ImageView view = nullptr;
|
|
vk::Format format = vk::Format::eUndefined;
|
|
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
|
u32 width = 0;
|
|
u32 height = 0;
|
|
u32 layers = 1;
|
|
u32 mip_levels = 1;
|
|
u32 dwords_per_pixel = 0;
|
|
};
|
|
|
|
[[nodiscard]] vk::Device Device() const { return m_device; }
|
|
[[nodiscard]] u32 SubgroupSize() const {
|
|
vk::PhysicalDeviceSubgroupProperties subgroup{};
|
|
vk::PhysicalDeviceProperties2 properties{};
|
|
properties.pNext = &subgroup;
|
|
m_physical_device.getProperties2(&properties);
|
|
return subgroup.subgroupSize;
|
|
}
|
|
[[nodiscard]] GraphicContext &RuntimeContext() {
|
|
EnsureRuntimeContext();
|
|
return m_runtime_context;
|
|
}
|
|
[[nodiscard]] RenderContext &RuntimeRenderer() {
|
|
EnsureRuntimeContext();
|
|
return Renderer();
|
|
}
|
|
|
|
void CheckHostImageAllocation() {
|
|
constexpr const char *name = "HostImageAllocation";
|
|
auto &graphics = RuntimeContext();
|
|
VulkanImage image;
|
|
vk::ImageCreateInfo create{};
|
|
create.flags = vk::ImageCreateFlagBits::eMutableFormat;
|
|
create.imageType = vk::ImageType::e2D;
|
|
create.format = vk::Format::eR8G8B8A8Unorm;
|
|
create.extent = {16, 8, 1};
|
|
create.mipLevels = 3;
|
|
create.arrayLayers = 4;
|
|
create.samples = vk::SampleCountFlagBits::e1;
|
|
create.tiling = vk::ImageTiling::eOptimal;
|
|
create.usage = vk::ImageUsageFlagBits::eTransferSrc |
|
|
vk::ImageUsageFlagBits::eTransferDst |
|
|
vk::ImageUsageFlagBits::eSampled;
|
|
create.initialLayout = vk::ImageLayout::eUndefined;
|
|
|
|
for (uint32_t pass = 0; pass < 2; ++pass) {
|
|
Require(name, "create", graphics.CreateImage(create, image),
|
|
"could not allocate native image");
|
|
Require(name, "allocation metadata",
|
|
image.image != nullptr && image.allocation != nullptr &&
|
|
image.format == create.format &&
|
|
image.image_type == create.imageType &&
|
|
image.extent == create.extent &&
|
|
image.layers == create.arrayLayers &&
|
|
image.mip_levels == create.mipLevels &&
|
|
image.samples == static_cast<uint32_t>(create.samples) &&
|
|
image.usage == create.usage && image.flags == create.flags,
|
|
"native image metadata did not follow its creation descriptor");
|
|
Require(name, "fresh synchronization state",
|
|
image.state.layout == create.initialLayout &&
|
|
image.state.access_mask == vk::AccessFlags2{} &&
|
|
image.state.pl_stage == vk::PipelineStageFlagBits2::eAllCommands &&
|
|
image.subresource_states.empty(),
|
|
"new allocation retained synchronization state from an old image");
|
|
VkMemoryPropertyFlags properties{};
|
|
vmaGetAllocationMemoryProperties(graphics.allocator, image.allocation,
|
|
&properties);
|
|
Require(name, "allocation policy",
|
|
(properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0,
|
|
"native image lost its device-local memory requirement");
|
|
|
|
// A reused presentation image may still describe its previous recording.
|
|
// No commands use this fixture; seed that bookkeeping before retirement.
|
|
image.state = {vk::PipelineStageFlagBits2::eTransfer,
|
|
vk::AccessFlagBits2::eTransferWrite,
|
|
vk::ImageLayout::eTransferDstOptimal};
|
|
image.subresource_states.assign(image.layers * image.mip_levels, image.state);
|
|
graphics.DeleteImage(image);
|
|
Require(name, "retirement",
|
|
image.image == nullptr && image.allocation == nullptr,
|
|
"retired native image retained an allocation handle");
|
|
|
|
create.flags = vk::ImageCreateFlagBits::e2DArrayCompatible;
|
|
create.imageType = vk::ImageType::e3D;
|
|
create.format = vk::Format::eR32Uint;
|
|
create.extent = {8, 4, 2};
|
|
create.mipLevels = 2;
|
|
create.arrayLayers = 1;
|
|
create.usage = vk::ImageUsageFlagBits::eTransferSrc |
|
|
vk::ImageUsageFlagBits::eTransferDst;
|
|
}
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckDescriptorHeapLargeSet() {
|
|
EnsureRuntimeContext();
|
|
std::array<vk::DescriptorSetLayoutBinding, 3> bindings{};
|
|
constexpr std::array stages{
|
|
vk::ShaderStageFlagBits::eVertex,
|
|
vk::ShaderStageFlagBits::eFragment,
|
|
vk::ShaderStageFlagBits::eCompute,
|
|
};
|
|
for (uint32_t i = 0; i < bindings.size(); i++) {
|
|
bindings[i].binding = i;
|
|
bindings[i].descriptorType = vk::DescriptorType::eSampler;
|
|
bindings[i].descriptorCount = 16;
|
|
bindings[i].stageFlags = stages[i];
|
|
}
|
|
vk::DescriptorSetLayoutCreateInfo create{};
|
|
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
|
create.bindingCount = static_cast<uint32_t>(bindings.size());
|
|
create.pBindings = bindings.data();
|
|
vk::DescriptorSetLayout layout = nullptr;
|
|
RequireVk("DescriptorHeapLargeSet", "layout",
|
|
m_device.createDescriptorSetLayout(&create, nullptr, &layout),
|
|
"vkCreateDescriptorSetLayout");
|
|
|
|
vk::PipelineLayoutCreateInfo pipeline_create{};
|
|
pipeline_create.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
|
pipeline_create.setLayoutCount = 1;
|
|
pipeline_create.pSetLayouts = &layout;
|
|
vk::PipelineLayout pipeline_layout = nullptr;
|
|
RequireVk("DescriptorHeapLargeSet", "pipeline layout",
|
|
m_device.createPipelineLayout(&pipeline_create, nullptr,
|
|
&pipeline_layout),
|
|
"vkCreatePipelineLayout");
|
|
|
|
{
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
const auto commit = [&] {
|
|
const auto set = context.GetDescriptorHeap().Commit(layout);
|
|
Require("DescriptorHeapLargeSet", "allocation", set != nullptr,
|
|
"ordinary descriptor heap could not allocate an oversized sampler set");
|
|
scheduler.Current().Handle().bindDescriptorSets(
|
|
vk::PipelineBindPoint::eCompute, pipeline_layout, 0, 1, &set, 0,
|
|
nullptr);
|
|
};
|
|
|
|
for (uint32_t i = 0; i < 22; i++) {
|
|
commit();
|
|
}
|
|
scheduler.Finish();
|
|
for (uint32_t i = 0; i < 21; i++) {
|
|
commit();
|
|
}
|
|
}
|
|
|
|
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
|
|
m_device.destroyDescriptorSetLayout(layout, nullptr);
|
|
std::printf("[host] %-32s ok\n", "DescriptorHeapLargeSet");
|
|
}
|
|
|
|
void CheckGraphicsPushConstantBank() {
|
|
constexpr const char *name = "GraphicsPushConstantStages";
|
|
EnsureRuntimeContext();
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo vertex_program{};
|
|
vertex_program.stage = ShaderType::Vertex;
|
|
vertex_program.bindings.push_data_start_dword = 2;
|
|
vertex_program.bindings.user_data_registers = {0, 1};
|
|
vertex_program.bindings.memory_offset_dword = 2;
|
|
ShaderRecompiler::IR::CompiledShaderInfo pixel_program{};
|
|
pixel_program.stage = ShaderType::Pixel;
|
|
pixel_program.bindings.push_data_start_dword = 0;
|
|
pixel_program.bindings.user_data_registers = {0, 1};
|
|
pixel_program.bindings.memory_offset_dword = 2;
|
|
ShaderStageRuntime vertex_runtime{.program = &vertex_program};
|
|
vertex_runtime.resources.user_data = {0x11111111u, 0x22222222u};
|
|
ShaderStageRuntime pixel_runtime{.program = &pixel_program};
|
|
pixel_runtime.resources.user_data = {0x33333333u, 0x44444444u};
|
|
auto vertex = context.GetRenderExecutor().PrepareBindings(vertex_runtime);
|
|
auto pixel = context.GetRenderExecutor().PrepareBindings(pixel_runtime);
|
|
|
|
const auto pipeline = RenderExecutorTestAccess::CommitBindings(
|
|
context.GetRenderExecutor(), scheduler.Current(), vertex, pixel);
|
|
Require(name, "shared push data",
|
|
vertex.shader_data ==
|
|
std::vector<uint32_t>{0x11111111u, 0x22222222u} &&
|
|
pixel.shader_data ==
|
|
std::vector<uint32_t>{0x33333333u, 0x44444444u},
|
|
"graphics stages did not commit their shared push data");
|
|
scheduler.Finish();
|
|
RenderExecutorTestAccess::DestroyDescriptorPipelines(
|
|
context.GetRenderExecutor(), std::span {&pipeline, 1u});
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckSchedulerTimeline() {
|
|
EnsureRuntimeContext();
|
|
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto *command = &scheduler.Current();
|
|
const auto original_handle = command->Handle();
|
|
HW::Context next_registers{};
|
|
HW::UserConfig next_user_config{};
|
|
HW::Shader next_shaders{};
|
|
scheduler.Begin(next_registers, next_user_config, next_shaders);
|
|
Require("SchedulerTimeline", "guest context rebind",
|
|
&scheduler.Current() == command &&
|
|
command->Handle() == original_handle &&
|
|
&command->GetRegisters() == &next_registers &&
|
|
&command->GetUserConfig() == &next_user_config &&
|
|
&command->GetShaders() == &next_shaders,
|
|
"rebinding guest state replaced the active command recording");
|
|
scheduler.FlushAndWait();
|
|
Require("SchedulerTimeline", "guest context after recording restart",
|
|
scheduler.Active() && &scheduler.Current() == command &&
|
|
&command->GetRegisters() == &next_registers &&
|
|
&command->GetUserConfig() == &next_user_config &&
|
|
&command->GetShaders() == &next_shaders,
|
|
"restarting command recording lost the current guest context");
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
const auto first_tick = scheduler.CurrentTick();
|
|
uint32_t completed = 0;
|
|
scheduler.DeferOperation([owned = std::make_unique<uint32_t>(1),
|
|
&completed] { completed = *owned; });
|
|
scheduler.Flush();
|
|
scheduler.Wait(first_tick);
|
|
Require("SchedulerTimeline", "wait boundary",
|
|
scheduler.CurrentTick() == first_tick + 1 &&
|
|
scheduler.IsFree(first_tick) && completed == 0,
|
|
"timeline wait released a resource in the middle of an operation");
|
|
scheduler.PopPendingOperations();
|
|
Require("SchedulerTimeline", "first tick", completed == 1,
|
|
"operation boundary did not release its deferred operation");
|
|
|
|
const auto second_tick = scheduler.CurrentTick();
|
|
std::atomic<bool> priority_in_callback{false};
|
|
scheduler.DeferPriorityOperation([owned = std::make_unique<uint32_t>(2),
|
|
&completed, &priority_in_callback] {
|
|
priority_in_callback = CommandScheduler::InDeferredOperation();
|
|
completed = *owned;
|
|
});
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
Require("SchedulerTimeline", "finish",
|
|
scheduler.CurrentTick() == second_tick + 1 &&
|
|
scheduler.IsFree(second_tick) && completed == 2 &&
|
|
priority_in_callback.load() &&
|
|
!CommandScheduler::InDeferredOperation(),
|
|
"scheduler completion drain lost its tick or callback context");
|
|
|
|
const auto implicit_flush_tick = scheduler.CurrentTick();
|
|
scheduler.DeferOperation([owned = std::make_unique<uint32_t>(3),
|
|
&completed] { completed = *owned; });
|
|
scheduler.Wait(implicit_flush_tick);
|
|
Require("SchedulerTimeline", "implicit flush",
|
|
scheduler.CurrentTick() == implicit_flush_tick + 1 &&
|
|
scheduler.IsFree(implicit_flush_tick) && completed == 2,
|
|
"waiting for the current tick released a deferred resource");
|
|
scheduler.PopPendingOperations();
|
|
Require("SchedulerTimeline", "implicit boundary", completed == 3,
|
|
"operation boundary did not release the implicit tick callback");
|
|
|
|
vk::SemaphoreTypeCreateInfo timeline_type{};
|
|
timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
|
timeline_type.semaphoreType = vk::SemaphoreType::eTimeline;
|
|
vk::SemaphoreCreateInfo timeline_create{};
|
|
timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo;
|
|
timeline_create.pNext = &timeline_type;
|
|
vk::Semaphore external_timeline = nullptr;
|
|
Require("SchedulerTimeline", "external timeline create",
|
|
m_runtime_context.device.createSemaphore(&timeline_create, nullptr,
|
|
&external_timeline) ==
|
|
vk::Result::eSuccess &&
|
|
external_timeline != nullptr,
|
|
"failed to create the external timeline semaphore");
|
|
|
|
constexpr uint64_t external_wait_value = 9;
|
|
const auto external_wait_tick = scheduler.CurrentTick();
|
|
SubmitInfo external_wait;
|
|
external_wait.AddWait(external_timeline, external_wait_value);
|
|
constexpr size_t blocked_submission_count = 6;
|
|
std::array<vk::CommandBuffer, blocked_submission_count> blocked_handles{};
|
|
for (size_t i = 0; i < blocked_submission_count; ++i) {
|
|
blocked_handles[i] = scheduler.Current().Handle();
|
|
if (i == 0) {
|
|
scheduler.Flush(external_wait);
|
|
} else {
|
|
scheduler.Flush();
|
|
}
|
|
}
|
|
bool distinct_handles = true;
|
|
for (size_t i = 0; i < blocked_handles.size(); ++i) {
|
|
for (size_t j = i + 1; j < blocked_handles.size(); ++j) {
|
|
distinct_handles &= blocked_handles[i] != blocked_handles[j];
|
|
}
|
|
}
|
|
const auto last_blocked_tick = scheduler.CurrentTick() - 1;
|
|
Require("SchedulerTimeline", "external timeline overflow",
|
|
distinct_handles &&
|
|
last_blocked_tick ==
|
|
external_wait_tick + blocked_submission_count - 1 &&
|
|
!scheduler.IsFree(last_blocked_tick),
|
|
"blocked submissions did not grow the command-buffer pool");
|
|
vk::SemaphoreSignalInfo signal_info{};
|
|
signal_info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
|
signal_info.semaphore = external_timeline;
|
|
signal_info.value = external_wait_value;
|
|
Require("SchedulerTimeline", "external timeline signal",
|
|
m_runtime_context.device.signalSemaphore(&signal_info) ==
|
|
vk::Result::eSuccess,
|
|
"failed to signal the external timeline semaphore");
|
|
scheduler.Wait(last_blocked_tick);
|
|
m_runtime_context.device.destroySemaphore(external_timeline, nullptr);
|
|
scheduler.Flush();
|
|
scheduler.Flush();
|
|
Require(
|
|
"SchedulerTimeline", "timeline pool reuse",
|
|
std::ranges::find(blocked_handles, scheduler.Current().Handle()) !=
|
|
blocked_handles.end(),
|
|
"completed command buffers were not reused after timeline progress");
|
|
scheduler.Shutdown();
|
|
|
|
CommandScheduler draining(Renderer(), m_runtime_context);
|
|
HW::Context drain_registers{};
|
|
HW::UserConfig drain_user_config{};
|
|
HW::Shader drain_shaders{};
|
|
draining.Begin(drain_registers, drain_user_config, drain_shaders);
|
|
uint32_t reentrant = 0;
|
|
draining.DeferOperation([&draining, &reentrant] {
|
|
reentrant++;
|
|
draining.DeferOperation([&reentrant] { reentrant++; });
|
|
});
|
|
|
|
std::binary_semaphore priority_entered{0};
|
|
std::binary_semaphore release_priority{0};
|
|
draining.DeferPriorityOperation(
|
|
[&draining, &priority_entered, &release_priority] {
|
|
priority_entered.release();
|
|
draining.Shutdown();
|
|
release_priority.acquire();
|
|
});
|
|
std::jthread shutdown_thread([&draining] { draining.Shutdown(); });
|
|
priority_entered.acquire();
|
|
std::jthread second_shutdown_thread([&draining] { draining.Shutdown(); });
|
|
|
|
std::atomic<bool> concurrent_completed{false};
|
|
std::jthread concurrent_defer([&draining, &concurrent_completed] {
|
|
draining.DeferOperation(
|
|
[&concurrent_completed] { concurrent_completed = true; });
|
|
});
|
|
release_priority.release();
|
|
shutdown_thread.join();
|
|
second_shutdown_thread.join();
|
|
concurrent_defer.join();
|
|
Require("SchedulerTimeline", "shutdown drain",
|
|
reentrant == 2 && concurrent_completed.load(),
|
|
"shutdown lost reentrant or concurrent deferred work");
|
|
std::printf("[host] %-32s ok\n", "SchedulerTimeline");
|
|
}
|
|
|
|
void CheckGpuMappedRangeLifecycle() {
|
|
EnsureRuntimeContext();
|
|
auto &context = Renderer();
|
|
CommandScheduler scheduler(context, m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
context.InitializeGpu(nullptr);
|
|
auto &gpu = context.GetGpu();
|
|
GpuResourceManager resources(m_runtime_context, scheduler);
|
|
resources.SetGpu(&gpu);
|
|
|
|
constexpr uint64_t base = 0x0000000200000000ull;
|
|
constexpr uint64_t page = 0x4000;
|
|
resources.MapMemory(base, page * 4);
|
|
resources.MapMemory(base + page * 2, page * 4);
|
|
Require("GpuMappedRangeLifecycle", "union",
|
|
resources.IsMapped(base, page * 6) &&
|
|
!resources.IsMapped(base, page * 7),
|
|
"overlapping maps did not form one interval union");
|
|
|
|
resources.UnmapMemory(base + page * 2, page * 2);
|
|
Require("GpuMappedRangeLifecycle", "subtract",
|
|
resources.IsMapped(base, page * 2) &&
|
|
resources.IsMapped(base + page * 4, page * 2) &&
|
|
!resources.IsMapped(base, page * 6),
|
|
"partial unmap did not punch the expected interval hole");
|
|
|
|
resources.UnmapMemory(base + page * 2, page * 2);
|
|
Require("GpuMappedRangeLifecycle", "idempotent unmap",
|
|
resources.IsMapped(base, page * 2) &&
|
|
resources.IsMapped(base + page * 4, page * 2),
|
|
"unmapping an absent interval changed neighboring mappings");
|
|
|
|
resources.UnmapMemory(base, page * 6);
|
|
Require("GpuMappedRangeLifecycle", "clear",
|
|
!resources.IsMapped(base, page * 6),
|
|
"full unmap did not clear the interval union");
|
|
|
|
constexpr uint64_t old_prt = base + page * 8;
|
|
constexpr uint64_t new_prt = base + page * 16;
|
|
resources.MapMemory(old_prt, page * 4);
|
|
resources.UnmapMemory(old_prt, page * 4);
|
|
resources.MapMemory(new_prt, page * 6);
|
|
Require("GpuMappedRangeLifecycle", "PRT replacement",
|
|
!resources.IsMapped(old_prt, page * 4) &&
|
|
resources.IsMapped(new_prt, page * 6),
|
|
"old-unmap/new-map did not replace full PRT coverage");
|
|
|
|
resources.SetGpu(nullptr);
|
|
scheduler.Finish();
|
|
context.ShutdownGpu();
|
|
std::printf("[host] %-32s ok\n", "GpuMappedRangeLifecycle");
|
|
}
|
|
|
|
void CheckStreamBufferRing() {
|
|
EnsureRuntimeContext();
|
|
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
GpuResourceManager resources(m_runtime_context, scheduler);
|
|
auto &cache = resources.GetBufferCache();
|
|
constexpr std::array<std::pair<MemoryUsage, uint64_t>, 4> utilities{{
|
|
{MemoryUsage::Upload, 512ull << 20},
|
|
{MemoryUsage::Stream, 64ull << 20},
|
|
{MemoryUsage::Download, 32ull << 20},
|
|
{MemoryUsage::DeviceLocal, 128ull << 20},
|
|
}};
|
|
std::array<vk::Buffer, utilities.size()> handles{};
|
|
for (size_t i = 0; i < utilities.size(); i++) {
|
|
auto &utility = cache.GetUtilityBuffer(utilities[i].first);
|
|
handles[i] = utility.Handle();
|
|
Require("StreamBufferRing", "utility ownership",
|
|
utility.Usage() == utilities[i].first &&
|
|
utility.Size() == utilities[i].second &&
|
|
utility.Handle() != nullptr,
|
|
"BufferCache utility buffer does not match the fixed layout");
|
|
}
|
|
for (size_t i = 0; i < handles.size(); i++) {
|
|
for (size_t j = i + 1; j < handles.size(); j++) {
|
|
Require("StreamBufferRing", "utility identity",
|
|
handles[i] != handles[j],
|
|
"utility usages alias the same native buffer");
|
|
}
|
|
}
|
|
auto *fixed_download = &cache.GetUtilityBuffer(MemoryUsage::Download);
|
|
const auto fixed_handle = fixed_download->Handle();
|
|
const auto [oversized_download, oversized_offset] =
|
|
fixed_download->Map(fixed_download->Size() + 4, 64, false);
|
|
const auto [download_probe, download_probe_offset] =
|
|
fixed_download->Map(64, 64, false);
|
|
Require("StreamBufferRing", "fixed download utility",
|
|
oversized_download == nullptr && oversized_offset == 0 &&
|
|
download_probe != nullptr && download_probe_offset == 0 &&
|
|
&cache.GetUtilityBuffer(MemoryUsage::Download) ==
|
|
fixed_download &&
|
|
fixed_download->Size() == (32ull << 20) &&
|
|
fixed_download->Handle() == fixed_handle,
|
|
"oversized download replaced or corrupted the fixed shared ring");
|
|
fixed_download->Commit();
|
|
|
|
const std::array<uint8_t, 16> overflow_data{
|
|
0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
|
|
0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5};
|
|
auto &upload_stream = cache.GetUtilityBuffer(MemoryUsage::Stream);
|
|
const auto stream_offset =
|
|
upload_stream.Copy(overflow_data.data(), overflow_data.size(), 16);
|
|
Require("StreamBufferRing", "cache-owned stream upload",
|
|
upload_stream.Handle() != nullptr &&
|
|
stream_offset < upload_stream.Size() &&
|
|
std::memcmp(upload_stream.Mapped().data() + stream_offset,
|
|
overflow_data.data(), overflow_data.size()) == 0,
|
|
"cache-owned stream upload lost bytes");
|
|
|
|
const auto atom =
|
|
m_runtime_context.physical_device_properties.limits.nonCoherentAtomSize;
|
|
uint64_t policy_size = 17;
|
|
uint64_t policy_alignment = 12;
|
|
uint64_t coherent_size = policy_size;
|
|
uint64_t coherent_alignment = policy_alignment;
|
|
Require("StreamBufferRing", "non-coherent reservation policy",
|
|
StreamBufferTestAccess::NormalizeReservation(
|
|
false, 256, policy_size, policy_alignment) &&
|
|
policy_size == 256 && policy_alignment == 768 &&
|
|
StreamBufferTestAccess::NormalizeReservation(
|
|
true, 256, coherent_size, coherent_alignment) &&
|
|
coherent_size == 17 && coherent_alignment == 12,
|
|
"non-coherent reservations are not isolated at the atom/texel LCM");
|
|
const auto small_ring_size = std::max<uint64_t>(64, atom * 2);
|
|
StreamBuffer stream(m_runtime_context, scheduler, MemoryUsage::Upload,
|
|
small_ring_size);
|
|
const auto first_size =
|
|
stream.IsCoherent() ? small_ring_size - 16 : small_ring_size - atom + 1;
|
|
constexpr uint64_t blocked_size = 24;
|
|
const auto [first, first_offset] = stream.Map(first_size, 16, false);
|
|
Require("StreamBufferRing", "first allocation",
|
|
first != nullptr && first_offset == 0,
|
|
"initial stream allocation failed");
|
|
std::memset(first, 0x5a, static_cast<size_t>(first_size));
|
|
stream.Commit();
|
|
|
|
const auto [oversize, oversize_offset] =
|
|
stream.Map(small_ring_size + 1, 1, false);
|
|
Require("StreamBufferRing", "oversize",
|
|
oversize == nullptr && oversize_offset == 0,
|
|
"oversize allocation was accepted");
|
|
const auto [blocked, blocked_offset] = stream.Map(blocked_size, 1, false);
|
|
const auto [blocked_again, blocked_again_offset] =
|
|
stream.Map(blocked_size, 1, false);
|
|
Require("StreamBufferRing", "transactional failure",
|
|
blocked == nullptr && blocked_offset == 0 &&
|
|
blocked_again == nullptr && blocked_again_offset == 0,
|
|
"failed wrap mutated stream state");
|
|
|
|
scheduler.Flush();
|
|
const auto [wrapped, wrapped_offset] = stream.Map(blocked_size, 1);
|
|
Require("StreamBufferRing", "watched wrap",
|
|
wrapped != nullptr && wrapped_offset == 0,
|
|
"completed watch did not release the wrapped allocation");
|
|
stream.Commit();
|
|
|
|
const auto download_alignment = atom / std::gcd<uint64_t>(atom, 12) * 12;
|
|
const auto download_ring_size = std::max<uint64_t>(
|
|
64, download_alignment + std::max<uint64_t>(atom, 16));
|
|
StreamBuffer download(m_runtime_context, scheduler, MemoryUsage::Download,
|
|
download_ring_size);
|
|
const auto [download_data, download_offset] = download.Map(16, 8);
|
|
Require("StreamBufferRing", "download allocation",
|
|
download_data != nullptr && download_offset == 0,
|
|
"download stream is not host visible");
|
|
download.Commit();
|
|
scheduler.Flush();
|
|
const auto [second_download, second_download_offset] = download.Map(16, 12);
|
|
Require("StreamBufferRing", "download atom isolation",
|
|
second_download != nullptr &&
|
|
(download.IsCoherent() ||
|
|
(download_offset % atom == 0 &&
|
|
second_download_offset % atom == 0 &&
|
|
second_download_offset >= download_offset + atom)) &&
|
|
second_download_offset % 12 == 0,
|
|
"download reservations share a non-coherent atom or lost caller "
|
|
"alignment");
|
|
download.Commit();
|
|
|
|
scheduler.Finish();
|
|
download.Invalidate(download_offset, 16);
|
|
std::printf("[host] %-32s ok\n", "StreamBufferRing");
|
|
}
|
|
|
|
void CheckGpuCommandLane() {
|
|
EnsureRuntimeContext();
|
|
auto &context = Renderer();
|
|
context.InitializeGpu(nullptr);
|
|
auto &gpu = context.GetGpu();
|
|
|
|
const auto caller_thread = std::this_thread::get_id();
|
|
std::thread::id gpu_thread;
|
|
std::thread::id nested_thread;
|
|
uint32_t order = 0;
|
|
gpu.SendCommand([&order] { order = 1; });
|
|
gpu.SendCommandSync([&] {
|
|
gpu_thread = std::this_thread::get_id();
|
|
Require("GpuCommandLane", "FIFO", order == 1,
|
|
"synchronous command overtook an older host command");
|
|
Require("GpuCommandLane", "GPU context", GuestGpu::IsGpuThread(),
|
|
"host command did not run on the GPU thread");
|
|
gpu.SendCommandSync(
|
|
[&nested_thread] { nested_thread = std::this_thread::get_id(); });
|
|
order = 2;
|
|
});
|
|
Require(
|
|
"GpuCommandLane", "dispatch thread",
|
|
order == 2 && gpu_thread != caller_thread &&
|
|
nested_thread == gpu_thread,
|
|
"host command did not run on the GPU thread or nested sync deadlocked");
|
|
|
|
{
|
|
const auto make_release_mem =
|
|
[](uint32_t data_sel, uint32_t interrupt_selector, void *destination,
|
|
uint64_t value, uint32_t action = 0x28u,
|
|
uint32_t gcr_cntl = 1u << 9u) {
|
|
std::array<uint32_t, 8> packet{};
|
|
const auto address = reinterpret_cast<uint64_t>(destination);
|
|
const auto event_index = (action >= 0x2fu ? 6u : 5u);
|
|
packet[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM);
|
|
packet[1] = action | (event_index << 8u) | (gcr_cntl << 12u);
|
|
packet[2] = (interrupt_selector << 24u) | (data_sel << 29u);
|
|
packet[3] = static_cast<uint32_t>(address);
|
|
packet[4] = static_cast<uint32_t>(address >> 32u);
|
|
packet[5] = static_cast<uint32_t>(value);
|
|
packet[6] = static_cast<uint32_t>(value >> 32u);
|
|
packet[7] = 0x123u;
|
|
return packet;
|
|
};
|
|
|
|
vk::SemaphoreTypeCreateInfo timeline_type{};
|
|
timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
|
timeline_type.semaphoreType = vk::SemaphoreType::eTimeline;
|
|
vk::SemaphoreCreateInfo timeline_create{};
|
|
timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo;
|
|
timeline_create.pNext = &timeline_type;
|
|
vk::Semaphore blocked_timeline = nullptr;
|
|
Require("GpuCommandLane", "blocked timeline create",
|
|
m_runtime_context.device.createSemaphore(
|
|
&timeline_create, nullptr, &blocked_timeline) ==
|
|
vk::Result::eSuccess &&
|
|
blocked_timeline != nullptr,
|
|
"failed to create the blocked GPU timeline");
|
|
|
|
constexpr uint64_t release_tick = 1;
|
|
auto &gpu_scheduler = context.GetCommandScheduler();
|
|
auto processor = std::make_unique<CommandProcessor>(context, 0);
|
|
gpu.SendCommandSync([&] {
|
|
processor->BufferInit();
|
|
SubmitInfo blocked_submit;
|
|
blocked_submit.AddWait(blocked_timeline, release_tick);
|
|
gpu_scheduler.Flush(blocked_submit);
|
|
});
|
|
|
|
std::binary_semaphore parser_complete{0};
|
|
alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX;
|
|
alignas(uint64_t) uint64_t cb_db_release_label = UINT64_MAX;
|
|
bool parser_kept_nonblocking_boundaries = false;
|
|
std::jthread no_gpu_wait([&] {
|
|
gpu.SendCommandSync([&] {
|
|
processor->BufferInit();
|
|
const auto tick_before = gpu_scheduler.CurrentTick();
|
|
processor->TriggerEvent(0x16u, 0u);
|
|
auto packet = make_release_mem(0, 0, nullptr, 0);
|
|
Pm4Execution execution;
|
|
const auto result = processor->Process(execution, packet);
|
|
processor->IncrementDe();
|
|
const bool cache_and_counter_did_not_submit =
|
|
result == Pm4ProcessResult::Complete &&
|
|
gpu_scheduler.CurrentTick() == tick_before;
|
|
|
|
const auto cb_db_tick = gpu_scheduler.CurrentTick();
|
|
auto cb_db_release =
|
|
make_release_mem(2, 0, &cb_db_release_label, 0, 0x14u, 0);
|
|
Pm4Execution cb_db_execution;
|
|
const auto cb_db_result =
|
|
processor->Process(cb_db_execution, cb_db_release);
|
|
const bool cb_db_release_did_not_submit =
|
|
cb_db_result == Pm4ProcessResult::Complete &&
|
|
cb_db_release_label == 0 &&
|
|
gpu_scheduler.CurrentTick() == cb_db_tick;
|
|
|
|
auto gds_interrupt_only =
|
|
make_release_mem(5, 1, &interrupt_only_gds_label, 1ull << 16u);
|
|
Pm4Execution gds_interrupt_execution;
|
|
const auto interrupt_tick = gpu_scheduler.CurrentTick();
|
|
const auto interrupt_result =
|
|
processor->Process(gds_interrupt_execution, gds_interrupt_only);
|
|
parser_kept_nonblocking_boundaries =
|
|
cache_and_counter_did_not_submit &&
|
|
cb_db_release_did_not_submit &&
|
|
interrupt_result == Pm4ProcessResult::Complete &&
|
|
gpu_scheduler.CurrentTick() == interrupt_tick + 1 &&
|
|
interrupt_only_gds_label == UINT64_MAX;
|
|
});
|
|
parser_complete.release();
|
|
});
|
|
const bool parser_did_not_wait_gpu =
|
|
parser_complete.try_acquire_for(std::chrono::seconds(2));
|
|
|
|
vk::SemaphoreSignalInfo signal_info{};
|
|
signal_info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
|
signal_info.semaphore = blocked_timeline;
|
|
signal_info.value = release_tick;
|
|
Require("GpuCommandLane", "blocked timeline signal",
|
|
m_runtime_context.device.signalSemaphore(&signal_info) ==
|
|
vk::Result::eSuccess,
|
|
"failed to release the blocked GPU timeline");
|
|
no_gpu_wait.join();
|
|
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
|
|
m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr);
|
|
Require("GpuCommandLane", "nonblocking GPU packets",
|
|
parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries,
|
|
"a cache event, GL2 writeback, DE counter, or interrupt boundary "
|
|
"used the wrong "
|
|
"submission behavior");
|
|
|
|
alignas(uint64_t) uint64_t release_label = 0;
|
|
alignas(uint64_t) uint64_t gds_label = UINT64_MAX;
|
|
bool release_mem_submission_counts = false;
|
|
gpu.SendCommandSync([&] {
|
|
processor->BufferInit();
|
|
|
|
auto immediate = make_release_mem(1, 0, &release_label, 0x11223344u);
|
|
Pm4Execution immediate_execution;
|
|
const auto immediate_tick = gpu_scheduler.CurrentTick();
|
|
const auto immediate_result =
|
|
processor->Process(immediate_execution, immediate);
|
|
const bool immediate_split_once =
|
|
gpu_scheduler.CurrentTick() == immediate_tick + 1;
|
|
|
|
auto gds = make_release_mem(5, 0, &gds_label, 1ull << 16u);
|
|
Pm4Execution gds_execution;
|
|
const auto gds_tick = gpu_scheduler.CurrentTick();
|
|
const auto gds_result = processor->Process(gds_execution, gds);
|
|
const bool gds_waited_once =
|
|
gpu_scheduler.CurrentTick() == gds_tick + 1;
|
|
|
|
auto interrupt_only = make_release_mem(0, 4, nullptr, 0);
|
|
Pm4Execution interrupt_execution;
|
|
const auto interrupt_tick = gpu_scheduler.CurrentTick();
|
|
const auto interrupt_result =
|
|
processor->Process(interrupt_execution, interrupt_only);
|
|
const bool interrupt_split_once =
|
|
gpu_scheduler.CurrentTick() == interrupt_tick + 1;
|
|
|
|
auto gds_interrupt = make_release_mem(5, 2, &gds_label, 1ull << 16u);
|
|
Pm4Execution gds_interrupt_execution;
|
|
const auto gds_interrupt_tick = gpu_scheduler.CurrentTick();
|
|
const auto gds_interrupt_result =
|
|
processor->Process(gds_interrupt_execution, gds_interrupt);
|
|
const bool gds_interrupt_waited_once =
|
|
gpu_scheduler.CurrentTick() == gds_interrupt_tick + 1;
|
|
|
|
release_mem_submission_counts =
|
|
immediate_result == Pm4ProcessResult::Complete &&
|
|
immediate_split_once && gds_result == Pm4ProcessResult::Complete &&
|
|
gds_waited_once && interrupt_result == Pm4ProcessResult::Complete &&
|
|
interrupt_split_once &&
|
|
gds_interrupt_result == Pm4ProcessResult::Complete &&
|
|
gds_interrupt_waited_once;
|
|
});
|
|
gpu.SendCommandSync([&] {
|
|
gpu_scheduler.Finish();
|
|
gpu_scheduler.WaitPriorityOperations(gpu_scheduler.CurrentTick() - 1);
|
|
});
|
|
Require("GpuCommandLane", "RELEASE_MEM submission counts",
|
|
release_mem_submission_counts &&
|
|
static_cast<uint32_t>(release_label) == 0x11223344u &&
|
|
static_cast<uint32_t>(gds_label) == 0,
|
|
"RELEASE_MEM lost its required split/readback or retained a "
|
|
"redundant GPU wait");
|
|
}
|
|
|
|
alignas(uint32_t) uint32_t packet_marker_a = 0;
|
|
alignas(uint32_t) uint32_t packet_marker_b = 0;
|
|
const auto write_packet = [](uint32_t *packet, uint32_t *destination,
|
|
uint32_t value) {
|
|
const auto destination_address = reinterpret_cast<uint64_t>(destination);
|
|
packet[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
packet[1] = 0;
|
|
packet[2] = static_cast<uint32_t>(destination_address);
|
|
packet[3] = static_cast<uint32_t>(destination_address >> 32u);
|
|
packet[4] = value;
|
|
};
|
|
|
|
uint32_t live_graphics_value = 0;
|
|
std::array<uint32_t, 5> live_graphics_commands{};
|
|
write_packet(live_graphics_commands.data(), &live_graphics_value, 11);
|
|
std::binary_semaphore graphics_gate_entered{0};
|
|
std::binary_semaphore graphics_gate_release{0};
|
|
gpu.SendCommand([&] {
|
|
graphics_gate_entered.release();
|
|
graphics_gate_release.acquire();
|
|
});
|
|
gpu.Submit(live_graphics_commands, {});
|
|
graphics_gate_entered.acquire();
|
|
live_graphics_commands[4] = 22;
|
|
graphics_gate_release.release();
|
|
gpu.Done();
|
|
Require("GpuCommandLane", "borrowed graphics commands",
|
|
live_graphics_value == 22,
|
|
"graphics submission executed a copied PM4 stream");
|
|
|
|
uint32_t live_compute_value = 0;
|
|
std::array<uint32_t, 5> live_compute_commands{};
|
|
write_packet(live_compute_commands.data(), &live_compute_value, 33);
|
|
std::binary_semaphore compute_gate_entered{0};
|
|
std::binary_semaphore compute_gate_release{0};
|
|
gpu.SendCommand([&] {
|
|
compute_gate_entered.release();
|
|
compute_gate_release.acquire();
|
|
});
|
|
gpu.SubmitCompute(0x20, live_compute_commands);
|
|
compute_gate_entered.acquire();
|
|
live_compute_commands[4] = 44;
|
|
compute_gate_release.release();
|
|
gpu.Done();
|
|
Require("GpuCommandLane", "borrowed compute commands",
|
|
live_compute_value == 44,
|
|
"compute submission executed a copied PM4 stream");
|
|
|
|
LibKernel::EventQueue::KernelEqueue interrupt_queue =
|
|
LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
|
|
uint32_t graphics_interrupt_udata = 0;
|
|
uint32_t compute_interrupt_udata = 0;
|
|
uint32_t other_compute_interrupt_udata = 0;
|
|
const bool interrupt_queue_ready =
|
|
LibKernel::EventQueue::KernelCreateEqueue(&interrupt_queue,
|
|
"pm4-interrupts") == 0 &&
|
|
Sync::AddEqEvent(context, interrupt_queue, 0,
|
|
&graphics_interrupt_udata) == 0 &&
|
|
Sync::AddEqEvent(context, interrupt_queue, 0x20,
|
|
&compute_interrupt_udata) == 0 &&
|
|
Sync::AddEqEvent(context, interrupt_queue, 0x21,
|
|
&other_compute_interrupt_udata) == 0;
|
|
Require("GpuCommandLane", "interrupt queue", interrupt_queue_ready,
|
|
"failed to register the PM4 interrupt events");
|
|
|
|
const auto make_interrupt_packet =
|
|
[](uint32_t data_selector, uint32_t interrupt_selector,
|
|
void *destination, uint64_t value, uint32_t context_id) {
|
|
std::array<uint32_t, 8> packet{};
|
|
const auto address = reinterpret_cast<uint64_t>(destination);
|
|
packet[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM);
|
|
packet[1] = 0x28u | (5u << 8u);
|
|
packet[2] = (interrupt_selector << 24u) | (data_selector << 29u);
|
|
packet[3] = static_cast<uint32_t>(address);
|
|
packet[4] = static_cast<uint32_t>(address >> 32u);
|
|
packet[5] = static_cast<uint32_t>(value);
|
|
packet[6] = static_cast<uint32_t>(value >> 32u);
|
|
packet[7] = context_id & 0x07ffffffu;
|
|
return packet;
|
|
};
|
|
const auto finish_gpu = [&] {
|
|
gpu.SendCommandSync([&] {
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
scheduler.Finish();
|
|
scheduler.WaitPriorityOperations(scheduler.CurrentTick() - 1);
|
|
});
|
|
};
|
|
const auto wait_for_interrupt =
|
|
[&](LibKernel::EventQueue::KernelEvent &event, int &count) {
|
|
LibKernel::KernelUseconds timeout = 0;
|
|
return LibKernel::EventQueue::KernelWaitEqueue(
|
|
interrupt_queue, &event, 1, &count, &timeout);
|
|
};
|
|
|
|
uint32_t no_interrupt_graphics_value = 0;
|
|
std::array<uint32_t, 5> no_interrupt_graphics_commands{};
|
|
write_packet(no_interrupt_graphics_commands.data(),
|
|
&no_interrupt_graphics_value, 55);
|
|
gpu.Submit(no_interrupt_graphics_commands, {});
|
|
gpu.Done();
|
|
finish_gpu();
|
|
|
|
LibKernel::EventQueue::KernelEvent interrupt_event{};
|
|
int interrupt_count = 0;
|
|
const auto no_graphics_interrupt_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
Require("GpuCommandLane", "no implicit graphics interrupt",
|
|
no_graphics_interrupt_wait == LibKernel::KERNEL_ERROR_ETIMEDOUT &&
|
|
interrupt_count == 0 && no_interrupt_graphics_value == 55,
|
|
"graphics submission completion synthesized an interrupt");
|
|
|
|
auto live_interrupt_commands =
|
|
make_interrupt_packet(0, 1, nullptr, 0, 0x234u);
|
|
std::binary_semaphore interrupt_gate_entered{0};
|
|
std::binary_semaphore interrupt_gate_release{0};
|
|
gpu.SendCommand([&] {
|
|
interrupt_gate_entered.release();
|
|
interrupt_gate_release.acquire();
|
|
});
|
|
gpu.SubmitCompute(0x20, live_interrupt_commands);
|
|
interrupt_gate_entered.acquire();
|
|
live_interrupt_commands[2] = 0;
|
|
interrupt_gate_release.release();
|
|
gpu.Done();
|
|
finish_gpu();
|
|
|
|
interrupt_count = 0;
|
|
const auto no_interrupt_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
Require("GpuCommandLane", "no implicit completion interrupt",
|
|
no_interrupt_wait == LibKernel::KERNEL_ERROR_ETIMEDOUT &&
|
|
interrupt_count == 0,
|
|
"submission completion synthesized an interrupt absent from the "
|
|
"executed PM4 stream");
|
|
|
|
uint32_t graphics_interrupt_label = 0xa5a5a5a5u;
|
|
auto graphics_interrupt_commands = make_interrupt_packet(
|
|
1, 1, &graphics_interrupt_label, 0x11223344u, 0);
|
|
gpu.Submit(graphics_interrupt_commands, {});
|
|
gpu.Done();
|
|
finish_gpu();
|
|
|
|
interrupt_count = 0;
|
|
const auto graphics_interrupt_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
Require("GpuCommandLane", "graphics interrupt routing",
|
|
graphics_interrupt_wait == 0 && interrupt_count == 1 &&
|
|
interrupt_event.ident == 0 && interrupt_event.data == 0 &&
|
|
interrupt_event.udata == &graphics_interrupt_udata &&
|
|
graphics_interrupt_label == 0xa5a5a5a5u,
|
|
"graphics kOnly interrupt was misrouted or wrote its label");
|
|
|
|
uint32_t compute_interrupt_label = 0x5a5a5a5au;
|
|
auto compute_interrupt_commands = make_interrupt_packet(
|
|
1, 0, &compute_interrupt_label, 0x55667788u, 0x456u);
|
|
std::binary_semaphore compute_interrupt_gate_entered{0};
|
|
std::binary_semaphore compute_interrupt_gate_release{0};
|
|
gpu.SendCommand([&] {
|
|
compute_interrupt_gate_entered.release();
|
|
compute_interrupt_gate_release.acquire();
|
|
});
|
|
gpu.SubmitCompute(0x20, compute_interrupt_commands);
|
|
compute_interrupt_gate_entered.acquire();
|
|
compute_interrupt_commands[2] |= 1u << 24u;
|
|
compute_interrupt_gate_release.release();
|
|
gpu.Done();
|
|
finish_gpu();
|
|
|
|
interrupt_count = 0;
|
|
const auto compute_interrupt_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
const bool compute_interrupt_matches =
|
|
compute_interrupt_wait == 0 && interrupt_count == 1 &&
|
|
interrupt_event.ident == 0x20 && interrupt_event.data == 0x456u &&
|
|
interrupt_event.udata == &compute_interrupt_udata &&
|
|
compute_interrupt_label == 0x55667788u;
|
|
|
|
uint64_t compute_clock_label = 0;
|
|
auto compute_clock_commands = make_interrupt_packet(
|
|
3, 1, &compute_clock_label, 0, 0x567u);
|
|
gpu.SubmitCompute(0x20, compute_clock_commands);
|
|
gpu.Done();
|
|
finish_gpu();
|
|
|
|
interrupt_count = 0;
|
|
const auto compute_clock_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
const bool compute_clock_matches =
|
|
compute_clock_wait == 0 && interrupt_count == 1 &&
|
|
interrupt_event.ident == 0x20 && interrupt_event.data == 0x567u &&
|
|
interrupt_event.udata == &compute_interrupt_udata &&
|
|
compute_clock_label != 0;
|
|
|
|
interrupt_count = 0;
|
|
const auto extra_interrupt_wait =
|
|
wait_for_interrupt(interrupt_event, interrupt_count);
|
|
const auto delete_other_compute_event =
|
|
Sync::DeleteEqEvent(context, interrupt_queue, 0x21);
|
|
LibKernel::EventQueue::KernelDeleteEqueue(interrupt_queue);
|
|
context.TriggerInterrupt(0, 0);
|
|
context.TriggerInterrupt(0x20, 0);
|
|
context.TriggerInterrupt(0x21, 0);
|
|
Require("GpuCommandLane", "compute interrupt routing",
|
|
compute_interrupt_matches && compute_clock_matches &&
|
|
extra_interrupt_wait == LibKernel::KERNEL_ERROR_ETIMEDOUT &&
|
|
interrupt_count == 0 && delete_other_compute_event == 0,
|
|
"compute kOnly interrupt was misrouted, broadcast, or lost a "
|
|
"label write");
|
|
|
|
std::array<uint32_t, 1024> polling_loop{};
|
|
for (size_t i = 0; i < polling_loop.size() - 4; i += 2) {
|
|
polling_loop[i] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
|
polling_loop[i + 1] = 0;
|
|
}
|
|
const auto loop_address = reinterpret_cast<uint64_t>(polling_loop.data());
|
|
polling_loop[1020] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
|
polling_loop[1021] = static_cast<uint32_t>(loop_address);
|
|
polling_loop[1022] = static_cast<uint32_t>(loop_address >> 32u);
|
|
polling_loop[1023] =
|
|
0x0f200000u | static_cast<uint32_t>(polling_loop.size());
|
|
|
|
std::array<uint32_t, 14> polling_commands{};
|
|
write_packet(polling_commands.data(), &packet_marker_a, 11);
|
|
polling_commands[5] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
|
polling_commands[6] = static_cast<uint32_t>(loop_address);
|
|
polling_commands[7] = static_cast<uint32_t>(loop_address >> 32u);
|
|
polling_commands[8] =
|
|
0x0f200000u | static_cast<uint32_t>(polling_loop.size());
|
|
write_packet(polling_commands.data() + 9, &packet_marker_b, 22);
|
|
std::binary_semaphore stream_gate_entered{0};
|
|
std::binary_semaphore stream_gate_release{0};
|
|
gpu.SendCommand([&] {
|
|
stream_gate_entered.release();
|
|
stream_gate_release.acquire();
|
|
});
|
|
gpu.Submit(polling_commands, {});
|
|
stream_gate_entered.acquire();
|
|
stream_gate_release.release();
|
|
uint32_t packet_marker_a_at_callback = UINT32_MAX;
|
|
uint32_t packet_marker_b_at_callback = UINT32_MAX;
|
|
for (uint32_t attempt = 0; attempt < 8 && packet_marker_a_at_callback != 11;
|
|
attempt++) {
|
|
gpu.SendCommandSync([&] {
|
|
if (packet_marker_a == 11 && packet_marker_b == 0) {
|
|
packet_marker_a_at_callback = packet_marker_a;
|
|
packet_marker_b_at_callback = packet_marker_b;
|
|
polling_commands[13] = 33;
|
|
polling_loop[1020] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
|
polling_loop[1021] = 0;
|
|
polling_loop[1022] = KYTY_PM4(2, Pm4::IT_NOP, Pm4::R_ZERO);
|
|
polling_loop[1023] = 0;
|
|
}
|
|
});
|
|
std::this_thread::yield();
|
|
}
|
|
gpu.Done();
|
|
Require("GpuCommandLane", "packet-boundary command polling",
|
|
packet_marker_a_at_callback == 11 &&
|
|
packet_marker_b_at_callback == 0 && packet_marker_a == 11 &&
|
|
packet_marker_b == 33,
|
|
"host command waited for an entire PM4 stream or resumed a copied "
|
|
"stream");
|
|
|
|
uint32_t label = 0;
|
|
uint32_t prefix = 0;
|
|
uint32_t suffix = 0;
|
|
const auto address = [](const void *value) {
|
|
return reinterpret_cast<uint64_t>(value);
|
|
};
|
|
std::array<uint32_t, 17> commands{};
|
|
commands[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[1] = 0;
|
|
commands[2] = static_cast<uint32_t>(address(&prefix));
|
|
commands[3] = static_cast<uint32_t>(address(&prefix) >> 32u);
|
|
commands[4] = 11;
|
|
commands[5] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
|
commands[6] = 0x10u | 3u;
|
|
commands[7] = static_cast<uint32_t>(address(&label));
|
|
commands[8] = static_cast<uint32_t>(address(&label) >> 32u);
|
|
commands[9] = 1;
|
|
commands[10] = UINT32_MAX;
|
|
commands[12] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[13] = 0;
|
|
commands[14] = static_cast<uint32_t>(address(&suffix));
|
|
commands[15] = static_cast<uint32_t>(address(&suffix) >> 32u);
|
|
commands[16] = 22;
|
|
gpu.Submit(commands, {});
|
|
|
|
std::binary_semaphore ordered_started{0};
|
|
std::atomic<bool> ordered_finished{false};
|
|
uint32_t ordered_suffix = 0;
|
|
std::jthread ordered([&] {
|
|
ordered_started.release();
|
|
gpu.Done();
|
|
ordered_suffix = suffix;
|
|
ordered_finished = true;
|
|
});
|
|
ordered_started.acquire();
|
|
gpu.SendCommandSync([&] {
|
|
Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(),
|
|
"submit done returned before a queued submission");
|
|
label = 1;
|
|
});
|
|
ordered.join();
|
|
Require("GpuCommandLane", "ordered completion",
|
|
prefix == 11 && suffix == 22 && ordered_suffix == 22 &&
|
|
ordered_finished.load(),
|
|
"submit done did not drain prior PM4 work");
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
|
|
constexpr uint64_t empty_unmap_size = 0x4000;
|
|
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
|
label = 0;
|
|
prefix = 0;
|
|
suffix = 0;
|
|
gpu.Submit(commands, {});
|
|
|
|
std::binary_semaphore unmap_complete{0};
|
|
std::jthread unmap_thread([&] {
|
|
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
|
unmap_complete.release();
|
|
});
|
|
const bool unmap_returned =
|
|
unmap_complete.try_acquire_for(std::chrono::seconds(2));
|
|
gpu.SendCommandSync([&] { label = 1; });
|
|
if (!unmap_returned) {
|
|
unmap_complete.acquire();
|
|
}
|
|
unmap_thread.join();
|
|
gpu.Done();
|
|
Require("GpuCommandLane", "unmap queue progress",
|
|
unmap_returned &&
|
|
!resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
|
|
prefix == 11 && suffix == 22,
|
|
"an unrelated unmap waited for a blocked PM4 submission");
|
|
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
std::atomic<bool> normal_completed{false};
|
|
gpu.SendCommandSync(
|
|
[&] { scheduler.DeferOperation([&] { normal_completed = true; }); });
|
|
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
|
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
|
Require("GpuCommandLane", "unmap native completion",
|
|
normal_completed.load() &&
|
|
!resources.IsMapped(empty_unmap_base, empty_unmap_size),
|
|
"unmap returned before an earlier native guest-memory callback");
|
|
|
|
std::binary_semaphore priority_entered{0};
|
|
std::binary_semaphore release_priority{0};
|
|
gpu.SendCommandSync([&] {
|
|
scheduler.DeferPriorityOperation([&] {
|
|
priority_entered.release();
|
|
release_priority.acquire();
|
|
});
|
|
scheduler.Flush();
|
|
});
|
|
priority_entered.acquire();
|
|
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
|
|
|
std::binary_semaphore priority_unmap_entered{0};
|
|
std::binary_semaphore priority_unmap_complete{0};
|
|
std::jthread priority_unmap_thread([&] {
|
|
gpu.SendCommandSync([&] {
|
|
priority_unmap_entered.release();
|
|
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
|
});
|
|
priority_unmap_complete.release();
|
|
});
|
|
priority_unmap_entered.acquire();
|
|
const bool unmap_overtook_priority =
|
|
priority_unmap_complete.try_acquire_for(std::chrono::seconds(1));
|
|
release_priority.release();
|
|
if (!unmap_overtook_priority) {
|
|
priority_unmap_complete.acquire();
|
|
}
|
|
priority_unmap_thread.join();
|
|
Require("GpuCommandLane", "unmap priority ordering",
|
|
!unmap_overtook_priority &&
|
|
!resources.IsMapped(empty_unmap_base, empty_unmap_size),
|
|
"unmap returned before an earlier guest-memory callback");
|
|
|
|
constexpr uintptr_t fault_base = 0x0000000200500000ull;
|
|
constexpr uint64_t fault_size = 0x10000;
|
|
int64_t fault_direct_offset = -1;
|
|
Require("GpuCommandLane", "processor fault direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
fault_size, fault_size, 0, &fault_direct_offset) == 0,
|
|
"processor-fault direct-memory allocation failed");
|
|
void *fault_memory = reinterpret_cast<void *>(fault_base);
|
|
Require("GpuCommandLane", "processor fault direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&fault_memory, fault_size, 0x3, 0x10, fault_direct_offset,
|
|
fault_size) == 0,
|
|
"processor-fault fixed direct-memory mapping failed");
|
|
Require("GpuCommandLane", "processor fault allocation",
|
|
fault_memory == reinterpret_cast<void *>(fault_base),
|
|
"fixed processor-fault allocation failed");
|
|
resources.MapMemory(fault_base, fault_size);
|
|
|
|
constexpr uint64_t immediate_dst = fault_base + 0x1000;
|
|
constexpr uint64_t immediate_memory_dst = fault_base + 0x2000;
|
|
constexpr uint64_t memory_src = fault_base + 0x4000;
|
|
constexpr uint64_t memory_copy_dst = fault_base + 0x6000;
|
|
constexpr uint64_t memory_dst = fault_base + 0x8000;
|
|
constexpr uint64_t l2_copy_dst = fault_base + 0xa000;
|
|
constexpr uint64_t nowhere_dst = fault_base + 0xc000;
|
|
constexpr uint64_t clean_cached_fill = fault_base + 0x3000;
|
|
constexpr uint64_t clean_cached_copy = fault_base + 0x3008;
|
|
constexpr uint64_t clean_cached_source = fault_base + 0x3010;
|
|
constexpr uint64_t clean_cached_readback = fault_base + 0xb000;
|
|
constexpr std::array<uint32_t, 2> source_words{0x11223344u, 0x55667788u};
|
|
constexpr std::array<uint32_t, 2> nowhere_words{0x89abcdefu, 0x01234567u};
|
|
constexpr std::array<uint32_t, 2> clean_copy_words{0x13579bdfu,
|
|
0x2468ace0u};
|
|
std::memcpy(reinterpret_cast<void *>(memory_src), source_words.data(),
|
|
sizeof(source_words));
|
|
std::memcpy(reinterpret_cast<void *>(nowhere_dst), nowhere_words.data(),
|
|
sizeof(nowhere_words));
|
|
std::memcpy(reinterpret_cast<void *>(clean_cached_source),
|
|
clean_copy_words.data(), sizeof(clean_copy_words));
|
|
|
|
std::array<uint32_t, 56> dma_commands{};
|
|
size_t dma_cursor = 0;
|
|
const auto append_dma = [&](uint8_t dst_sel, uint64_t dst, uint8_t src_sel,
|
|
uint64_t src, uint32_t bytes,
|
|
uint32_t command_flags = 0) {
|
|
auto *packet = dma_commands.data() + dma_cursor;
|
|
dma_cursor += 7;
|
|
packet[0] = KYTY_PM4(7, Pm4::IT_DMA_DATA, 0);
|
|
packet[1] = (static_cast<uint32_t>(dst_sel) << 20u) |
|
|
(static_cast<uint32_t>(src_sel) << 29u);
|
|
packet[2] = static_cast<uint32_t>(src);
|
|
packet[3] = static_cast<uint32_t>(src >> 32u);
|
|
packet[4] = static_cast<uint32_t>(dst);
|
|
packet[5] = static_cast<uint32_t>(dst >> 32u);
|
|
packet[6] = bytes | command_flags;
|
|
};
|
|
constexpr uint64_t gds_immediate_offset = 0;
|
|
constexpr uint64_t gds_memory_offset = 0x10;
|
|
constexpr uint32_t immediate_value = 0xa1b2c3d4u;
|
|
append_dma(1, gds_immediate_offset, 2, immediate_value,
|
|
sizeof(source_words));
|
|
append_dma(0, immediate_dst, 1, gds_immediate_offset, sizeof(source_words));
|
|
append_dma(1, gds_memory_offset, 0, memory_src, sizeof(source_words));
|
|
append_dma(0, memory_dst, 1, gds_memory_offset, sizeof(source_words));
|
|
append_dma(0, immediate_memory_dst, 2, immediate_value,
|
|
sizeof(source_words));
|
|
append_dma(0, memory_copy_dst, 0, memory_src, sizeof(source_words));
|
|
append_dma(3, l2_copy_dst, 3, memory_src, sizeof(source_words));
|
|
append_dma(2, nowhere_dst, 3, nowhere_dst, sizeof(nowhere_words),
|
|
1u << 31u);
|
|
Require("GpuCommandLane", "DMA_DATA packet assembly",
|
|
dma_cursor == dma_commands.size(),
|
|
"DMA_DATA packet stream has the wrong size");
|
|
gpu.Submit(dma_commands, {});
|
|
gpu.Done();
|
|
constexpr uint32_t clean_fill_value = 0xdecafbad;
|
|
gpu.SendCommandSync([&] {
|
|
auto &buffer_cache = resources.GetBufferCache();
|
|
Require("GpuCommandLane", "clean cached setup",
|
|
buffer_cache.IsRegionRegistered(clean_cached_fill,
|
|
sizeof(source_words)) &&
|
|
!buffer_cache.HasGpuDirtyBytes(nowhere_dst,
|
|
sizeof(nowhere_words)) &&
|
|
buffer_cache.HasGpuDirtyBytes(immediate_dst,
|
|
sizeof(source_words)),
|
|
"DMA prefetch modified ownership or did not establish a dirty "
|
|
"cached page");
|
|
|
|
buffer_cache.FillBuffer(clean_cached_fill, sizeof(source_words),
|
|
clean_fill_value, false);
|
|
buffer_cache.CopyBuffer(clean_cached_copy, clean_cached_source,
|
|
sizeof(clean_copy_words), false, false);
|
|
Require("GpuCommandLane", "clean cached DMA ownership",
|
|
!buffer_cache.HasGpuDirtyBytes(clean_cached_fill,
|
|
sizeof(source_words)) &&
|
|
!buffer_cache.HasGpuDirtyBytes(clean_cached_copy,
|
|
sizeof(clean_copy_words)) &&
|
|
buffer_cache.HasGpuDirtyBytes(immediate_dst,
|
|
sizeof(source_words)),
|
|
"clean cached DMA acquired ownership or released a byte-disjoint "
|
|
"dirty sibling");
|
|
|
|
constexpr uint64_t gds_clean_mirror = 0x20;
|
|
buffer_cache.CopyBuffer(gds_clean_mirror, clean_cached_fill,
|
|
2 * sizeof(source_words), true, false);
|
|
buffer_cache.CopyBuffer(clean_cached_readback, gds_clean_mirror,
|
|
2 * sizeof(source_words), false, true);
|
|
});
|
|
Require("GpuCommandLane", "DMA_DATA immediate GDS readback",
|
|
resources.HandleFault(PageFaultAccess::Read, immediate_dst),
|
|
"immediate-to-GDS-to-memory copy did not publish GPU bytes");
|
|
Require("GpuCommandLane", "DMA_DATA memory GDS readback",
|
|
resources.HandleFault(PageFaultAccess::Read, memory_dst),
|
|
"memory-to-GDS-to-memory copy did not publish GPU bytes");
|
|
Require("GpuCommandLane", "DMA_DATA immediate memory readback",
|
|
resources.HandleFault(PageFaultAccess::Read, immediate_memory_dst),
|
|
"immediate-to-memory copy did not publish GPU bytes");
|
|
Require("GpuCommandLane", "DMA_DATA memory copy readback",
|
|
resources.HandleFault(PageFaultAccess::Read, memory_copy_dst),
|
|
"memory-to-memory copy did not publish GPU bytes");
|
|
Require("GpuCommandLane", "DMA_DATA L2 readback",
|
|
resources.HandleFault(PageFaultAccess::Read, l2_copy_dst),
|
|
"MemoryUsingL2 copy did not publish GPU bytes");
|
|
Require("GpuCommandLane", "clean cached mirror readback",
|
|
resources.HandleFault(PageFaultAccess::Read, clean_cached_readback),
|
|
"clean host DMA was not reflected in the cached buffer");
|
|
std::array<uint32_t, 2> immediate_words{};
|
|
std::array<uint32_t, 2> copied_words{};
|
|
std::array<uint32_t, 2> immediate_memory_words{};
|
|
std::array<uint32_t, 2> memory_copy_words{};
|
|
std::array<uint32_t, 2> l2_copy_words{};
|
|
std::array<uint32_t, 4> clean_cached_words{};
|
|
std::memcpy(immediate_words.data(),
|
|
reinterpret_cast<const void *>(immediate_dst),
|
|
sizeof(immediate_words));
|
|
std::memcpy(copied_words.data(), reinterpret_cast<const void *>(memory_dst),
|
|
sizeof(copied_words));
|
|
std::memcpy(immediate_memory_words.data(),
|
|
reinterpret_cast<const void *>(immediate_memory_dst),
|
|
sizeof(immediate_memory_words));
|
|
std::memcpy(memory_copy_words.data(),
|
|
reinterpret_cast<const void *>(memory_copy_dst),
|
|
sizeof(memory_copy_words));
|
|
std::memcpy(l2_copy_words.data(),
|
|
reinterpret_cast<const void *>(l2_copy_dst),
|
|
sizeof(l2_copy_words));
|
|
std::memcpy(clean_cached_words.data(),
|
|
reinterpret_cast<const void *>(clean_cached_readback),
|
|
sizeof(clean_cached_words));
|
|
Require("GpuCommandLane", "DMA_DATA immediate GDS contents",
|
|
immediate_words ==
|
|
std::array<uint32_t, 2>{immediate_value, immediate_value},
|
|
"immediate-to-GDS-to-memory bytes do not match");
|
|
Require("GpuCommandLane", "DMA_DATA memory GDS contents",
|
|
copied_words == source_words,
|
|
"memory-to-GDS-to-memory bytes do not match");
|
|
Require("GpuCommandLane", "DMA_DATA immediate memory contents",
|
|
immediate_memory_words ==
|
|
std::array<uint32_t, 2>{immediate_value, immediate_value},
|
|
"immediate-to-memory bytes do not match");
|
|
Require("GpuCommandLane", "DMA_DATA memory copy contents",
|
|
memory_copy_words == source_words,
|
|
"memory-to-memory bytes do not match");
|
|
Require("GpuCommandLane", "DMA_DATA L2 contents",
|
|
l2_copy_words == source_words, "MemoryUsingL2 bytes do not match");
|
|
Require("GpuCommandLane", "DMA_DATA nowhere contents",
|
|
std::memcmp(reinterpret_cast<const void *>(nowhere_dst),
|
|
nowhere_words.data(), sizeof(nowhere_words)) == 0,
|
|
"Nowhere destination modified guest memory");
|
|
Require("GpuCommandLane", "clean cached DMA contents",
|
|
clean_cached_words == std::array<uint32_t, 4>{clean_fill_value,
|
|
clean_fill_value,
|
|
clean_copy_words[0],
|
|
clean_copy_words[1]},
|
|
"host-memory fill/copy did not update the clean cached mirror");
|
|
|
|
gpu.SendCommandSync([&] {
|
|
Require("GpuCommandLane", "GPU memory invalidation",
|
|
resources.InvalidateMemory(fault_base, sizeof(uint32_t)),
|
|
"GPU memory invalidation did not find its mapped range");
|
|
});
|
|
|
|
auto &buffer_cache = resources.GetBufferCache();
|
|
Require("GpuCommandLane", "clean tracked fault setup",
|
|
buffer_cache.IsRegionRegistered(immediate_dst, sizeof(uint32_t)) &&
|
|
!buffer_cache.HasGpuDirtyBytes(immediate_dst, sizeof(uint32_t)),
|
|
"clean write-fault test address is not backed by a clean cached "
|
|
"Buffer");
|
|
constexpr uint64_t dirty_fault_address = fault_base + 0xd000;
|
|
constexpr uint32_t dirty_fault_value = 0x5a17c0deu;
|
|
constexpr uint32_t dirty_fault_stale = 0x0ddba11u;
|
|
std::memcpy(reinterpret_cast<void *>(dirty_fault_address),
|
|
&dirty_fault_stale, sizeof(dirty_fault_stale));
|
|
gpu.SendCommandSync([&] {
|
|
auto dirty = buffer_cache.ObtainBuffer(
|
|
dirty_fault_address, sizeof(dirty_fault_value), true, false);
|
|
Require("GpuCommandLane", "dirty tracked fault setup",
|
|
dirty.first != nullptr,
|
|
"dirty write-fault test could not create a cached Buffer");
|
|
buffer_cache.FillBuffer(dirty_fault_address, sizeof(dirty_fault_value),
|
|
dirty_fault_value, false);
|
|
});
|
|
Require("GpuCommandLane", "dirty tracked fault ownership",
|
|
buffer_cache.HasGpuDirtyBytes(dirty_fault_address,
|
|
sizeof(dirty_fault_value)),
|
|
"dirty write-fault test address did not acquire GPU ownership");
|
|
|
|
std::binary_semaphore block_entered{0};
|
|
std::binary_semaphore block_release{0};
|
|
std::binary_semaphore clean_fault_complete{0};
|
|
bool clean_fault_handled = false;
|
|
gpu.SendCommand([&] {
|
|
block_entered.release();
|
|
block_release.acquire();
|
|
});
|
|
block_entered.acquire();
|
|
std::jthread clean_fault_thread([&] {
|
|
clean_fault_handled =
|
|
resources.HandleFault(PageFaultAccess::Write, immediate_dst);
|
|
clean_fault_complete.release();
|
|
});
|
|
const bool clean_fault_was_direct =
|
|
clean_fault_complete.try_acquire_for(std::chrono::seconds(1));
|
|
if (!clean_fault_was_direct) {
|
|
block_release.release();
|
|
clean_fault_complete.acquire();
|
|
}
|
|
clean_fault_thread.join();
|
|
Require("GpuCommandLane", "clean fault direct path",
|
|
clean_fault_was_direct && clean_fault_handled,
|
|
"a clean CPU write fault waited for the blocked GPU command lane");
|
|
|
|
std::binary_semaphore dirty_fault_complete{0};
|
|
bool dirty_fault_handled = false;
|
|
std::jthread dirty_fault_thread([&] {
|
|
dirty_fault_handled =
|
|
resources.HandleFault(PageFaultAccess::Write, dirty_fault_address);
|
|
dirty_fault_complete.release();
|
|
});
|
|
const bool dirty_fault_bypassed_lane =
|
|
dirty_fault_complete.try_acquire_for(std::chrono::milliseconds(200));
|
|
block_release.release();
|
|
if (!dirty_fault_bypassed_lane) {
|
|
dirty_fault_complete.acquire();
|
|
}
|
|
dirty_fault_thread.join();
|
|
uint32_t dirty_fault_backing = 0;
|
|
std::memcpy(&dirty_fault_backing,
|
|
reinterpret_cast<const void *>(dirty_fault_address),
|
|
sizeof(dirty_fault_backing));
|
|
Require("GpuCommandLane", "dirty fault synchronization",
|
|
!dirty_fault_bypassed_lane && dirty_fault_handled &&
|
|
dirty_fault_backing == dirty_fault_value &&
|
|
!buffer_cache.HasGpuDirtyBytes(dirty_fault_address,
|
|
sizeof(dirty_fault_value)),
|
|
"a GPU-dirty write fault bypassed synchronization or lost the "
|
|
"published bytes");
|
|
|
|
constexpr uint64_t empty_copy_fault_address = fault_base + 0xe000;
|
|
constexpr uint32_t empty_copy_fault_value = 0x6b28d1efu;
|
|
gpu.SendCommandSync([&] {
|
|
auto dirty = buffer_cache.ObtainBuffer(empty_copy_fault_address,
|
|
sizeof(empty_copy_fault_value),
|
|
true, false);
|
|
Require("GpuCommandLane", "empty-copy write setup",
|
|
dirty.first != nullptr,
|
|
"empty-copy write test could not create a cached Buffer");
|
|
buffer_cache.FillBuffer(empty_copy_fault_address,
|
|
sizeof(empty_copy_fault_value),
|
|
empty_copy_fault_value, false);
|
|
buffer_cache.ReadMemory(empty_copy_fault_address,
|
|
sizeof(empty_copy_fault_value));
|
|
const bool gpu_owned = buffer_cache.IsRegionGpuModified(
|
|
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
|
const bool cpu_owned = buffer_cache.IsRegionCpuModified(
|
|
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
|
Require("GpuCommandLane", "empty-copy read ownership",
|
|
!gpu_owned && !cpu_owned,
|
|
"readback did not leave clean ownership for the queued write "
|
|
"invalidation");
|
|
buffer_cache.ReadMemory(empty_copy_fault_address,
|
|
sizeof(empty_copy_fault_value), true);
|
|
});
|
|
uint32_t empty_copy_fault_backing = 0;
|
|
std::memcpy(&empty_copy_fault_backing,
|
|
reinterpret_cast<const void *>(empty_copy_fault_address),
|
|
sizeof(empty_copy_fault_backing));
|
|
const bool empty_copy_cpu_owned = buffer_cache.IsRegionCpuModified(
|
|
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
|
const bool empty_copy_gpu_owned = buffer_cache.IsRegionGpuModified(
|
|
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
|
const bool empty_copy_dirty = buffer_cache.HasGpuDirtyBytes(
|
|
empty_copy_fault_address, sizeof(empty_copy_fault_value));
|
|
Require("GpuCommandLane", "empty-copy write ownership",
|
|
empty_copy_fault_backing == empty_copy_fault_value &&
|
|
empty_copy_cpu_owned && !empty_copy_gpu_owned &&
|
|
!empty_copy_dirty,
|
|
"write invalidation without a remaining copy did not establish CPU "
|
|
"ownership");
|
|
|
|
resources.UnmapMemory(fault_base, fault_size);
|
|
Require("GpuCommandLane", "processor fault unmap",
|
|
Libs::LibKernel::Memory::KernelMunmap(fault_base, fault_size) == 0,
|
|
"processor-fault direct-memory mapping release failed");
|
|
Require("GpuCommandLane", "processor fault free",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
fault_direct_offset, fault_size) == 0,
|
|
"processor-fault direct-memory allocation release failed");
|
|
|
|
std::binary_semaphore command_entered{0};
|
|
std::binary_semaphore release_command{0};
|
|
std::atomic<bool> shutdown_complete{false};
|
|
std::atomic<bool> submission_lane_entered{false};
|
|
gpu.SendCommand([&] {
|
|
command_entered.release();
|
|
release_command.acquire();
|
|
gpu.Done();
|
|
submission_lane_entered = true;
|
|
});
|
|
command_entered.acquire();
|
|
std::jthread shutdown_thread([&] {
|
|
gpu.Shutdown();
|
|
shutdown_complete = true;
|
|
});
|
|
while (!gpu.IsStopping()) {
|
|
std::this_thread::yield();
|
|
}
|
|
Require("GpuCommandLane", "owned shutdown", !shutdown_complete.load(),
|
|
"GPU owner returned before an in-flight command completed");
|
|
release_command.release();
|
|
shutdown_thread.join();
|
|
Require(
|
|
"GpuCommandLane", "owned shutdown completion",
|
|
shutdown_complete.load() && submission_lane_entered.load(),
|
|
"GPU owner did not drain a command that entered the submission lane");
|
|
context.ShutdownGpu();
|
|
std::printf("[host] %-32s ok\n", "GpuCommandLane");
|
|
}
|
|
|
|
void CheckUnifiedImageViewCache() {
|
|
EnsureRuntimeContext();
|
|
constexpr const char *name = "UnifiedImageViewCache";
|
|
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
|
|
|
ImageInfo color_info{};
|
|
color_info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
color_info.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
color_info.type = Prospero::ImageType::kColor2D;
|
|
color_info.extent = {8, 8, 1};
|
|
color_info.resources = {2, 2};
|
|
color_info.pitch = 8;
|
|
color_info.bytes_per_block = 4;
|
|
color_info.samples = 1;
|
|
color_info.tile_mode = Prospero::TileMode::kLinear;
|
|
color_info.mip_layout[0] = {0, 512, 8, 8};
|
|
color_info.mip_layout[1] = {512, 128, 4, 4};
|
|
|
|
Libs::Graphics::Image color(m_runtime_context, scheduler, color_info);
|
|
const vk::ComponentMapping identity{};
|
|
const vk::ComponentMapping bgra{
|
|
vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eG,
|
|
vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eA};
|
|
|
|
ImageViewInfo sampled{};
|
|
sampled.format = color_info.pixel_format;
|
|
sampled.type = vk::ImageViewType::e2D;
|
|
sampled.aspect = vk::ImageAspectFlagBits::eColor;
|
|
sampled.base_level = 0;
|
|
sampled.level_count = 1;
|
|
sampled.base_layer = 0;
|
|
sampled.layer_count = 1;
|
|
sampled.mapping = identity;
|
|
sampled.usage = vk::ImageUsageFlagBits::eSampled;
|
|
|
|
const auto first = color.FindView(sampled);
|
|
const auto first_again = color.FindView(sampled);
|
|
auto swizzled_info = sampled;
|
|
swizzled_info.mapping = bgra;
|
|
const auto swizzled = color.FindView(swizzled_info);
|
|
auto mip_info = sampled;
|
|
mip_info.base_level = 1;
|
|
const auto mip = color.FindView(mip_info);
|
|
auto layer_info = sampled;
|
|
layer_info.base_layer = 1;
|
|
const auto layer = color.FindView(layer_info);
|
|
auto array_info = sampled;
|
|
array_info.type = vk::ImageViewType::e2DArray;
|
|
array_info.layer_count = 2;
|
|
const auto array = color.FindView(array_info);
|
|
auto reinterpreted_info = sampled;
|
|
reinterpreted_info.format = vk::Format::eR8G8B8A8Uint;
|
|
const auto reinterpreted = color.FindView(reinterpreted_info);
|
|
auto storage_info = sampled;
|
|
storage_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto storage = color.FindView(storage_info);
|
|
auto attachment_info = sampled;
|
|
attachment_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto attachment = color.FindView(attachment_info);
|
|
|
|
Require(name, "color views",
|
|
first != nullptr && first_again == first && swizzled != nullptr &&
|
|
swizzled != first && mip != nullptr && mip != first &&
|
|
layer != nullptr && layer != first && array != nullptr &&
|
|
array != first && reinterpreted != nullptr &&
|
|
reinterpreted != first && storage != nullptr &&
|
|
storage != first && attachment == first &&
|
|
color.views.size() == 7,
|
|
"dynamic view identity omitted mapping, format, mip, layer, type, "
|
|
"or storage usage");
|
|
|
|
auto video_out_info = color_info;
|
|
video_out_info.pixel_format = vk::Format::eA2R10G10B10UnormPack32;
|
|
video_out_info.guest_format = Prospero::BufferFormat::k10_10_10_2UNorm;
|
|
Libs::Graphics::Image video_out(m_runtime_context, scheduler,
|
|
video_out_info);
|
|
auto video_out_view_info = sampled;
|
|
video_out_view_info.format = video_out_info.pixel_format;
|
|
video_out_view_info.usage = vk::ImageUsageFlagBits::eTransferSrc;
|
|
const auto video_out_view = video_out.FindView(video_out_view_info);
|
|
auto storage_alias_info = video_out_view_info;
|
|
storage_alias_info.format = vk::Format::eA2B10G10R10UnormPack32;
|
|
storage_alias_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto storage_alias_view = video_out.FindView(storage_alias_info);
|
|
Require(
|
|
name, "video-out storage alias usage",
|
|
ImageViewOps::FormatsCompatible(video_out_info.pixel_format,
|
|
storage_alias_info.format) &&
|
|
static_cast<bool>(video_out.backing.usage &
|
|
vk::ImageUsageFlagBits::eStorage) &&
|
|
video_out_view != nullptr && storage_alias_view != nullptr,
|
|
"compatible video-out backing was created without storage usage");
|
|
|
|
ImageInfo depth_info{};
|
|
depth_info.pixel_format = vk::Format::eD32Sfloat;
|
|
depth_info.guest_format = Prospero::BufferFormat::k32Float;
|
|
depth_info.type = Prospero::ImageType::kColor2D;
|
|
depth_info.extent = {8, 8, 1};
|
|
depth_info.resources = {1, 2};
|
|
depth_info.pitch = 8;
|
|
depth_info.bytes_per_block = 4;
|
|
depth_info.samples = 1;
|
|
depth_info.tile_mode = Prospero::TileMode::kDepth;
|
|
depth_info.mip_layout[0] = {0, 512, 8, 8};
|
|
|
|
Libs::Graphics::Image depth(m_runtime_context, scheduler, depth_info);
|
|
ImageViewInfo depth_sampled{};
|
|
depth_sampled.format = depth_info.pixel_format;
|
|
depth_sampled.type = vk::ImageViewType::e2D;
|
|
depth_sampled.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
depth_sampled.base_level = 0;
|
|
depth_sampled.level_count = 1;
|
|
depth_sampled.base_layer = 0;
|
|
depth_sampled.layer_count = 1;
|
|
depth_sampled.mapping = {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eR,
|
|
vk::ComponentSwizzle::eR,
|
|
vk::ComponentSwizzle::eR};
|
|
depth_sampled.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto depth_first = depth.FindView(depth_sampled);
|
|
auto depth_alias_info = depth_sampled;
|
|
depth_alias_info.format = vk::Format::eR32Uint;
|
|
const auto depth_alias = depth.FindView(depth_alias_info);
|
|
const auto depth_again = depth.FindView(depth_sampled);
|
|
auto depth_array_info = depth_sampled;
|
|
depth_array_info.type = vk::ImageViewType::e2DArray;
|
|
depth_array_info.layer_count = 2;
|
|
const auto depth_array = depth.FindView(depth_array_info);
|
|
auto depth_attachment_info = depth_array_info;
|
|
depth_attachment_info.mapping = {};
|
|
depth_attachment_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto depth_attachment = depth.FindView(depth_attachment_info);
|
|
Require(name, "depth views",
|
|
depth_first != nullptr && depth_again == depth_first &&
|
|
depth_alias == depth_first && depth_array != nullptr &&
|
|
depth_array != depth_first && depth_attachment != nullptr &&
|
|
depth_attachment != depth_array &&
|
|
depth.views.size() == 3,
|
|
"unified depth view cache lost sampled/attachment identity");
|
|
|
|
auto depth_stencil_info = depth_info;
|
|
depth_stencil_info.pixel_format = vk::Format::eD24UnormS8Uint;
|
|
depth_stencil_info.guest_format = Prospero::BufferFormat::k16UNorm;
|
|
depth_stencil_info.bytes_per_block = 2;
|
|
Libs::Graphics::Image depth_stencil(m_runtime_context, scheduler,
|
|
depth_stencil_info);
|
|
auto depth_only_info = depth_sampled;
|
|
depth_only_info.format = depth_stencil_info.pixel_format;
|
|
const auto depth_only = depth_stencil.FindView(depth_only_info);
|
|
auto combined_info = depth_only_info;
|
|
combined_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
combined_info.mapping = {};
|
|
combined_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto combined = depth_stencil.FindView(combined_info);
|
|
Require(name, "depth/stencil aspects",
|
|
depth_only != nullptr && combined != nullptr &&
|
|
depth_only != combined &&
|
|
depth_stencil.views.size() == 2 &&
|
|
depth_stencil.views[0].info.aspect ==
|
|
vk::ImageAspectFlagBits::eDepth &&
|
|
depth_stencil.views[1].info.aspect ==
|
|
(vk::ImageAspectFlagBits::eDepth |
|
|
vk::ImageAspectFlagBits::eStencil),
|
|
"explicit depth-only and combined aspects shared one image view");
|
|
Require(name, "role-free backing",
|
|
color.backing.image_type == vk::ImageType::e2D &&
|
|
color.backing.layers == 2 &&
|
|
depth.backing.image_type == vk::ImageType::e2D &&
|
|
ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Uint) &&
|
|
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat,
|
|
vk::Format::eR32Sfloat),
|
|
"role-free image backing or host format compatibility diverged");
|
|
|
|
auto line_info = color_info;
|
|
line_info.type = Prospero::ImageType::kColor1D;
|
|
line_info.extent = {8, 1, 1};
|
|
line_info.resources = {2, 2};
|
|
line_info.mip_layout[0] = {0, 64, 8, 1};
|
|
line_info.mip_layout[1] = {64, 16, 4, 1};
|
|
Libs::Graphics::Image line(m_runtime_context, scheduler, line_info);
|
|
auto line_view_info = sampled;
|
|
line_view_info.type = vk::ImageViewType::e1D;
|
|
const auto line_view = line.FindView(line_view_info);
|
|
auto line_array_info = line_view_info;
|
|
line_array_info.type = vk::ImageViewType::e1DArray;
|
|
line_array_info.layer_count = 2;
|
|
const auto line_array = line.FindView(line_array_info);
|
|
Require(name, "1D views",
|
|
line_view != nullptr && line_array != nullptr &&
|
|
line_array != line_view &&
|
|
line.backing.image_type == vk::ImageType::e1D &&
|
|
line.backing.layers == 2,
|
|
"1D and 1D-array views did not use a first-class 1D backing");
|
|
|
|
auto volume_info = color_info;
|
|
volume_info.type = Prospero::ImageType::kColor3D;
|
|
volume_info.extent = {8, 8, 4};
|
|
volume_info.resources = {2, 1};
|
|
Libs::Graphics::Image volume(m_runtime_context, scheduler, volume_info);
|
|
auto slice_info = sampled;
|
|
slice_info.type = vk::ImageViewType::e2D;
|
|
slice_info.base_level = 1;
|
|
slice_info.base_layer = 1;
|
|
const auto slice = volume.FindView(slice_info);
|
|
auto slice_array_info = slice_info;
|
|
slice_array_info.type = vk::ImageViewType::e2DArray;
|
|
slice_array_info.base_layer = 0;
|
|
slice_array_info.layer_count = 2;
|
|
const auto slice_array = volume.FindView(slice_array_info);
|
|
Require(name, "3D slice views",
|
|
slice != nullptr && slice_array != nullptr &&
|
|
slice_array != slice &&
|
|
static_cast<bool>(volume.backing.flags &
|
|
vk::ImageCreateFlagBits::e2DArrayCompatible),
|
|
"2D slice views of a compatible 3D backing were rejected");
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckBufferCacheDirtyGarbageCollection() {
|
|
constexpr const char *name = "BufferCacheDirtyGarbageCollection";
|
|
constexpr uintptr_t base = 0x0000000200700000ull;
|
|
constexpr uint64_t allocation_size = 0x2400000;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
constexpr uint64_t first_offset = 0x100;
|
|
constexpr uint64_t second_offset = 0x200;
|
|
constexpr uint64_t clean_offset = 0x300;
|
|
constexpr uint64_t unmap_offset = 0x4100;
|
|
constexpr uint64_t partial_unmap_offset = 0x8100;
|
|
constexpr uint64_t partial_unmap_survivor_offset = 0xc100;
|
|
constexpr uint64_t ring_fault_first_offset = 0x2100;
|
|
constexpr uint64_t ring_fault_second_offset = 0x2110;
|
|
constexpr uint32_t first_value = 0x10203040u;
|
|
constexpr uint32_t second_value = 0x50607080u;
|
|
constexpr uint32_t first_stale = 0x0badf00du;
|
|
constexpr uint32_t second_stale = 0xdeadbeefu;
|
|
constexpr uint32_t clean_value = 0xaabbccddu;
|
|
constexpr uint32_t unmap_value = 0x91a2b3c4u;
|
|
constexpr uint32_t remap_value = 0xd5e6f708u;
|
|
constexpr uint32_t partial_unmap_value = 0x62738495u;
|
|
constexpr uint32_t partial_unmap_survivor_value = 0xa6b7c8d9u;
|
|
constexpr uint32_t ring_fault_first_value = 0x0a1b2c3du;
|
|
constexpr uint32_t ring_fault_second_value = 0x4e5f6071u;
|
|
|
|
EnsureRuntimeContext();
|
|
auto &context = Renderer();
|
|
CommandScheduler scheduler(context, m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
context.InitializeGpu(nullptr);
|
|
auto &gpu = context.GetGpu();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"dirty-GC direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"dirty-GC fixed direct-memory mapping failed");
|
|
auto *memory = static_cast<uint8_t *>(mapped);
|
|
std::memcpy(memory + first_offset, &first_stale, sizeof(first_stale));
|
|
std::memcpy(memory + second_offset, &second_stale, sizeof(second_stale));
|
|
std::memcpy(memory + clean_offset, &clean_value, sizeof(clean_value));
|
|
|
|
{
|
|
GpuResourceManager resources(m_runtime_context, scheduler);
|
|
resources.SetGpu(&gpu);
|
|
auto &cache = resources.GetBufferCache();
|
|
resources.MapMemory(base, allocation_size);
|
|
|
|
const auto MarkGpuWrite = [&](uint64_t address, uint64_t size) {
|
|
auto allocation = cache.ObtainBuffer(address, size, true, false);
|
|
Require(name, "dirty allocation", allocation.first != nullptr,
|
|
"dirty-GC buffer allocation failed");
|
|
};
|
|
const auto ReadNativeValue =
|
|
[&](const Libs::Graphics::Buffer &buffer, uint64_t offset) {
|
|
auto readback =
|
|
CreateHostBuffer(name, sizeof(uint32_t),
|
|
vk::BufferUsageFlagBits::eTransferDst, {0});
|
|
const vk::BufferCopy copy{offset, 0, sizeof(uint32_t)};
|
|
scheduler.Current().Handle().copyBuffer(buffer.Handle(), readback.buffer,
|
|
1, ©);
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = readback.buffer;
|
|
barrier.size = readback.size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0,
|
|
nullptr);
|
|
scheduler.Finish();
|
|
const auto value = ReadBuffer(name, readback, 1)[0];
|
|
DestroyBuffer(&readback);
|
|
return value;
|
|
};
|
|
|
|
constexpr uint64_t large_copy_source_offset = 0x40000;
|
|
constexpr uint64_t large_copy_destination_offset = 0x80000;
|
|
constexpr uint64_t large_copy_size = 96 * 1024;
|
|
std::vector<uint8_t> large_copy_source(large_copy_size);
|
|
for (uint64_t index = 0; index < large_copy_size; ++index) {
|
|
large_copy_source[index] =
|
|
static_cast<uint8_t>((index * 37 + index / 251) & 0xffu);
|
|
}
|
|
std::memcpy(memory + large_copy_source_offset, large_copy_source.data(),
|
|
large_copy_size);
|
|
std::memset(memory + large_copy_destination_offset, 0, large_copy_size);
|
|
cache.CopyBuffer(base + large_copy_destination_offset,
|
|
base + large_copy_source_offset, large_copy_size, false,
|
|
false);
|
|
Require(name, "large host copy",
|
|
std::memcmp(memory + large_copy_destination_offset,
|
|
large_copy_source.data(), large_copy_size) == 0,
|
|
"chunked host copy lost bytes across its scratch boundary");
|
|
|
|
constexpr uint64_t index_offset = 0x180000;
|
|
constexpr uint64_t index_page = BufferCache::CACHING_PAGESIZE;
|
|
constexpr uint64_t index_span = 3 * index_page;
|
|
const uint64_t index_begin = base + index_offset;
|
|
Require(name, "registered-range empty lookup",
|
|
!cache.IsRegionRegistered(index_begin, index_span) &&
|
|
!BufferCacheTestAccess::PageOwner(cache, index_begin),
|
|
"empty Buffer cache reported a registered range");
|
|
const auto index_left =
|
|
cache.FindBuffer(index_begin + 0x100, sizeof(uint32_t));
|
|
const auto index_right = cache.FindBuffer(
|
|
index_begin + 2 * index_page + 0x100, sizeof(uint32_t));
|
|
Require(name, "registered-range owners",
|
|
index_left && index_right &&
|
|
cache.FindBuffer(index_begin + 0x200, sizeof(uint32_t)) ==
|
|
index_left &&
|
|
BufferCacheTestAccess::PageOwner(cache, index_begin) ==
|
|
index_left &&
|
|
!BufferCacheTestAccess::PageOwner(cache,
|
|
index_begin + index_page) &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, index_begin + 2 * index_page) == index_right,
|
|
"failed to create disjoint Buffer index owners");
|
|
Require(
|
|
name, "registered-range boundaries",
|
|
cache.IsRegionRegistered(index_begin, 1) &&
|
|
cache.IsRegionRegistered(index_begin + index_page - 1, 1) &&
|
|
!cache.IsRegionRegistered(index_begin - 1, 1) &&
|
|
!cache.IsRegionRegistered(index_begin + index_page, index_page) &&
|
|
cache.IsRegionRegistered(index_begin + index_page,
|
|
index_page + 1) &&
|
|
!cache.IsRegionRegistered(index_begin + index_span, 1) &&
|
|
cache.IsRegionRegistered(index_begin - 1, index_span + 2),
|
|
"indexed lookup mishandled a half-open boundary, gap, or broad "
|
|
"overlap");
|
|
const auto index_bridge =
|
|
cache.FindBuffer(index_begin + index_page - 1, index_page + 2);
|
|
Require(name, "registered-range merge",
|
|
index_bridge && cache.IsRegionRegistered(index_begin + index_page,
|
|
index_page) &&
|
|
BufferCacheTestAccess::PageOwner(cache, index_begin) ==
|
|
index_bridge &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, index_begin + index_page) == index_bridge &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, index_begin + 2 * index_page) == index_bridge,
|
|
"bridging acquisition did not publish its merged Buffer range");
|
|
scheduler.Finish();
|
|
constexpr uint64_t recycled_offset = index_offset + 4 * index_page;
|
|
const auto recycled = cache.FindBuffer(base + recycled_offset + 0x100,
|
|
sizeof(uint32_t));
|
|
Require(name, "slot generation reuse",
|
|
(recycled.index == index_left.index && recycled != index_left) ||
|
|
(recycled.index == index_right.index &&
|
|
recycled != index_right),
|
|
"retired Buffer slot was not reused with a new generation");
|
|
const auto [resolved_left, resolved_left_offset] = cache.ObtainBuffer(
|
|
index_begin + 0x100, sizeof(uint32_t), true, false, index_left);
|
|
const auto [resolved_right, resolved_right_offset] =
|
|
cache.ObtainBuffer(index_begin + 2 * index_page + 0x100,
|
|
sizeof(uint32_t), true, false, index_right);
|
|
Require(name, "two-pass stale id resolution",
|
|
resolved_left == &cache.GetBuffer(index_bridge) &&
|
|
resolved_right == resolved_left &&
|
|
resolved_left_offset != resolved_right_offset,
|
|
"saved descriptor IDs did not resolve through the merged owner");
|
|
|
|
constexpr uint64_t stream_page = BufferCache::CACHING_PAGESIZE;
|
|
constexpr uint64_t stream_leap = stream_page * 128;
|
|
for (const bool forward : {true, false}) {
|
|
const char *stream_name =
|
|
forward ? "ForwardBufferStream" : "BackwardBufferStream";
|
|
const uint64_t stream_begin = base + (forward ? 0x1800000 : 0x2000000);
|
|
const uint64_t caught_address = forward
|
|
? stream_begin + stream_leap / 2
|
|
: stream_begin - stream_leap / 2;
|
|
const uint64_t untouched_address = forward
|
|
? stream_begin - stream_leap / 2
|
|
: stream_begin + stream_leap / 2;
|
|
const auto caught = cache.FindBuffer(caught_address, stream_page);
|
|
const auto untouched = cache.FindBuffer(untouched_address, stream_page);
|
|
auto grown = cache.FindBuffer(stream_begin, stream_page * 2);
|
|
constexpr uint32_t stream_value = 0x6a3b92d1u;
|
|
for (const auto address : {stream_begin, caught_address}) {
|
|
MarkGpuWrite(address, sizeof(stream_value));
|
|
cache.FillBuffer(address, sizeof(stream_value), stream_value, false);
|
|
}
|
|
|
|
uint64_t requested_begin = stream_begin;
|
|
uint64_t requested_end = stream_begin + stream_page * 2;
|
|
for (uint32_t step = 1; step <= 18; ++step) {
|
|
const auto address = (forward ? requested_end : requested_begin) - 4;
|
|
grown = cache.FindBuffer(address, 8);
|
|
if (forward) {
|
|
requested_end += stream_page;
|
|
} else {
|
|
requested_begin -= stream_page;
|
|
}
|
|
if (step < 18) {
|
|
const auto &owner = cache.GetBuffer(grown);
|
|
Require(stream_name, "growth before threshold",
|
|
owner.CpuAddress() == requested_begin &&
|
|
owner.Size() == requested_end - requested_begin &&
|
|
owner.StreamScore() == static_cast<int>(step),
|
|
"sequential requests grew beyond their consumed pages too "
|
|
"early");
|
|
}
|
|
}
|
|
const auto &grown_buffer = cache.GetBuffer(grown);
|
|
Require(stream_name, "growth reserve",
|
|
grown_buffer.CpuAddress() ==
|
|
(forward ? requested_begin
|
|
: requested_begin - stream_leap) &&
|
|
grown_buffer.Size() ==
|
|
requested_end - requested_begin + stream_leap &&
|
|
grown_buffer.StreamScore() == 0 &&
|
|
cache.GetBuffer(caught).is_deleted &&
|
|
!cache.GetBuffer(untouched).is_deleted &&
|
|
BufferCacheTestAccess::PageOwner(cache, caught_address) ==
|
|
grown &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, untouched_address) == untouched,
|
|
"streaming did not reserve the next pages or retain correct "
|
|
"neighboring owners");
|
|
for (uint32_t step = 0; step < 128; ++step) {
|
|
const auto address = forward
|
|
? requested_end + step * stream_page - 4
|
|
: requested_begin - step * stream_page - 4;
|
|
Require(stream_name, "reuse reserved pages",
|
|
cache.FindBuffer(address, 8) == grown,
|
|
"continued sequential requests replaced the buffer within "
|
|
"its growth reserve");
|
|
}
|
|
for (const auto address : {stream_begin, caught_address}) {
|
|
Require(
|
|
stream_name, "preserve GPU contents",
|
|
ReadNativeValue(grown_buffer, grown_buffer.Offset(address)) ==
|
|
stream_value,
|
|
"stream growth lost GPU-written contents from a merged owner");
|
|
}
|
|
}
|
|
|
|
resources.UnmapMemory(index_begin, index_span);
|
|
resources.UnmapMemory(base + recycled_offset, index_page);
|
|
Require(name, "invalidation retains owners",
|
|
!resources.IsMapped(index_begin, index_span) &&
|
|
!resources.IsMapped(base + recycled_offset, index_page) &&
|
|
cache.IsRegionRegistered(index_begin, index_span) &&
|
|
BufferCacheTestAccess::PageOwner(cache, index_begin) ==
|
|
index_bridge &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, index_begin + index_page) == index_bridge &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, index_begin + 2 * index_page) == index_bridge &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, base + recycled_offset) == recycled &&
|
|
cache.IsRegionCpuModified(index_begin, index_span) &&
|
|
cache.IsRegionCpuModified(base + recycled_offset,
|
|
index_page),
|
|
"CPU invalidation retired a reusable Buffer owner");
|
|
resources.MapMemory(index_begin, index_span);
|
|
resources.MapMemory(base + recycled_offset, index_page);
|
|
|
|
MarkGpuWrite(base + first_offset, sizeof(first_value));
|
|
MarkGpuWrite(base + second_offset, sizeof(second_value));
|
|
cache.FillBuffer(base + first_offset, sizeof(first_value), first_value,
|
|
false);
|
|
cache.FillBuffer(base + second_offset, sizeof(second_value), second_value,
|
|
false);
|
|
auto &download = BufferCacheTestAccess::DownloadBuffer(cache);
|
|
auto *fixed_download = &download;
|
|
const auto fixed_download_handle = download.Handle();
|
|
auto [ring_tail, ring_tail_offset] =
|
|
download.Map(download.Size() - sizeof(uint32_t), 4);
|
|
Require(name, "fault-ring tail reservation",
|
|
ring_tail != nullptr && ring_tail_offset == 0,
|
|
"failed to position the reusable download ring near wrap");
|
|
download.Commit();
|
|
MarkGpuWrite(base + ring_fault_first_offset,
|
|
sizeof(ring_fault_first_value));
|
|
MarkGpuWrite(base + ring_fault_second_offset,
|
|
sizeof(ring_fault_second_value));
|
|
cache.FillBuffer(base + ring_fault_first_offset,
|
|
sizeof(ring_fault_first_value), ring_fault_first_value,
|
|
false);
|
|
cache.FillBuffer(base + ring_fault_second_offset,
|
|
sizeof(ring_fault_second_value), ring_fault_second_value,
|
|
false);
|
|
Require(name, "fault-ring wrapped batch",
|
|
resources.HandleFault(PageFaultAccess::Read,
|
|
base + ring_fault_first_offset),
|
|
"fault readback could not wrap a live download-ring tick");
|
|
// The widened window collects the two earlier writes and both
|
|
// fault-adjacent writes.
|
|
uint64_t expected_packing_offset = 4 * 64;
|
|
uint64_t expected_packing_alignment = 1;
|
|
if (!download.IsCoherent()) {
|
|
Require(name, "fault-ring atom policy",
|
|
StreamBufferTestAccess::NormalizeReservation(
|
|
false,
|
|
m_runtime_context.physical_device_properties.limits
|
|
.nonCoherentAtomSize,
|
|
expected_packing_offset, expected_packing_alignment),
|
|
"failed to calculate non-coherent fault-ring stride");
|
|
}
|
|
auto [packing_probe, packing_offset] = download.Map(1, 1, false);
|
|
Require(name, "adjacent download reservation stride",
|
|
packing_probe != nullptr &&
|
|
packing_offset == expected_packing_offset,
|
|
"adjacent fault downloads did not reserve an atom-safe stride");
|
|
download.Commit();
|
|
uint32_t widened_first_backing = 0;
|
|
uint32_t widened_second_backing = 0;
|
|
uint32_t ring_fault_first_backing = 0;
|
|
uint32_t ring_fault_second_backing = 0;
|
|
std::memcpy(&widened_first_backing, memory + first_offset,
|
|
sizeof(widened_first_backing));
|
|
std::memcpy(&widened_second_backing, memory + second_offset,
|
|
sizeof(widened_second_backing));
|
|
std::memcpy(&ring_fault_first_backing, memory + ring_fault_first_offset,
|
|
sizeof(ring_fault_first_backing));
|
|
std::memcpy(&ring_fault_second_backing, memory + ring_fault_second_offset,
|
|
sizeof(ring_fault_second_backing));
|
|
Require(name, "fault-ring wrapped contents",
|
|
widened_first_backing == first_value &&
|
|
widened_second_backing == second_value &&
|
|
ring_fault_first_backing == ring_fault_first_value &&
|
|
ring_fault_second_backing == ring_fault_second_value &&
|
|
&BufferCacheTestAccess::DownloadBuffer(cache) ==
|
|
fixed_download &&
|
|
download.Handle() == fixed_download_handle &&
|
|
download.Size() == (32ull << 20),
|
|
"wrapped fault batch published incorrect disjoint ranges");
|
|
|
|
constexpr uint64_t window_size = 512 * 1024;
|
|
constexpr uint64_t window_owner_offset = 0x240000;
|
|
constexpr uint64_t window_owner_size = 0xc0000;
|
|
constexpr uint64_t window_fault_offset = window_owner_offset + 0x100;
|
|
constexpr uint64_t window_inside_offset =
|
|
window_owner_offset + window_size - sizeof(uint32_t);
|
|
constexpr uint64_t window_outside_offset =
|
|
window_owner_offset + window_size;
|
|
constexpr uint32_t window_stale = 0x13579bdfu;
|
|
constexpr uint32_t window_value = 0x2468ace0u;
|
|
constexpr std::array window_offsets{
|
|
window_fault_offset, window_inside_offset, window_outside_offset};
|
|
static_assert((base + window_owner_offset) % window_size ==
|
|
window_size / 2);
|
|
for (const auto offset : window_offsets) {
|
|
Libs::LibKernel::Memory::WriteBacking(base + offset, &window_stale,
|
|
sizeof(window_stale));
|
|
}
|
|
(void)cache.FindBuffer(base + window_owner_offset, window_owner_size);
|
|
for (const auto offset : window_offsets) {
|
|
MarkGpuWrite(base + offset, sizeof(window_value));
|
|
cache.FillBuffer(base + offset, sizeof(window_value), window_value,
|
|
false);
|
|
}
|
|
cache.ReadMemory(base + window_fault_offset, sizeof(window_value));
|
|
uint32_t window_inside_backing = 0;
|
|
uint32_t window_outside_backing = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + window_inside_offset,
|
|
&window_inside_backing,
|
|
sizeof(window_inside_backing));
|
|
Libs::LibKernel::Memory::TryReadBacking(base + window_outside_offset,
|
|
&window_outside_backing,
|
|
sizeof(window_outside_backing));
|
|
Require(name, "widened-window boundary",
|
|
window_inside_backing == window_value &&
|
|
window_outside_backing == window_stale &&
|
|
!cache.HasGpuDirtyBytes(base + window_inside_offset,
|
|
sizeof(window_value)) &&
|
|
cache.HasGpuDirtyBytes(base + window_outside_offset,
|
|
sizeof(window_value)) &&
|
|
!cache.IsRegionGpuModified(base + window_inside_offset,
|
|
sizeof(window_value)) &&
|
|
cache.IsRegionGpuModified(base + window_outside_offset,
|
|
sizeof(window_value)),
|
|
"readback did not honor the clamped half-open 512 KiB window");
|
|
cache.ReadMemory(base + window_outside_offset, sizeof(window_value));
|
|
|
|
Libs::LibKernel::Memory::WriteBacking(base + first_offset, &first_stale,
|
|
sizeof(first_stale));
|
|
Libs::LibKernel::Memory::WriteBacking(base + second_offset, &second_stale,
|
|
sizeof(second_stale));
|
|
MarkGpuWrite(base + first_offset, sizeof(first_value));
|
|
MarkGpuWrite(base + second_offset, sizeof(second_value));
|
|
cache.FillBuffer(base + first_offset, sizeof(first_value), first_value,
|
|
false);
|
|
cache.FillBuffer(base + second_offset, sizeof(second_value), second_value,
|
|
false);
|
|
|
|
for (uint32_t tick = 0; tick < 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "age before pressure",
|
|
cache.IsRegionRegistered(base, allocation_size),
|
|
"buffer was reclaimed without memory pressure");
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
|
cache, 0, std::numeric_limits<uint64_t>::max());
|
|
const auto gc_submission_tick = scheduler.CurrentTick();
|
|
cache.RunGarbageCollector();
|
|
uint32_t first_before_completion = 0;
|
|
uint32_t second_before_completion = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + first_offset,
|
|
&first_before_completion,
|
|
sizeof(first_before_completion));
|
|
Libs::LibKernel::Memory::TryReadBacking(base + second_offset,
|
|
&second_before_completion,
|
|
sizeof(second_before_completion));
|
|
Require(name, "normal dirty retention",
|
|
first_before_completion == first_stale &&
|
|
second_before_completion == second_stale &&
|
|
cache.IsRegionRegistered(base, allocation_size) &&
|
|
cache.IsRegionGpuModified(base + first_offset,
|
|
sizeof(first_value)) &&
|
|
cache.IsRegionGpuModified(base + second_offset,
|
|
sizeof(second_value)) &&
|
|
cache.HasGpuDirtyBytes(base + first_offset,
|
|
sizeof(first_value)) &&
|
|
cache.HasGpuDirtyBytes(base + second_offset,
|
|
sizeof(second_value)) &&
|
|
scheduler.CurrentTick() == gc_submission_tick,
|
|
"normal GC retired or synchronized a dirty Buffer");
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(cache, 0, 0);
|
|
const auto older_publication_tick = scheduler.CurrentTick();
|
|
std::binary_semaphore older_publication_entered{0};
|
|
std::binary_semaphore release_older_publication{0};
|
|
std::atomic<bool> gc_returned{false};
|
|
scheduler.DeferPriorityOperation([&] {
|
|
older_publication_entered.release();
|
|
release_older_publication.acquire();
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
base + first_offset, &first_stale, sizeof(first_stale));
|
|
});
|
|
std::jthread release_publication([&] {
|
|
older_publication_entered.acquire();
|
|
const auto deadline = std::chrono::steady_clock::now() +
|
|
std::chrono::milliseconds(100);
|
|
while (!gc_returned.load() &&
|
|
std::chrono::steady_clock::now() < deadline) {
|
|
std::this_thread::yield();
|
|
}
|
|
release_older_publication.release();
|
|
});
|
|
cache.RunGarbageCollector();
|
|
gc_returned = true;
|
|
release_publication.join();
|
|
scheduler.WaitPriorityOperations(older_publication_tick);
|
|
|
|
uint32_t first_backing = 0;
|
|
uint32_t second_backing = 0;
|
|
uint32_t clean_backing = 0;
|
|
std::memcpy(&first_backing, memory + first_offset, sizeof(first_backing));
|
|
std::memcpy(&second_backing, memory + second_offset,
|
|
sizeof(second_backing));
|
|
std::memcpy(&clean_backing, memory + clean_offset, sizeof(clean_backing));
|
|
Require(name, "downloaded contents",
|
|
first_backing == first_value && second_backing == second_value &&
|
|
clean_backing == clean_value &&
|
|
!cache.IsRegionRegistered(base, allocation_size) &&
|
|
!cache.IsRegionGpuModified(base + first_offset,
|
|
sizeof(first_value)) &&
|
|
!cache.IsRegionGpuModified(base + second_offset,
|
|
sizeof(second_value)) &&
|
|
!cache.HasGpuDirtyBytes(base + first_offset,
|
|
sizeof(first_value)) &&
|
|
!cache.HasGpuDirtyBytes(base + second_offset,
|
|
sizeof(second_value)) &&
|
|
scheduler.CurrentTick() > gc_submission_tick,
|
|
"critical GC did not publish exact ranges before clearing "
|
|
"Buffer ownership");
|
|
|
|
constexpr uint64_t starvation_offset = 0x100000;
|
|
constexpr uint64_t starvation_stride = 2 * BufferCache::CACHING_PAGESIZE;
|
|
constexpr uint32_t starvation_count = 33;
|
|
constexpr uint32_t starvation_value = 0x31415926u;
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
|
cache, std::numeric_limits<uint64_t>::max(),
|
|
std::numeric_limits<uint64_t>::max());
|
|
for (uint32_t index = 0; index < starvation_count; index++) {
|
|
const auto address = base + starvation_offset + index * starvation_stride;
|
|
(void)cache.ObtainBuffer(address, sizeof(starvation_value), true);
|
|
cache.FillBuffer(address, sizeof(starvation_value), starvation_value,
|
|
false);
|
|
}
|
|
for (uint32_t tick = 0; tick < 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
constexpr uint64_t starvation_clean_offset =
|
|
starvation_offset + starvation_count * starvation_stride;
|
|
const auto starvation_clean = cache.FindBuffer(
|
|
base + starvation_clean_offset, sizeof(starvation_value));
|
|
Require(name, "normal-GC clean candidate",
|
|
static_cast<bool>(starvation_clean),
|
|
"failed to create the clean starvation candidate");
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
|
cache, 0, std::numeric_limits<uint64_t>::max());
|
|
cache.RunGarbageCollector();
|
|
Require(
|
|
name, "normal-GC dirty bypass",
|
|
cache.IsRegionRegistered(base + starvation_offset,
|
|
sizeof(starvation_value)) &&
|
|
cache.IsRegionRegistered(
|
|
base + starvation_offset +
|
|
(starvation_count - 1) * starvation_stride,
|
|
sizeof(starvation_value)) &&
|
|
!cache.IsRegionRegistered(base + starvation_clean_offset,
|
|
sizeof(starvation_value)),
|
|
"old dirty owners hid an eligible clean Buffer from normal GC");
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(cache, 0, 0);
|
|
const auto starvation_retired =
|
|
BufferCacheTestAccess::PageOwner(cache, base + starvation_offset);
|
|
cache.RunGarbageCollector();
|
|
Require(name, "critical-GC starvation cleanup",
|
|
!cache.IsRegionRegistered(base + starvation_offset,
|
|
sizeof(starvation_value)) &&
|
|
!cache.IsRegionRegistered(
|
|
base + starvation_offset +
|
|
(starvation_count - 1) * starvation_stride,
|
|
sizeof(starvation_value)) &&
|
|
!BufferCacheTestAccess::IsBufferAllocated(
|
|
cache, starvation_retired),
|
|
"critical GC did not immediately free the skipped dirty owners");
|
|
|
|
constexpr uint64_t lookup_only_offset = 0x218000;
|
|
constexpr uint64_t obtained_offset = 0x220000;
|
|
constexpr uint64_t residency_size =
|
|
2 * BufferCache::CACHING_PAGESIZE;
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
|
cache, std::numeric_limits<uint64_t>::max(),
|
|
std::numeric_limits<uint64_t>::max());
|
|
const auto lookup_only = cache.FindBuffer(
|
|
base + lookup_only_offset, residency_size);
|
|
const auto obtained =
|
|
cache.FindBuffer(base + obtained_offset, residency_size);
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "lookup-only owner identity",
|
|
cache.FindBuffer(base + lookup_only_offset, residency_size) ==
|
|
lookup_only,
|
|
"lookup-only discovery replaced its valid Buffer owner");
|
|
const auto [obtained_buffer, obtained_buffer_offset] = cache.ObtainBuffer(
|
|
base + obtained_offset, residency_size, false, false, obtained);
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
|
cache, 0, std::numeric_limits<uint64_t>::max());
|
|
cache.RunGarbageCollector();
|
|
Require(name, "lookup versus acquisition residency",
|
|
!cache.IsRegionRegistered(base + lookup_only_offset,
|
|
residency_size) &&
|
|
!BufferCacheTestAccess::PageOwner(
|
|
cache, base + lookup_only_offset) &&
|
|
obtained_buffer == &cache.GetBuffer(obtained) &&
|
|
obtained_buffer_offset == 0 &&
|
|
cache.IsRegionRegistered(base + obtained_offset,
|
|
residency_size) &&
|
|
BufferCacheTestAccess::PageOwner(
|
|
cache, base + obtained_offset) == obtained,
|
|
"lookup refreshed residency or actual acquisition did not");
|
|
BufferCacheTestAccess::SetGarbageCollectionThresholds(cache, 0, 0);
|
|
|
|
auto unmap_allocation = cache.ObtainBuffer(
|
|
base + unmap_offset, sizeof(unmap_value), true, false);
|
|
Require(name, "direct-invalidation allocation",
|
|
unmap_allocation.first != nullptr,
|
|
"direct-invalidation buffer allocation failed");
|
|
cache.FillBuffer(base + unmap_offset, sizeof(unmap_value), unmap_value,
|
|
false);
|
|
resources.UnmapMemory(base + 0x4000, 0x4000);
|
|
uint32_t unmap_backing = 0;
|
|
std::memcpy(&unmap_backing, memory + unmap_offset, sizeof(unmap_backing));
|
|
Require(name, "direct-invalidation contents",
|
|
unmap_backing == unmap_value &&
|
|
!resources.IsMapped(base + 0x4000, 0x4000) &&
|
|
cache.IsRegionRegistered(base + 0x4000, 0x4000) &&
|
|
cache.IsRegionCpuModified(base + 0x4000, 0x4000) &&
|
|
!cache.IsRegionGpuModified(base + 0x4000, 0x4000) &&
|
|
!cache.HasGpuDirtyBytes(base + 0x4000, 0x4000),
|
|
"dirty invalidation did not publish bytes and retain its owner");
|
|
std::memcpy(memory + unmap_offset, &remap_value, sizeof(remap_value));
|
|
resources.MapMemory(base + 0x4000, 0x4000);
|
|
const auto remapped =
|
|
cache.ObtainBuffer(base + unmap_offset, sizeof(remap_value), true);
|
|
Require(name, "same-address remap allocation",
|
|
remapped.first == unmap_allocation.first,
|
|
"same-address remap did not synchronize the retained Buffer");
|
|
Require(name, "same-address remap contents",
|
|
ReadNativeValue(*remapped.first, remapped.second) == remap_value,
|
|
"same-address remap reused stale native Buffer bytes");
|
|
|
|
auto partial_unmap_allocation =
|
|
cache.ObtainBuffer(base + 0x8000, 0x8000, true, false);
|
|
Require(name, "partial-invalidation allocation",
|
|
partial_unmap_allocation.first != nullptr,
|
|
"cross-page cached buffer allocation failed");
|
|
cache.FillBuffer(base + partial_unmap_offset, sizeof(partial_unmap_value),
|
|
partial_unmap_value, false);
|
|
cache.FillBuffer(base + partial_unmap_survivor_offset,
|
|
sizeof(partial_unmap_survivor_value),
|
|
partial_unmap_survivor_value, false);
|
|
resources.UnmapMemory(base + 0x8000, 0x4000);
|
|
uint32_t partial_unmap_survivor_backing = 0;
|
|
std::memcpy(&partial_unmap_survivor_backing,
|
|
memory + partial_unmap_survivor_offset,
|
|
sizeof(partial_unmap_survivor_backing));
|
|
Require(
|
|
name, "partial-invalidation ownership",
|
|
!resources.IsMapped(base + 0x8000, 0x4000) &&
|
|
cache.IsRegionRegistered(base + 0x8000, 0x8000) &&
|
|
cache.IsRegionCpuModified(base + 0x8000, 0x4000) &&
|
|
!cache.IsRegionCpuModified(
|
|
base + partial_unmap_survivor_offset,
|
|
sizeof(partial_unmap_survivor_value)) &&
|
|
!cache.HasGpuDirtyBytes(base + partial_unmap_offset,
|
|
sizeof(partial_unmap_value)) &&
|
|
!cache.HasGpuDirtyBytes(base + partial_unmap_survivor_offset,
|
|
sizeof(partial_unmap_survivor_value)) &&
|
|
partial_unmap_survivor_backing == partial_unmap_survivor_value,
|
|
"widened partial invalidation mishandled ownership or backing");
|
|
resources.MapMemory(base + 0x8000, 0x4000);
|
|
auto survivor =
|
|
cache.ObtainBuffer(base + partial_unmap_survivor_offset,
|
|
sizeof(partial_unmap_survivor_value), false);
|
|
Require(name, "partial-invalidation survivor", survivor.first != nullptr,
|
|
"cached-buffer remainder was not retained");
|
|
Require(name, "partial-invalidation survivor contents",
|
|
ReadNativeValue(*survivor.first, survivor.second) ==
|
|
partial_unmap_survivor_value,
|
|
"partial invalidation lost disjoint native bytes");
|
|
|
|
constexpr uint64_t large_offset = 0x10000;
|
|
constexpr uint64_t large_size = 33ull * 1024 * 1024;
|
|
constexpr uint32_t large_value = 0x5aa55aa5u;
|
|
constexpr uint32_t large_stale = 0x12345678u;
|
|
std::memcpy(memory + large_offset, &large_stale, sizeof(large_stale));
|
|
std::memcpy(memory + large_offset + large_size - sizeof(large_stale),
|
|
&large_stale, sizeof(large_stale));
|
|
auto large_allocation =
|
|
cache.ObtainBuffer(base + large_offset, large_size, true, false);
|
|
Require(name, "near-capacity dirty allocation",
|
|
large_allocation.first != nullptr,
|
|
"failed to allocate the near-capacity dirty native buffer");
|
|
cache.FillBuffer(base + large_offset, large_size, large_value, false);
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "near-capacity synchronized retirement",
|
|
!cache.IsRegionRegistered(base + large_offset, large_size),
|
|
"near-capacity dirty Buffer survived pressured collection");
|
|
uint32_t large_before_completion = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + large_offset,
|
|
&large_before_completion,
|
|
sizeof(large_before_completion));
|
|
Require(name, "near-capacity backing publication",
|
|
large_before_completion == large_value,
|
|
"near-capacity Buffer GC retired ownership before publication");
|
|
const auto large_image_source =
|
|
cache.ObtainBufferForImage(base + large_offset, sizeof(large_value));
|
|
Require(name, "fixed download after Buffer retirement",
|
|
large_image_source.first != nullptr &&
|
|
&BufferCacheTestAccess::DownloadBuffer(cache) ==
|
|
fixed_download &&
|
|
fixed_download->Handle() == fixed_download_handle &&
|
|
fixed_download->Size() == (32ull << 20),
|
|
"image acquisition replaced the shared Buffer download stream");
|
|
uint32_t large_first = 0;
|
|
uint32_t large_last = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + large_offset, &large_first,
|
|
sizeof(large_first));
|
|
Libs::LibKernel::Memory::TryReadBacking(base + large_offset + large_size -
|
|
sizeof(large_last),
|
|
&large_last, sizeof(large_last));
|
|
Require(name, "near-capacity Buffer publication contents",
|
|
large_first == large_value && large_last == large_value,
|
|
"near-capacity Buffer GC did not publish its complete transfer");
|
|
|
|
constexpr uint64_t grouped_first_offset = 0x10000;
|
|
constexpr uint64_t grouped_second_offset = 0x1200000;
|
|
constexpr uint64_t grouped_owner_size = 17ull * 1024 * 1024;
|
|
constexpr uint32_t grouped_first_value = 0x1122aabbu;
|
|
constexpr uint32_t grouped_second_value = 0x3344ccddu;
|
|
constexpr uint32_t grouped_stale = 0;
|
|
static_assert(grouped_owner_size < (32ull << 20) &&
|
|
grouped_owner_size * 2 > (32ull << 20));
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
base + grouped_first_offset, &grouped_stale, sizeof(grouped_stale));
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
base + grouped_second_offset, &grouped_stale, sizeof(grouped_stale));
|
|
auto grouped_first = cache.ObtainBuffer(base + grouped_first_offset,
|
|
grouped_owner_size, true, false);
|
|
auto grouped_second = cache.ObtainBuffer(base + grouped_second_offset,
|
|
grouped_owner_size, true, false);
|
|
Require(name, "per-owner GC allocation",
|
|
grouped_first.first != nullptr &&
|
|
grouped_second.first != nullptr &&
|
|
grouped_first.first != grouped_second.first,
|
|
"disjoint GC candidates merged into one Buffer owner");
|
|
cache.FillBuffer(base + grouped_first_offset, grouped_owner_size,
|
|
grouped_first_value, false);
|
|
cache.FillBuffer(base + grouped_second_offset, grouped_owner_size,
|
|
grouped_second_value, false);
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "per-owner fixed-ring retirement",
|
|
!cache.IsRegionRegistered(base + grouped_first_offset,
|
|
grouped_owner_size) &&
|
|
!cache.IsRegionRegistered(base + grouped_second_offset,
|
|
grouped_owner_size) &&
|
|
&BufferCacheTestAccess::DownloadBuffer(cache) ==
|
|
fixed_download &&
|
|
fixed_download->Handle() == fixed_download_handle,
|
|
"GC aggregated disjoint retirees or replaced the download ring");
|
|
uint32_t grouped_first_backing = 0;
|
|
uint32_t grouped_second_backing = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + grouped_first_offset,
|
|
&grouped_first_backing,
|
|
sizeof(grouped_first_backing));
|
|
Libs::LibKernel::Memory::TryReadBacking(base + grouped_second_offset,
|
|
&grouped_second_backing,
|
|
sizeof(grouped_second_backing));
|
|
Require(name, "per-owner fixed-ring contents",
|
|
grouped_first_backing == grouped_first_value &&
|
|
grouped_second_backing == grouped_second_value,
|
|
"per-owner GC transfers lost data while wrapping the fixed ring");
|
|
|
|
constexpr uint64_t disjoint_owner_offset = 0x2140000;
|
|
constexpr uint64_t disjoint_owner_size = 0x8000;
|
|
constexpr uint64_t disjoint_dirty_offset = disjoint_owner_offset + 0x100;
|
|
constexpr uint64_t disjoint_new_owner_offset =
|
|
disjoint_owner_offset + 0x5000;
|
|
constexpr uint32_t disjoint_value = 0x89abcdefu;
|
|
constexpr uint32_t disjoint_stale = 0x76543210u;
|
|
std::memcpy(memory + disjoint_dirty_offset, &disjoint_stale,
|
|
sizeof(disjoint_stale));
|
|
auto wide_owner = cache.ObtainBuffer(base + disjoint_owner_offset,
|
|
disjoint_owner_size, false);
|
|
Require(name, "disjoint retirement owner", wide_owner.first != nullptr,
|
|
"failed to create the clean cross-page retirement owner");
|
|
(void)cache.ObtainBuffer(base + disjoint_dirty_offset,
|
|
sizeof(disjoint_value), true, false);
|
|
cache.FillBuffer(base + disjoint_dirty_offset, sizeof(disjoint_value),
|
|
disjoint_value, false);
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "disjoint synchronized retirement",
|
|
!cache.IsRegionRegistered(base + disjoint_owner_offset,
|
|
disjoint_owner_size),
|
|
"cross-page owner survived pressured collection");
|
|
uint32_t disjoint_before_unmap = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + disjoint_dirty_offset,
|
|
&disjoint_before_unmap,
|
|
sizeof(disjoint_before_unmap));
|
|
Require(
|
|
name, "disjoint retirement publication",
|
|
disjoint_before_unmap == disjoint_value,
|
|
"whole-owner Buffer retired before publishing its dirty bytes");
|
|
resources.UnmapMemory(base + disjoint_owner_offset + 0x4000, 0x4000);
|
|
uint32_t disjoint_after_unmap = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + disjoint_dirty_offset,
|
|
&disjoint_after_unmap,
|
|
sizeof(disjoint_after_unmap));
|
|
Require(name, "disjoint synchronized invalidation",
|
|
disjoint_after_unmap == disjoint_value &&
|
|
!cache.HasGpuDirtyBytes(base + disjoint_dirty_offset,
|
|
sizeof(disjoint_value)),
|
|
"disjoint invalidation lost the completed whole-owner publication");
|
|
resources.MapMemory(base + disjoint_owner_offset + 0x4000, 0x4000);
|
|
auto disjoint_new_owner =
|
|
cache.ObtainBuffer(base + disjoint_new_owner_offset,
|
|
sizeof(disjoint_value), true, false);
|
|
Require(name, "disjoint post-publication reacquire",
|
|
disjoint_new_owner.first != nullptr,
|
|
"disjoint acquisition failed after whole-owner publication");
|
|
uint32_t disjoint_backing = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + disjoint_dirty_offset,
|
|
&disjoint_backing,
|
|
sizeof(disjoint_backing));
|
|
Require(name, "disjoint retirement contents",
|
|
disjoint_backing == disjoint_value,
|
|
"old retirement callback lost dirty bytes or retained ownership "
|
|
"after a disjoint reacquire");
|
|
cache.ReadMemory(base + disjoint_new_owner_offset,
|
|
sizeof(disjoint_value));
|
|
|
|
constexpr uint64_t reacquire_owner_offset = 0x2200000;
|
|
constexpr uint64_t reacquire_owner_size = 0x8000;
|
|
constexpr uint64_t reacquire_dirty_offset =
|
|
reacquire_owner_offset + 0x100;
|
|
constexpr uint64_t reacquire_disjoint_offset =
|
|
reacquire_owner_offset + 0x5000;
|
|
constexpr uint32_t reacquire_value = 0xc001d00du;
|
|
constexpr uint32_t reacquire_stale = 0x0badf00du;
|
|
std::memcpy(memory + reacquire_dirty_offset, &reacquire_stale,
|
|
sizeof(reacquire_stale));
|
|
auto reacquire_owner = cache.ObtainBuffer(base + reacquire_owner_offset,
|
|
reacquire_owner_size, false);
|
|
Require(name, "reacquire retirement owner",
|
|
reacquire_owner.first != nullptr,
|
|
"failed to create a fresh cross-page retirement owner");
|
|
(void)cache.ObtainBuffer(base + reacquire_dirty_offset,
|
|
sizeof(reacquire_value), true, false);
|
|
cache.FillBuffer(base + reacquire_dirty_offset, sizeof(reacquire_value),
|
|
reacquire_value, false);
|
|
for (uint32_t tick = 0; tick <= 160; tick++) {
|
|
cache.RunGarbageCollector();
|
|
}
|
|
Require(name, "reacquire synchronized retirement",
|
|
!cache.IsRegionRegistered(base + reacquire_owner_offset,
|
|
reacquire_owner_size),
|
|
"fresh cross-page owner survived pressured collection");
|
|
uint32_t reacquire_before = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + reacquire_dirty_offset,
|
|
&reacquire_before,
|
|
sizeof(reacquire_before));
|
|
Require(name, "reacquire publication completed",
|
|
reacquire_before == reacquire_value,
|
|
"fresh whole-owner ownership retired before publication");
|
|
auto reacquired =
|
|
cache.ObtainBuffer(base + reacquire_disjoint_offset,
|
|
sizeof(reacquire_value), true, false);
|
|
Require(name, "independent disjoint post-publication reacquire",
|
|
reacquired.first != nullptr,
|
|
"clean disjoint-half acquisition failed after publication");
|
|
uint32_t reacquire_after = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + reacquire_dirty_offset,
|
|
&reacquire_after,
|
|
sizeof(reacquire_after));
|
|
Require(name, "independent disjoint reacquire contents",
|
|
reacquire_after == reacquire_value &&
|
|
!cache.HasGpuDirtyBytes(base + reacquire_dirty_offset,
|
|
sizeof(reacquire_value)),
|
|
"disjoint-half acquisition failed to publish the retired "
|
|
"owner's dirty prefix");
|
|
cache.ReadMemory(base + reacquire_disjoint_offset,
|
|
sizeof(reacquire_value));
|
|
|
|
resources.SetGpu(nullptr);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
context.ShutdownGpu();
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"dirty-GC direct-memory mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"dirty-GC direct-memory allocation release failed");
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckComputeMetaClearClassification() {
|
|
constexpr const char *name = "ComputeMetaClearClassification";
|
|
constexpr uint64_t read_only_meta = 0x0000000204201f00ull;
|
|
constexpr uint64_t read_write_meta = 0x0000000204202000ull;
|
|
constexpr uint64_t write_only_meta = 0x0000000204202100ull;
|
|
EnsureRuntimeContext();
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &texture_cache = context.GetTextureCache();
|
|
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_only_meta);
|
|
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, read_write_meta);
|
|
TextureCacheTestAccess::RegisterHtileMeta(texture_cache, write_only_meta);
|
|
Require(name, "HTile fixture",
|
|
texture_cache.IsMeta(read_only_meta) &&
|
|
texture_cache.IsMeta(read_write_meta) &&
|
|
texture_cache.IsMeta(write_only_meta) &&
|
|
!texture_cache.IsMetaCleared(read_only_meta, 0) &&
|
|
!texture_cache.IsMetaCleared(read_write_meta, 0) &&
|
|
!texture_cache.IsMetaCleared(write_only_meta, 0),
|
|
"test HTile entries were not registered in their initial state");
|
|
|
|
const auto MakeInput = [](uint64_t address, bool read, bool written,
|
|
ShaderRecompiler::IR::CompiledShaderInfo &program) {
|
|
program.stage = ShaderType::Compute;
|
|
ShaderRecompiler::IR::BufferResource resource{};
|
|
resource.read = read;
|
|
resource.written = written;
|
|
resource.formatted = true;
|
|
program.info.buffers.push_back(resource);
|
|
|
|
ShaderBufferResource descriptor{};
|
|
descriptor.UpdateAddress48(address);
|
|
descriptor.fields[1] |= 16u << 16u;
|
|
descriptor.fields[2] = 8;
|
|
descriptor.fields[3] =
|
|
DstSel(4, 5, 6, 7) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32UInt)
|
|
<< 12u);
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 4;
|
|
std::copy_n(descriptor.fields, value.dword_count, value.dwords.begin());
|
|
ShaderComputeInputInfo input{};
|
|
input.stage.program = &program;
|
|
input.stage.resources.buffers.push_back(value);
|
|
return input;
|
|
};
|
|
auto &executor = context.GetRenderExecutor();
|
|
auto &command = scheduler.Current();
|
|
ShaderRecompiler::IR::CompiledShaderInfo read_only_program{};
|
|
const auto read_only_input =
|
|
MakeInput(read_only_meta, true, false, read_only_program);
|
|
const bool read_only_consumed =
|
|
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
|
|
executor, read_only_input, command);
|
|
Require(name, "read-only dispatch preserved",
|
|
!read_only_consumed &&
|
|
!texture_cache.IsMetaCleared(read_only_meta, 0),
|
|
"a metadata read-only dispatch changed logical clear state");
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo read_write_program{};
|
|
const auto read_write_input =
|
|
MakeInput(read_write_meta, true, true, read_write_program);
|
|
const bool read_write_consumed =
|
|
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
|
|
executor, read_write_input, command);
|
|
Require(name, "read/write dispatch preserved",
|
|
!read_write_consumed &&
|
|
!texture_cache.IsMetaCleared(read_write_meta, 0),
|
|
"a metadata read/modify/write dispatch was replaced by a clear");
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo write_only_program{};
|
|
const auto write_only_input =
|
|
MakeInput(write_only_meta, false, true, write_only_program);
|
|
const bool write_only_consumed =
|
|
RenderExecutorTestAccess::TryConsumeComputeMetaClear(
|
|
executor, write_only_input, command);
|
|
Require(name, "write-only fill consumed",
|
|
write_only_consumed &&
|
|
texture_cache.IsMetaCleared(write_only_meta, 0),
|
|
"a metadata write-only fill was not consumed as a clear");
|
|
scheduler.Finish();
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckUnifiedTextureCacheFlow() {
|
|
constexpr const char *name = "UnifiedTextureCacheFlow";
|
|
constexpr uintptr_t base = 0x0000000200600000ull;
|
|
constexpr uint64_t allocation_size = 0x2800000;
|
|
constexpr uint64_t allocation_alignment = 0x200000;
|
|
EnsureRuntimeContext();
|
|
auto &context = Renderer();
|
|
CommandScheduler scheduler(context, m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
context.InitializeGpu(nullptr);
|
|
auto &gpu = context.GetGpu();
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"cache test direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"fixed cache direct-memory mapping failed");
|
|
auto *memory = static_cast<uint8_t *>(mapped);
|
|
const uint32_t initial = 0x44332211u;
|
|
std::memcpy(memory, &initial, sizeof(initial));
|
|
|
|
{
|
|
GpuResourceManager resources(m_runtime_context, scheduler);
|
|
resources.SetGpu(&gpu);
|
|
namespace Exception = Common::HostException;
|
|
Require(name, "guest fault handler",
|
|
Exception::InstallHandler([](const Exception::ExceptionInfo &info) {
|
|
if (info.type != Exception::ExceptionType::AccessViolation ||
|
|
(info.access_violation_type != Exception::AccessViolationType::Read &&
|
|
info.access_violation_type != Exception::AccessViolationType::Write)) {
|
|
return false;
|
|
}
|
|
const auto access =
|
|
info.access_violation_type == Exception::AccessViolationType::Write
|
|
? PageFaultAccess::Write : PageFaultAccess::Read;
|
|
return LibKernel::Memory::HandleGpuFault(access, info.access_violation_vaddr);
|
|
}),
|
|
"failed to install the production guest-memory fault route");
|
|
LibKernel::Memory::InstallGpuResources(&resources);
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
const auto [narrow_download, narrow_download_offset] =
|
|
TextureCacheTestAccess::MapDownload(texture_cache, 4, 4);
|
|
const auto [wide_download, wide_download_offset] =
|
|
TextureCacheTestAccess::MapDownload(texture_cache, 1, 16);
|
|
Require(name, "image download alignment",
|
|
narrow_download != nullptr && narrow_download_offset % 4 == 0 &&
|
|
wide_download != nullptr && wide_download_offset % 16 == 0,
|
|
"wide/block image readback was not aligned to its texel block");
|
|
resources.MapMemory(base, allocation_size);
|
|
|
|
ImageDesc sampled{};
|
|
sampled.type = BindingType::Texture;
|
|
sampled.info.data = {base, sizeof(initial)};
|
|
sampled.info.pixel_format = vk::Format::eR8G8B8A8Srgb;
|
|
sampled.info.guest_format = Prospero::BufferFormat::k8_8_8_8Srgb;
|
|
sampled.info.type = Prospero::ImageType::kColor2D;
|
|
sampled.info.extent = {1, 1, 1};
|
|
sampled.info.resources = {1, 1};
|
|
sampled.info.pitch = 1;
|
|
sampled.info.bytes_per_block = 4;
|
|
sampled.info.samples = 1;
|
|
sampled.info.tile_mode = Prospero::TileMode::kLinear;
|
|
sampled.info.mip_layout[0] = {0, 4, 1, 1};
|
|
sampled.view_info.format = sampled.info.pixel_format;
|
|
sampled.view_info.type = vk::ImageViewType::e2D;
|
|
sampled.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
sampled.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
|
|
auto &command = scheduler.Current();
|
|
auto first_desc = sampled;
|
|
const auto first = texture_cache.FindImage(first_desc);
|
|
auto repeated_desc = sampled;
|
|
const auto repeated = texture_cache.FindImage(repeated_desc);
|
|
auto compatible_desc = sampled;
|
|
compatible_desc.info.pixel_format = vk::Format::eR8G8B8A8Uint;
|
|
compatible_desc.info.guest_format = Prospero::BufferFormat::k8_8_8_8UInt;
|
|
compatible_desc.view_info.format = compatible_desc.info.pixel_format;
|
|
const auto compatible = texture_cache.FindImage(compatible_desc);
|
|
Require(name, "normalized FindImage",
|
|
first && repeated == first && compatible == first &&
|
|
texture_cache.GetImage(first).info.pixel_format ==
|
|
vk::Format::eR8G8B8A8Srgb,
|
|
"registered compatible backing did not reuse one ImageId");
|
|
|
|
const auto IsOnlyImage = [](const std::vector<ImageId> &ids,
|
|
ImageId expected) {
|
|
return ids.size() == 1 && ids.front() == expected;
|
|
};
|
|
const auto exact_byte_miss =
|
|
TextureCacheTestAccess::FindImages(texture_cache, base + 8, 1, false);
|
|
const auto touched_page_hit =
|
|
TextureCacheTestAccess::FindImages(texture_cache, base + 8, 1, true);
|
|
const auto next_page_miss = TextureCacheTestAccess::FindImages(
|
|
texture_cache, base + 0x1000, 1, true);
|
|
Require(name, "exact and 4-KiB page filtering",
|
|
exact_byte_miss.empty() && IsOnlyImage(touched_page_hit, first) &&
|
|
next_page_miss.empty(),
|
|
"coarse candidates did not preserve exact-byte and touched-page "
|
|
"semantics");
|
|
|
|
const auto MakeOwnershipInfo = [](uint64_t address, uint64_t size) {
|
|
ImageInfo info{};
|
|
info.data = {address, size};
|
|
info.extent = {1, 1, 1};
|
|
info.resources = {1, 1};
|
|
info.samples = 1;
|
|
return info;
|
|
};
|
|
const auto spanning_info = MakeOwnershipInfo(base + 0x1ff000, 0x2000);
|
|
const auto spanning =
|
|
TextureCacheTestAccess::InsertImage(texture_cache, spanning_info);
|
|
const auto spanning_results = TextureCacheTestAccess::FindImages(
|
|
texture_cache, spanning_info.data.address, spanning_info.data.size,
|
|
false);
|
|
Require(name, "one-MiB cross-page deduplication",
|
|
spanning && IsOnlyImage(spanning_results, spanning) &&
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, spanning_info.data.address) == 1 &&
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, spanning_info.data.End() - 1) == 1,
|
|
"an image spanning two coarse pages was missing or returned more "
|
|
"than once");
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, spanning);
|
|
Require(name, "cross-page owner cleanup",
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, spanning_info.data.address) == 0 &&
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, spanning_info.data.End() - 1) == 0 &&
|
|
TextureCacheTestAccess::FindImages(
|
|
texture_cache, spanning_info.data.address,
|
|
spanning_info.data.size, false)
|
|
.empty(),
|
|
"cross-page unregister left a stale coarse-page membership");
|
|
|
|
const auto shared_info = MakeOwnershipInfo(base + 0x500100, 0x100);
|
|
const auto shared_first =
|
|
TextureCacheTestAccess::InsertImage(texture_cache, shared_info);
|
|
const auto shared_second =
|
|
TextureCacheTestAccess::InsertImage(texture_cache, shared_info);
|
|
const auto shared_results = TextureCacheTestAccess::FindImages(
|
|
texture_cache, shared_info.data.address, shared_info.data.size,
|
|
false);
|
|
Require(name, "shared coarse-page registration",
|
|
shared_results.size() == 2 &&
|
|
std::find(shared_results.begin(), shared_results.end(),
|
|
shared_first) != shared_results.end() &&
|
|
std::find(shared_results.begin(), shared_results.end(),
|
|
shared_second) != shared_results.end(),
|
|
"two registered owners were not retained in one coarse page");
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, shared_first);
|
|
const auto shared_survivor = TextureCacheTestAccess::FindImages(
|
|
texture_cache, shared_info.data.address, shared_info.data.size,
|
|
false);
|
|
Require(name, "shared coarse-page unregister",
|
|
IsOnlyImage(shared_survivor, shared_second) &&
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, shared_info.data.address) == 1,
|
|
"unregistering one owner removed its coarse-page neighbor");
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, shared_second);
|
|
Require(name, "final shared coarse-page unregister",
|
|
TextureCacheTestAccess::PageOwnerCount(
|
|
texture_cache, shared_info.data.address) == 0,
|
|
"the final shared owner remained registered");
|
|
|
|
constexpr uint64_t large_owner_size = 64ull * 1024 * 1024;
|
|
const auto large_info =
|
|
MakeOwnershipInfo(base + 0x800000, large_owner_size);
|
|
const auto large_owner =
|
|
TextureCacheTestAccess::InsertImage(texture_cache, large_info);
|
|
Require(name, "production one-MiB registration granularity",
|
|
TextureCacheTestAccess::OwnedPageCount(
|
|
texture_cache, large_info.data.address, large_info.data.size,
|
|
large_owner) == 64,
|
|
"64 MiB image registration did not create exactly 64 coarse "
|
|
"memberships");
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, large_owner);
|
|
Require(name, "production one-MiB unregister granularity",
|
|
TextureCacheTestAccess::OwnedPageCount(
|
|
texture_cache, large_info.data.address, large_info.data.size,
|
|
large_owner) == 0,
|
|
"large image unregister left coarse memberships behind");
|
|
|
|
const ImageId stale{first.index, first.generation + 1};
|
|
TextureCacheTestAccess::AddPageOwner(texture_cache, base, stale);
|
|
const auto stale_filtered = TextureCacheTestAccess::FindImages(
|
|
texture_cache, base, sizeof(initial), false);
|
|
Require(name, "stale page owner filtering",
|
|
IsOnlyImage(stale_filtered, first) &&
|
|
TextureCacheTestAccess::RemovePageOwner(texture_cache, base,
|
|
stale),
|
|
"a stale generation escaped the direct page-owner lookup");
|
|
|
|
TextureCacheTestAccess::SetQueryEpoch(texture_cache, UINT32_MAX);
|
|
const auto wrap_results = TextureCacheTestAccess::FindImages(
|
|
texture_cache, base, sizeof(initial), false);
|
|
Require(name, "page-owner query epoch wrap",
|
|
IsOnlyImage(wrap_results, first) &&
|
|
TextureCacheTestAccess::QueryEpoch(texture_cache) == 1,
|
|
"query deduplication failed while wrapping its epoch");
|
|
|
|
auto &image = texture_cache.GetImage(first);
|
|
constexpr uint32_t final_sampled_value = 0x88776655u;
|
|
Require(name, "sampled write between discovery and acquisition",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
sampled.info.data.address),
|
|
"sampled image did not accept a CPU update after discovery");
|
|
std::memcpy(memory, &final_sampled_value, sizeof(final_sampled_value));
|
|
Require(name, "sampled final-acquisition precondition",
|
|
image.IsCpuDirty(),
|
|
"the between-phase CPU write did not dirty the discovered image");
|
|
const auto first_view = texture_cache.FindTexture(first, first_desc);
|
|
const auto repeated_view =
|
|
texture_cache.FindTexture(repeated, repeated_desc);
|
|
auto mapped_view_info = sampled.view_info;
|
|
mapped_view_info.mapping.r = vk::ComponentSwizzle::eB;
|
|
mapped_view_info.mapping.b = vk::ComponentSwizzle::eR;
|
|
const auto mapped_view = image.FindView(mapped_view_info);
|
|
Require(
|
|
name, "dynamic views",
|
|
first_view != nullptr && repeated_view == first_view &&
|
|
mapped_view != nullptr && mapped_view != first_view &&
|
|
image.views.size() == 2 && !image.usage.texture &&
|
|
!image.IsGpuModified() && !image.IsCpuDirty() &&
|
|
!TextureCacheTestAccess::PendingDownload(texture_cache, first),
|
|
"TextureCache::FindTexture did not preserve sampled view "
|
|
"identity, final refresh, and cache-only ownership");
|
|
|
|
constexpr uint64_t resource_replacement_offset = 0x700000;
|
|
const std::array<uint32_t, 3> resource_replacement_data{
|
|
0x01020304u, 0x11121314u, 0x21222324u};
|
|
std::memcpy(memory + resource_replacement_offset,
|
|
resource_replacement_data.data(),
|
|
sizeof(resource_replacement_data));
|
|
auto underspecified = sampled;
|
|
underspecified.info.data = {base + resource_replacement_offset,
|
|
sizeof(resource_replacement_data)};
|
|
underspecified.info.pixel_format = vk::Format::eR32Uint;
|
|
underspecified.info.guest_format = Prospero::BufferFormat::k32UInt;
|
|
underspecified.info.extent = {2, 1, 1};
|
|
underspecified.info.resources = {1, 1};
|
|
underspecified.info.pitch = 2;
|
|
underspecified.info.mip_layout[0] = {0, 8, 2, 1};
|
|
underspecified.info.mip_layout[1] = {8, 4, 1, 1};
|
|
underspecified.view_info.format = underspecified.info.pixel_format;
|
|
underspecified.view_info.level_count = 1;
|
|
const auto underspecified_id = texture_cache.FindImage(underspecified);
|
|
(void)texture_cache.FindTexture(underspecified_id, underspecified);
|
|
texture_cache.MarkGpuWritten(underspecified_id);
|
|
|
|
auto complete = underspecified;
|
|
complete.info.resources = {2, 1};
|
|
complete.view_info.level_count = 2;
|
|
const auto complete_id = texture_cache.FindImage(complete);
|
|
const auto complete_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, complete_id);
|
|
Require(name, "insufficient-resource replacement",
|
|
complete_id && complete_id != underspecified_id &&
|
|
!TextureCacheTestAccess::Contains(texture_cache,
|
|
underspecified_id) &&
|
|
complete_owner != nullptr && complete_owner->registered &&
|
|
complete_owner->info.resources == complete.info.resources &&
|
|
!complete_owner->IsGpuModified() &&
|
|
complete_owner->IsDefinitelyCpuDirty(),
|
|
"an exact backing with insufficient resources was expanded "
|
|
"instead of being discarded and recreated");
|
|
|
|
ImageInfo chain = sampled.info;
|
|
chain.data = {0x10000, 0x8000};
|
|
chain.extent = {8, 8, 1};
|
|
chain.resources = {2, 4};
|
|
chain.pitch = 8;
|
|
chain.mip_layout[0] = {0, 0x4000, 8, 8};
|
|
chain.mip_layout[1] = {0x4000, 0x4000, 4, 4};
|
|
ImageInfo subresource = chain;
|
|
subresource.data = {0x16000, 0x1000};
|
|
subresource.extent = {4, 4, 1};
|
|
subresource.resources = {1, 1};
|
|
subresource.pitch = 4;
|
|
subresource.mip_layout[0] = {0, 0x1000, 4, 4};
|
|
const auto mip = subresource.MipOf(chain);
|
|
Require(name, "overlap resolution",
|
|
mip == 1 && subresource.SliceOf(chain, mip) == 2 &&
|
|
ImageRangeOverlaps(chain.data, subresource.data) &&
|
|
ImagePageRangesOverlap(chain.data, subresource.data),
|
|
"normalized mip/slice overlap did not resolve");
|
|
|
|
auto exact_desc = compatible_desc;
|
|
const auto exact = texture_cache.FindImage(exact_desc, true);
|
|
Require(name, "exact-format coexistence",
|
|
exact && exact != first &&
|
|
TextureCacheTestAccess::Contains(texture_cache, first) &&
|
|
texture_cache.GetImage(first).info.pixel_format ==
|
|
sampled.info.pixel_format &&
|
|
texture_cache.GetImage(exact).info.pixel_format ==
|
|
compatible_desc.info.pixel_format,
|
|
"exact-format lookup replaced or synchronized its compatible "
|
|
"cache record");
|
|
auto compatible_after_exact_desc = compatible_desc;
|
|
const auto compatible_after_exact =
|
|
texture_cache.FindImage(compatible_after_exact_desc);
|
|
Require(name, "last compatible format winner",
|
|
compatible_after_exact == exact,
|
|
"non-exact lookup did not retain the last compatible "
|
|
"registration");
|
|
|
|
auto null_desc = sampled;
|
|
null_desc.info.data = {};
|
|
auto null_repeat_desc = null_desc;
|
|
const auto null_image = texture_cache.FindImage(null_desc);
|
|
const auto null_repeat = texture_cache.FindImage(null_repeat_desc);
|
|
Require(name, "null image",
|
|
null_image && null_repeat == null_image && null_image != exact,
|
|
"typed null-image lookup was not stable");
|
|
|
|
constexpr uint64_t extent_alias_offset = 0x2400000;
|
|
auto narrow_target = sampled;
|
|
narrow_target.type = BindingType::RenderTarget;
|
|
narrow_target.info.data = {base + extent_alias_offset, 0x10000};
|
|
narrow_target.info.pixel_format = vk::Format::eR16G16B16A16Sfloat;
|
|
narrow_target.info.guest_format =
|
|
Prospero::BufferFormat::k16_16_16_16Float;
|
|
narrow_target.info.extent = {1, 1, 1};
|
|
narrow_target.info.pitch = 128;
|
|
narrow_target.info.bytes_per_block = 8;
|
|
narrow_target.info.tile_mode = Prospero::TileMode::kRenderTarget;
|
|
narrow_target.info.mip_layout[0] = {0, 0x10000, 128, 1};
|
|
narrow_target.view_info.format = narrow_target.info.pixel_format;
|
|
narrow_target.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto narrow_target_id = texture_cache.FindImage(narrow_target);
|
|
const auto narrow_target_view =
|
|
texture_cache.FindRenderTarget(narrow_target_id, narrow_target);
|
|
|
|
auto wide_target = narrow_target;
|
|
wide_target.info.extent.width = 9;
|
|
const auto wide_target_id = texture_cache.FindImage(wide_target);
|
|
const auto wide_target_view =
|
|
texture_cache.FindRenderTarget(wide_target_id, wide_target);
|
|
const auto wide_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, wide_target_id);
|
|
Require(name, "equal-allocation render-target growth",
|
|
narrow_target_view != nullptr && wide_target_view != nullptr &&
|
|
wide_target_id != narrow_target_id &&
|
|
!TextureCacheTestAccess::Contains(texture_cache,
|
|
narrow_target_id) &&
|
|
wide_owner != nullptr &&
|
|
wide_owner->info.extent == vk::Extent3D{9, 1, 1} &&
|
|
wide_owner->backing.extent == vk::Extent3D{9, 1, 1},
|
|
"a 9x1 render target reused its equal-size 1x1 native backing");
|
|
|
|
auto narrow_again = narrow_target;
|
|
const auto narrow_again_id = texture_cache.FindImage(narrow_again);
|
|
Require(name, "larger render-target backing reuse",
|
|
narrow_again_id == wide_target_id &&
|
|
texture_cache.GetImage(narrow_again_id).backing.extent ==
|
|
vk::Extent3D{9, 1, 1},
|
|
"a smaller render area replaced its compatible larger backing");
|
|
|
|
auto MakeLinearDesc =
|
|
[&](uint64_t address, uint64_t size, vk::Format format,
|
|
Prospero::BufferFormat guest_format, Prospero::ImageType type,
|
|
vk::Extent3D extent, uint32_t layers, uint32_t bytes_per_block,
|
|
uint32_t samples) {
|
|
ImageDesc desc{};
|
|
desc.type = BindingType::Texture;
|
|
desc.info.data = {address, size};
|
|
desc.info.pixel_format = format;
|
|
desc.info.guest_format = guest_format;
|
|
desc.info.type = type;
|
|
desc.info.extent = extent;
|
|
desc.info.resources = {1, layers};
|
|
desc.info.pitch = extent.width;
|
|
desc.info.bytes_per_block = bytes_per_block;
|
|
desc.info.samples = samples;
|
|
desc.info.tile_mode = Prospero::TileMode::kLinear;
|
|
desc.info.mip_layout[0] = {0, size, extent.width, extent.height};
|
|
desc.view_info.format = format;
|
|
if (type == Prospero::ImageType::kColor3D) {
|
|
desc.view_info.type = vk::ImageViewType::e3D;
|
|
} else if (layers > 1) {
|
|
desc.view_info.type = vk::ImageViewType::e2DArray;
|
|
} else {
|
|
desc.view_info.type = vk::ImageViewType::e2D;
|
|
}
|
|
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
desc.view_info.layer_count = layers;
|
|
desc.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
return desc;
|
|
};
|
|
const auto HostReadBarrier = [&](vk::Buffer buffer, uint64_t size,
|
|
vk::PipelineStageFlags source_stage,
|
|
vk::AccessFlags source_access) {
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = source_access;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = buffer;
|
|
barrier.offset = 0;
|
|
barrier.size = size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
source_stage, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1,
|
|
&barrier, 0, nullptr);
|
|
};
|
|
const auto TransferReadBarrier = [&](vk::Buffer buffer, uint64_t size) {
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = buffer;
|
|
barrier.offset = 0;
|
|
barrier.size = size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &barrier,
|
|
0, nullptr);
|
|
};
|
|
|
|
constexpr uint64_t mip_layout_offset = 0x27f0000;
|
|
std::vector<u32> mip_guest(0x2000 / sizeof(u32));
|
|
std::iota(mip_guest.begin(), mip_guest.end(), 0x51000000u);
|
|
ImageId previous{};
|
|
for (const uint32_t width : {32u, 64u, 32u}) {
|
|
if (previous) {
|
|
Require(name, "mip-layout CPU overwrite",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + mip_layout_offset),
|
|
"mip-layout guest overwrite did not invalidate the old texture");
|
|
}
|
|
std::memcpy(memory + mip_layout_offset, mip_guest.data(),
|
|
mip_guest.size() * sizeof(u32));
|
|
TileSurfaceLayout surface{};
|
|
Require(name, "mip-tail layout",
|
|
TileGetTiledTextureLayout(
|
|
{.format = Prospero::BufferFormat::kBc3Srgb,
|
|
.tile_mode = Prospero::TileMode::kStandard4KB,
|
|
.width = width, .height = 64, .levels = 7}, surface) &&
|
|
surface.first_tail_level == (width == 32 ? 0u : 1u) &&
|
|
surface.total_size == (width == 32 ? 0x1000u : 0x2000u),
|
|
"BC3 width change did not move mip zero into or out of the tail");
|
|
ImageDesc desc{};
|
|
desc.type = BindingType::Texture;
|
|
desc.info.data = {base + mip_layout_offset, surface.total_size};
|
|
desc.info.pixel_format = vk::Format::eBc3SrgbBlock;
|
|
desc.info.guest_format = Prospero::BufferFormat::kBc3Srgb;
|
|
desc.info.extent = {width, 64, 1};
|
|
desc.info.resources = {7, 1};
|
|
desc.info.pitch = 64;
|
|
desc.info.bytes_per_block = 16;
|
|
desc.info.tile_mode = Prospero::TileMode::kStandard4KB;
|
|
desc.view_info.format = desc.info.pixel_format;
|
|
desc.view_info.type = vk::ImageViewType::e2D;
|
|
desc.view_info.level_count = 7;
|
|
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
desc.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
std::vector<vk::BufferImageCopy> copies;
|
|
std::vector<u32> expected;
|
|
for (uint32_t level = 0; level < 7; level++) {
|
|
const auto &layout = surface.mips[level];
|
|
desc.info.mip_layout[level] = {
|
|
layout.offset, layout.size, layout.padded_width,
|
|
layout.padded_height};
|
|
vk::BufferImageCopy copy{};
|
|
copy.bufferOffset = expected.size() * sizeof(u32);
|
|
copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, level, 0, 1};
|
|
copy.imageExtent = {std::max(width >> level, 1u),
|
|
std::max(64u >> level, 1u), 1};
|
|
copies.push_back(copy);
|
|
for (uint32_t y = 0; y < layout.height; y++) {
|
|
for (uint32_t x = 0; x < layout.width; x++) {
|
|
uint32_t offset = 0;
|
|
Require(name, "mip-tail CPU address",
|
|
TileGetBlockOffset(
|
|
surface.texture.block, x + layout.tail_x,
|
|
y + layout.tail_y, 0, offset),
|
|
"BC3 mip-tail block address could not be computed");
|
|
const auto word = (layout.offset + offset) / sizeof(u32);
|
|
expected.insert(expected.end(), mip_guest.begin() + word,
|
|
mip_guest.begin() + word + 4);
|
|
}
|
|
}
|
|
}
|
|
const auto id = texture_cache.FindImage(desc);
|
|
Require(name, "distinct mip-layout backing", id && id != previous,
|
|
"different guest mip layouts reused the same native image");
|
|
(void)texture_cache.FindTexture(id, desc);
|
|
auto readback = CreateHostBuffer(
|
|
name, expected.size() * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(expected.size()));
|
|
texture_cache.GetImage(id).Download(copies, readback.buffer, 0,
|
|
readback.size);
|
|
HostReadBarrier(readback.buffer, readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
Require(name, "mip-layout GPU contents",
|
|
ReadBuffer(name, readback, expected.size()) == expected,
|
|
"cached native copying contaminated the requested guest mip layout");
|
|
DestroyBuffer(&readback);
|
|
if (width == 64) {
|
|
// Leave only the larger backing so shrinking must reject its layout.
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, previous);
|
|
}
|
|
previous = id;
|
|
}
|
|
|
|
// A formatted Buffer read must use the private
|
|
// Exercise the cache-native image-copy path. Use a request larger
|
|
// than the stream shortcut and poison guest backing
|
|
// after upload so stale CPU staging cannot accidentally
|
|
// satisfy the content check.
|
|
constexpr uint64_t mip_prefix_offset = 0x2740000;
|
|
constexpr auto mip_format = Prospero::BufferFormat::k32UInt;
|
|
constexpr auto mip_tile = Prospero::TileMode::kLinear;
|
|
constexpr uint32_t mip_width = 4097;
|
|
constexpr uint32_t mip_height = 1;
|
|
constexpr uint32_t mip_levels = 1;
|
|
const uint32_t mip_pitch =
|
|
TileGetTexturePitch(mip_format, mip_width, mip_tile);
|
|
TileSizeAlign mip_total{};
|
|
std::array<TileSizeOffset, 16> mip_sizes{};
|
|
std::array<TilePaddedSize, 16> mip_padded{};
|
|
TileGetTextureSize(mip_format, mip_width, mip_height, mip_levels,
|
|
mip_tile, &mip_total, mip_sizes.data(),
|
|
mip_padded.data());
|
|
const uint64_t mip_prefix_size = mip_sizes[0].offset + mip_sizes[0].size;
|
|
const uint64_t mip_guest_size = mip_total.size + 256;
|
|
Require(name, "formatted mip fixture",
|
|
mip_sizes[0].offset == 0 &&
|
|
mip_prefix_size > BufferCache::CACHING_PAGESIZE &&
|
|
mip_prefix_size < mip_guest_size &&
|
|
mip_guest_size % sizeof(uint32_t) == 0,
|
|
"single-mip fixture did not expose a cache-sized fitting backing "
|
|
"prefix");
|
|
std::vector<uint32_t> mip_native(mip_guest_size / sizeof(uint32_t));
|
|
std::iota(mip_native.begin(), mip_native.end(), 0x61000000u);
|
|
std::memcpy(memory + mip_prefix_offset, mip_native.data(),
|
|
mip_guest_size);
|
|
auto mip_desc = MakeLinearDesc(
|
|
base + mip_prefix_offset, mip_guest_size, vk::Format::eR32Uint,
|
|
mip_format, Prospero::ImageType::kColor2D, {mip_width, mip_height, 1},
|
|
1, sizeof(uint32_t), 1);
|
|
mip_desc.info.resources.levels = mip_levels;
|
|
mip_desc.info.pitch = mip_pitch;
|
|
mip_desc.view_info.level_count = mip_levels;
|
|
for (uint32_t level = 0; level < mip_levels; level++) {
|
|
mip_desc.info.mip_layout[level] = {
|
|
mip_sizes[level].offset, mip_sizes[level].size,
|
|
mip_padded[level].width, mip_padded[level].height};
|
|
}
|
|
const auto mip_image = texture_cache.FindImage(mip_desc);
|
|
(void)texture_cache.FindTexture(mip_image, mip_desc);
|
|
texture_cache.MarkGpuWritten(mip_image);
|
|
std::vector<uint32_t> mip_stale(mip_native.size(), 0xdeadbeefu);
|
|
Libs::LibKernel::Memory::WriteBacking(base + mip_prefix_offset,
|
|
mip_stale.data(), mip_guest_size);
|
|
|
|
Libs::Graphics::Buffer mip_insufficient(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
mip_desc.info.data.address, AllFlags, mip_prefix_size - 1);
|
|
Libs::Graphics::Buffer mip_prefix(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
mip_desc.info.data.address, AllFlags, mip_prefix_size);
|
|
Require(name, "formatted mip containment",
|
|
!BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), mip_insufficient,
|
|
mip_desc.info.data.address, mip_prefix_size - 1) &&
|
|
BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), mip_prefix,
|
|
mip_desc.info.data.address, mip_prefix_size) &&
|
|
texture_cache.GetImage(mip_image).IsGpuModified(),
|
|
"image synchronization accepted a partial first "
|
|
"mip, rejected a fitting mip "
|
|
"prefix, or transferred ownership");
|
|
|
|
auto &buffer_cache = resources.GetBufferCache();
|
|
std::pair<Libs::Graphics::Buffer *, uint64_t> mip_formatted;
|
|
bool formatted_owner_was_absent = false;
|
|
gpu.SendCommandSync([&] {
|
|
formatted_owner_was_absent = !buffer_cache.IsRegionRegistered(
|
|
mip_desc.info.data.address, mip_desc.info.data.size);
|
|
mip_formatted = buffer_cache.ObtainBuffer(
|
|
mip_desc.info.data.address, mip_desc.info.data.size, false, true);
|
|
});
|
|
Require(name, "formatted cache fixture", formatted_owner_was_absent,
|
|
"formatted image already had a cached Buffer owner");
|
|
Require(name, "formatted Buffer path",
|
|
mip_formatted.first != nullptr &&
|
|
texture_cache.GetImage(mip_image).IsGpuModified(),
|
|
"formatted read bypassed the cached image-copy "
|
|
"path or transferred ownership");
|
|
auto mip_prefix_readback = CreateHostBuffer(
|
|
name, mip_prefix_size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(mip_prefix_size / sizeof(uint32_t), 0));
|
|
auto mip_formatted_readback = CreateHostBuffer(
|
|
name, mip_guest_size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(mip_guest_size / sizeof(uint32_t), 0));
|
|
TransferReadBarrier(mip_prefix.Handle(), mip_prefix_size);
|
|
const vk::BufferCopy mip_prefix_copy{0, 0, mip_prefix_size};
|
|
command.Handle().copyBuffer(
|
|
mip_prefix.Handle(), mip_prefix_readback.buffer, 1, &mip_prefix_copy);
|
|
TransferReadBarrier(mip_formatted.first->Handle(), mip_guest_size);
|
|
const vk::BufferCopy mip_formatted_copy{mip_formatted.second, 0,
|
|
mip_guest_size};
|
|
command.Handle().copyBuffer(mip_formatted.first->Handle(),
|
|
mip_formatted_readback.buffer, 1,
|
|
&mip_formatted_copy);
|
|
HostReadBarrier(mip_prefix_readback.buffer, mip_prefix_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
HostReadBarrier(mip_formatted_readback.buffer,
|
|
mip_formatted_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
const std::array<uint32_t, 2> volume_values{0x10203040u, 0x50607080u};
|
|
TileSizeAlign volume_size{};
|
|
TileGetTextureTotalSize(Prospero::BufferFormat::k32UInt, 1, 1, 2, 1,
|
|
Prospero::TileMode::kLinear, true, volume_size);
|
|
const auto volume_pitch = TileGetTexturePitch(
|
|
Prospero::BufferFormat::k32UInt, 1, Prospero::TileMode::kLinear);
|
|
const auto volume_slice_words = volume_size.size / 2 / sizeof(uint32_t);
|
|
std::vector<uint32_t> volume_guest(volume_size.size / sizeof(uint32_t));
|
|
volume_guest[0] = volume_values[0];
|
|
volume_guest[volume_slice_words] = volume_values[1];
|
|
std::memcpy(memory + 0x1000, volume_guest.data(), volume_size.size);
|
|
auto array_desc =
|
|
MakeLinearDesc(base + 0x1000, volume_size.size,
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 2, 4, 1);
|
|
array_desc.info.pitch = volume_pitch;
|
|
array_desc.info.mip_layout[0].pitch = volume_pitch;
|
|
const auto array_image = texture_cache.FindImage(array_desc);
|
|
(void)texture_cache.FindTexture(array_image, array_desc);
|
|
texture_cache.MarkGpuWritten(array_image);
|
|
auto volume_desc = array_desc;
|
|
volume_desc.info.type = Prospero::ImageType::kColor3D;
|
|
volume_desc.info.extent = {1, 1, 2};
|
|
volume_desc.info.resources.layers = 1;
|
|
volume_desc.view_info.type = vk::ImageViewType::e3D;
|
|
volume_desc.view_info.layer_count = 1;
|
|
const auto volume_image = texture_cache.FindImage(volume_desc);
|
|
Require(name, "2D-array/3D overlap copy",
|
|
volume_image && volume_image != array_image &&
|
|
texture_cache.GetImage(volume_image).backing.image_type ==
|
|
vk::ImageType::e3D &&
|
|
texture_cache.GetImage(volume_image).IsGpuModified(),
|
|
"array-to-volume expansion lost slices or GPU "
|
|
"ownership");
|
|
|
|
constexpr uint64_t unique_volume_offset = 0x27b0000;
|
|
std::memcpy(memory + unique_volume_offset, volume_guest.data(),
|
|
volume_size.size);
|
|
auto unique_volume_desc = volume_desc;
|
|
unique_volume_desc.info.data.address = base + unique_volume_offset;
|
|
const auto unique_volume_image =
|
|
texture_cache.FindImage(unique_volume_desc);
|
|
(void)texture_cache.FindTexture(unique_volume_image, unique_volume_desc);
|
|
texture_cache.MarkGpuWritten(unique_volume_image);
|
|
Libs::Graphics::Buffer partial_volume(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
unique_volume_desc.info.data.address, AllFlags,
|
|
unique_volume_desc.info.data.size - sizeof(uint32_t));
|
|
Libs::Graphics::Buffer full_volume(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
unique_volume_desc.info.data.address, AllFlags,
|
|
unique_volume_desc.info.data.size);
|
|
Require(name, "formatted volume containment",
|
|
!BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), partial_volume,
|
|
unique_volume_desc.info.data.address,
|
|
unique_volume_desc.info.data.size - sizeof(uint32_t)) &&
|
|
BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), full_volume,
|
|
unique_volume_desc.info.data.address,
|
|
unique_volume_desc.info.data.size) &&
|
|
texture_cache.GetImage(unique_volume_image).IsGpuModified(),
|
|
"partial 3D synchronization was accepted, "
|
|
"full-volume synchronization was "
|
|
"rejected, or ownership changed");
|
|
auto volume_readback = CreateHostBuffer(
|
|
name, volume_size.size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(volume_guest.size(), 0));
|
|
TransferReadBarrier(full_volume.Handle(), volume_size.size);
|
|
const vk::BufferCopy volume_copy{0, 0, volume_size.size};
|
|
command.Handle().copyBuffer(full_volume.Handle(), volume_readback.buffer,
|
|
1, &volume_copy);
|
|
HostReadBarrier(volume_readback.buffer, volume_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
constexpr uint64_t depth_containment_offset = 0x27c0000;
|
|
constexpr std::array<float, 3> depth_containment_values{0.25f, 0.75f,
|
|
0.5f};
|
|
std::memcpy(memory + depth_containment_offset,
|
|
depth_containment_values.data(),
|
|
sizeof(depth_containment_values));
|
|
auto depth_containment = MakeLinearDesc(
|
|
base + depth_containment_offset, sizeof(depth_containment_values),
|
|
vk::Format::eD32Sfloat, Prospero::BufferFormat::k32Float,
|
|
Prospero::ImageType::kColor2D, {2, 1, 1}, 1, sizeof(float), 1);
|
|
depth_containment.type = BindingType::DepthTarget;
|
|
depth_containment.info.resources.levels = 2;
|
|
depth_containment.info.mip_layout[0] = {0, 2 * sizeof(float), 2, 1};
|
|
depth_containment.info.mip_layout[1] = {2 * sizeof(float), sizeof(float),
|
|
1, 1};
|
|
depth_containment.view_info.format = vk::Format::eD32Sfloat;
|
|
depth_containment.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
depth_containment.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
depth_containment.view_info.level_count = 2;
|
|
const auto depth_containment_image =
|
|
texture_cache.FindImage(depth_containment);
|
|
(void)texture_cache.FindDepthTarget(depth_containment_image,
|
|
depth_containment);
|
|
texture_cache.MarkGpuWritten(depth_containment_image);
|
|
Libs::Graphics::Buffer partial_depth(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
depth_containment.info.data.address, AllFlags,
|
|
depth_containment.info.mip_layout[0].size);
|
|
Require(
|
|
name, "formatted depth containment",
|
|
!BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), partial_depth,
|
|
depth_containment.info.data.address,
|
|
depth_containment.info.mip_layout[0].size) &&
|
|
texture_cache.GetImage(depth_containment_image).IsGpuModified(),
|
|
"partial depth synchronization was accepted or "
|
|
"transferred ownership");
|
|
|
|
constexpr uint64_t raw_d16_offset = 0x27d0000;
|
|
constexpr std::array<uint16_t, 2> raw_d16_values{0x2000u, 0xc000u};
|
|
std::memcpy(memory + raw_d16_offset, raw_d16_values.data(),
|
|
sizeof(raw_d16_values));
|
|
auto raw_d16 = MakeLinearDesc(
|
|
base + raw_d16_offset, sizeof(raw_d16_values), vk::Format::eD16Unorm,
|
|
Prospero::BufferFormat::k16UNorm, Prospero::ImageType::kColor2D,
|
|
{2, 1, 1}, 1, sizeof(uint16_t), 1);
|
|
raw_d16.type = BindingType::DepthTarget;
|
|
raw_d16.view_info.format = vk::Format::eD16Unorm;
|
|
raw_d16.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
raw_d16.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto raw_d16_image = texture_cache.FindImage(raw_d16);
|
|
(void)texture_cache.FindDepthTarget(raw_d16_image, raw_d16);
|
|
|
|
auto raw_d16_uint = raw_d16;
|
|
raw_d16_uint.type = BindingType::Texture;
|
|
raw_d16_uint.info.pixel_format = vk::Format::eR16Uint;
|
|
raw_d16_uint.info.guest_format = Prospero::BufferFormat::k16UInt;
|
|
raw_d16_uint.view_info.format = vk::Format::eR16Uint;
|
|
raw_d16_uint.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
raw_d16_uint.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto raw_d16_uint_image = texture_cache.FindImage(raw_d16_uint);
|
|
Require(
|
|
name, "raw D16 uint texture backing",
|
|
raw_d16_uint_image && raw_d16_uint_image != raw_d16_image &&
|
|
!TextureCacheTestAccess::Contains(texture_cache, raw_d16_image) &&
|
|
texture_cache.GetImage(raw_d16_uint_image).backing.format ==
|
|
vk::Format::eR16Uint,
|
|
"D16 uint sampling did not recreate a true integer color backing");
|
|
Require(name, "raw D16 uint texture view",
|
|
texture_cache.FindTexture(raw_d16_uint_image, raw_d16_uint) !=
|
|
nullptr,
|
|
"D16 uint sampling did not create an integer sampled view");
|
|
|
|
constexpr uint64_t layered_raw_d16_offset = raw_d16_offset + 0x1000;
|
|
constexpr std::array<uint16_t, 12> layered_raw_d16_values{
|
|
0x0102u, 0x0304u, 0x1112u, 0x1314u, 0x2122u, 0x2324u,
|
|
0x3132u, 0x3334u, 0x4142u, 0x4344u, 0x5152u, 0x5354u};
|
|
std::memcpy(memory + layered_raw_d16_offset,
|
|
layered_raw_d16_values.data(),
|
|
sizeof(layered_raw_d16_values));
|
|
auto layered_d16_color = MakeLinearDesc(
|
|
base + layered_raw_d16_offset, sizeof(layered_raw_d16_values),
|
|
vk::Format::eR16Unorm, Prospero::BufferFormat::k16UNorm,
|
|
Prospero::ImageType::kColor2D, {2, 1, 1}, 6, sizeof(uint16_t), 1);
|
|
const auto layered_d16_color_image =
|
|
texture_cache.FindImage(layered_d16_color);
|
|
(void)texture_cache.FindTexture(layered_d16_color_image,
|
|
layered_d16_color);
|
|
auto &layered_d16_color_native =
|
|
texture_cache.GetImage(layered_d16_color_image);
|
|
layered_d16_color_native.Transit(vk::ImageLayout::eTransferDstOptimal,
|
|
vk::AccessFlagBits2::eTransferWrite, {},
|
|
scheduler.Current().Handle());
|
|
vk::ClearColorValue layered_raw_d16_clear_value{};
|
|
layered_raw_d16_clear_value.float32[0] = 1.0f;
|
|
const vk::ImageSubresourceRange layered_raw_d16_clear_range{
|
|
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 6};
|
|
scheduler.Current().Handle().clearColorImage(
|
|
layered_d16_color_native.backing.image,
|
|
vk::ImageLayout::eTransferDstOptimal, &layered_raw_d16_clear_value, 1,
|
|
&layered_raw_d16_clear_range);
|
|
texture_cache.MarkGpuWritten(layered_d16_color_image);
|
|
|
|
auto layered_raw_d16_depth = layered_d16_color;
|
|
layered_raw_d16_depth.type = BindingType::DepthTarget;
|
|
layered_raw_d16_depth.info.data.size = sizeof(uint16_t) * 2 * 5;
|
|
layered_raw_d16_depth.info.pixel_format = vk::Format::eD16Unorm;
|
|
layered_raw_d16_depth.info.guest_format =
|
|
Prospero::BufferFormat::k16UNorm;
|
|
layered_raw_d16_depth.info.resources.layers = 5;
|
|
layered_raw_d16_depth.info.mip_layout[0].size =
|
|
layered_raw_d16_depth.info.data.size;
|
|
layered_raw_d16_depth.view_info.format = vk::Format::eD16Unorm;
|
|
layered_raw_d16_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
layered_raw_d16_depth.view_info.layer_count = 5;
|
|
layered_raw_d16_depth.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto layered_raw_d16_depth_image =
|
|
texture_cache.FindImage(layered_raw_d16_depth);
|
|
const auto &layered_depth_owner =
|
|
texture_cache.GetImage(layered_raw_d16_depth_image);
|
|
Require(name, "layered raw D16 owner layout",
|
|
layered_raw_d16_depth_image != layered_d16_color_image &&
|
|
!TextureCacheTestAccess::Contains(texture_cache,
|
|
layered_d16_color_image) &&
|
|
layered_depth_owner.info.resources.layers == 6 &&
|
|
layered_depth_owner.info.data.size ==
|
|
sizeof(layered_raw_d16_values) &&
|
|
layered_depth_owner.info.mip_layout[0].size ==
|
|
sizeof(layered_raw_d16_values),
|
|
"a partial depth view detached the retained six-layer owner from "
|
|
"its full guest range");
|
|
(void)texture_cache.FindDepthTarget(layered_raw_d16_depth_image,
|
|
layered_raw_d16_depth);
|
|
|
|
auto reacquired_layered_raw_d16_uint = layered_d16_color;
|
|
reacquired_layered_raw_d16_uint.info.pixel_format = vk::Format::eR16Uint;
|
|
reacquired_layered_raw_d16_uint.info.guest_format =
|
|
Prospero::BufferFormat::k16UInt;
|
|
reacquired_layered_raw_d16_uint.view_info.format = vk::Format::eR16Uint;
|
|
const auto reacquired_layered_raw_d16_uint_image =
|
|
texture_cache.FindImage(reacquired_layered_raw_d16_uint);
|
|
const auto reacquired_layered_raw_d16_uint_view =
|
|
texture_cache.FindTexture(reacquired_layered_raw_d16_uint_image,
|
|
reacquired_layered_raw_d16_uint);
|
|
auto layered_raw_d16_readback = CreateHostBuffer(
|
|
name, sizeof(layered_raw_d16_values),
|
|
vk::BufferUsageFlagBits::eTransferDst, std::vector<u32>(6, 0));
|
|
auto &reacquired_layered_raw_d16_native =
|
|
texture_cache.GetImage(reacquired_layered_raw_d16_uint_image);
|
|
reacquired_layered_raw_d16_native.Transit(
|
|
vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {}, scheduler.Current().Handle());
|
|
vk::BufferImageCopy layered_raw_d16_copy{};
|
|
layered_raw_d16_copy.bufferRowLength = 2;
|
|
layered_raw_d16_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor,
|
|
0, 0, 6};
|
|
layered_raw_d16_copy.imageExtent = {2, 1, 1};
|
|
scheduler.Current().Handle().copyImageToBuffer(
|
|
reacquired_layered_raw_d16_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal, layered_raw_d16_readback.buffer,
|
|
1, &layered_raw_d16_copy);
|
|
HostReadBarrier(layered_raw_d16_readback.buffer,
|
|
layered_raw_d16_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
const auto layered_raw_d16_words =
|
|
ReadBuffer(name, layered_raw_d16_readback, 6);
|
|
std::array<uint16_t, 12> layered_raw_d16_after{};
|
|
std::memcpy(layered_raw_d16_after.data(), layered_raw_d16_words.data(),
|
|
sizeof(layered_raw_d16_after));
|
|
std::array<uint16_t, 12> layered_raw_d16_expected{};
|
|
layered_raw_d16_expected.fill(UINT16_MAX);
|
|
Require(
|
|
name, "layered raw D16 round trip",
|
|
reacquired_layered_raw_d16_uint_image &&
|
|
reacquired_layered_raw_d16_uint_image !=
|
|
layered_raw_d16_depth_image &&
|
|
!TextureCacheTestAccess::Contains(texture_cache,
|
|
layered_raw_d16_depth_image) &&
|
|
reacquired_layered_raw_d16_native.backing.format ==
|
|
vk::Format::eR16Uint &&
|
|
reacquired_layered_raw_d16_uint_view != nullptr &&
|
|
layered_raw_d16_after == layered_raw_d16_expected,
|
|
"the partial depth view lost a layer or failed to restore raw UINT "
|
|
"sampling");
|
|
DestroyBuffer(&layered_raw_d16_readback);
|
|
|
|
auto native_array_info = array_desc.info;
|
|
native_array_info.data = {};
|
|
auto native_volume_info = volume_desc.info;
|
|
native_volume_info.data = {};
|
|
Libs::Graphics::Image native_array(m_runtime_context, scheduler,
|
|
native_array_info);
|
|
Libs::Graphics::Image native_volume(m_runtime_context, scheduler,
|
|
native_volume_info);
|
|
native_volume.CopyImage(native_array);
|
|
native_array.CopyImage(native_volume);
|
|
Require(name, "Image-owned 2D-array/3D copy state",
|
|
native_volume.backing.state.layout ==
|
|
vk::ImageLayout::eTransferSrcOptimal &&
|
|
native_volume.backing.state.access_mask ==
|
|
vk::AccessFlagBits2::eTransferRead &&
|
|
native_array.backing.state.layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
native_array.backing.state.access_mask ==
|
|
(vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eTransferRead),
|
|
"Image::CopyImage did not retain pinned "
|
|
"source/destination states");
|
|
|
|
ImageInfo resolve_source_info{};
|
|
resolve_source_info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
resolve_source_info.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
resolve_source_info.type = Prospero::ImageType::kColor2D;
|
|
resolve_source_info.extent = {4, 4, 1};
|
|
resolve_source_info.resources = {1, 1};
|
|
resolve_source_info.pitch = 4;
|
|
resolve_source_info.bytes_per_block = 4;
|
|
resolve_source_info.samples = 2;
|
|
resolve_source_info.tile_mode = Prospero::TileMode::kRenderTarget;
|
|
ImageInfo resolve_destination_info = resolve_source_info;
|
|
resolve_destination_info.type = Prospero::ImageType::kColor2D;
|
|
resolve_destination_info.samples = 1;
|
|
Libs::Graphics::Image resolve_source(m_runtime_context, scheduler,
|
|
resolve_source_info);
|
|
Libs::Graphics::Image resolve_destination(m_runtime_context, scheduler,
|
|
resolve_destination_info);
|
|
resolve_destination.Resolve(resolve_source, {}, {});
|
|
Require(
|
|
name, "Image-owned MSAA resolve",
|
|
resolve_source.backing.state.layout ==
|
|
vk::ImageLayout::eTransferSrcOptimal &&
|
|
resolve_destination.backing.state.layout ==
|
|
vk::ImageLayout::eTransferDstOptimal,
|
|
"Image::Resolve did not issue the MSAA-to-single-sample transfer");
|
|
Libs::Graphics::Image copy_destination(m_runtime_context, scheduler,
|
|
resolve_destination_info);
|
|
copy_destination.Resolve(resolve_destination, {}, {});
|
|
Require(name, "Image-owned single-sample resolve copy",
|
|
resolve_destination.backing.state.layout ==
|
|
vk::ImageLayout::eTransferSrcOptimal &&
|
|
copy_destination.backing.state.layout ==
|
|
vk::ImageLayout::eTransferDstOptimal,
|
|
"Image::Resolve did not retain the single-sample copy path");
|
|
|
|
const std::array<uint16_t, 2> multisample_source{0x4000u, 0xc000u};
|
|
std::memcpy(memory + 0x2000, &multisample_source,
|
|
sizeof(multisample_source));
|
|
auto color_desc = MakeLinearDesc(
|
|
base + 0x2000, sizeof(multisample_source), vk::Format::eR16G16Unorm,
|
|
Prospero::BufferFormat::k16_16UNorm, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
const auto color_image = texture_cache.FindImage(color_desc);
|
|
(void)texture_cache.FindTexture(color_image, color_desc);
|
|
texture_cache.MarkGpuWritten(color_image);
|
|
const std::array<uint16_t, 2> refreshed_multisample_source{0x2000u,
|
|
0xe000u};
|
|
Require(name, "unequal-sample source CPU write",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
color_desc.info.data.address),
|
|
"color-to-MS-depth source did not accept a CPU update");
|
|
std::memcpy(memory + 0x2000, refreshed_multisample_source.data(),
|
|
sizeof(refreshed_multisample_source));
|
|
constexpr uint64_t ms_stencil_offset = 0x80000;
|
|
constexpr uint64_t ms_stencil_size = 0x10000;
|
|
auto ms_stencil_owner = resources.GetBufferCache().ObtainBuffer(
|
|
base + ms_stencil_offset, ms_stencil_size, false);
|
|
Require(name, "MS stencil buffer allocation",
|
|
ms_stencil_owner.first != nullptr,
|
|
"failed to create an unequal-sample stencil source");
|
|
resources.GetBufferCache().FillBuffer(
|
|
base + ms_stencil_offset, ms_stencil_size, 0x41414141u, false);
|
|
auto ms_depth_desc = color_desc;
|
|
ms_depth_desc.type = BindingType::DepthTarget;
|
|
ms_depth_desc.info.stencil = {base + ms_stencil_offset, ms_stencil_size};
|
|
ms_depth_desc.info.pixel_format = vk::Format::eD24UnormS8Uint;
|
|
ms_depth_desc.info.guest_format = Prospero::BufferFormat::k16UNorm;
|
|
ms_depth_desc.info.bytes_per_block = 2;
|
|
ms_depth_desc.info.samples = 2;
|
|
ms_depth_desc.info.type = Prospero::ImageType::kColor2D;
|
|
ms_depth_desc.view_info.format = vk::Format::eD24UnormS8Uint;
|
|
ms_depth_desc.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
ms_depth_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto ms_depth_image = texture_cache.FindImage(ms_depth_desc);
|
|
constexpr uint64_t ms_htile_address = base + 0x2600000;
|
|
constexpr uint64_t ms_htile_size = 0x10000;
|
|
ms_depth_desc.info.metadata.range = {ms_htile_address, ms_htile_size};
|
|
ms_depth_desc.info.metadata.kind = ImageMetadataKind::Htile;
|
|
const auto ms_depth_with_htile = texture_cache.FindImage(ms_depth_desc);
|
|
const auto ms_depth_view =
|
|
texture_cache.FindDepthTarget(ms_depth_with_htile, ms_depth_desc);
|
|
auto sampled_ms_depth = ms_depth_desc;
|
|
sampled_ms_depth.type = BindingType::Texture;
|
|
sampled_ms_depth.info.metadata = {};
|
|
sampled_ms_depth.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto sampled_ms_depth_image =
|
|
texture_cache.FindImage(sampled_ms_depth);
|
|
Require(
|
|
name, "unequal-sample depth overlap",
|
|
ms_depth_image && ms_depth_with_htile == ms_depth_image &&
|
|
sampled_ms_depth_image == ms_depth_image &&
|
|
ms_depth_image != color_image && ms_depth_view != nullptr &&
|
|
texture_cache.GetImage(ms_depth_image).backing.samples == 2 &&
|
|
texture_cache.GetImage(ms_depth_image).backing.state.layout ==
|
|
vk::ImageLayout::eDepthStencilAttachmentOptimal &&
|
|
texture_cache.GetImage(ms_depth_image).IsGpuModified() &&
|
|
texture_cache.GetImage(ms_depth_image).info.htile_clear_mask ==
|
|
0 &&
|
|
texture_cache.GetImage(ms_depth_image).info.metadata.range ==
|
|
ms_depth_desc.info.metadata.range &&
|
|
texture_cache.IsMeta(ms_htile_address) &&
|
|
!texture_cache.IsMetaCleared(ms_htile_address, 0) &&
|
|
!resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + ms_stencil_offset, ms_stencil_size) &&
|
|
!texture_cache.IsRegionGpuModified(base + ms_stencil_offset,
|
|
ms_stencil_size),
|
|
"unequal-sample overlap did not run the color-to-MS-depth pass "
|
|
"without manufacturing stencil ownership");
|
|
auto &oversized_ms = texture_cache.GetImage(ms_depth_image);
|
|
const auto ms_data_size = oversized_ms.info.data.size;
|
|
oversized_ms.info.data.size = (32ull << 20) + 4;
|
|
const bool oversized_ms_readback =
|
|
!TextureCacheTestAccess::TryDownload(texture_cache, ms_depth_image);
|
|
oversized_ms.info.data.size = ms_data_size;
|
|
Require(name, "oversized multisample download rejection",
|
|
oversized_ms_readback && oversized_ms.IsGpuModified() &&
|
|
!oversized_ms.IsBufferModified() &&
|
|
!resources.GetBufferCache().HasGpuDirtyBytes(
|
|
ms_depth_desc.info.data.address, ms_data_size),
|
|
"an unsupported multisample source reserved download storage or "
|
|
"published Buffer ownership");
|
|
|
|
const std::array<uint16_t, 4> multisample_source_4x{0x0000u, 0x4000u,
|
|
0x8000u, 0xffffu};
|
|
std::memcpy(memory + 0x7000, multisample_source_4x.data(),
|
|
sizeof(multisample_source_4x));
|
|
auto color_desc_4x =
|
|
MakeLinearDesc(base + 0x7000, sizeof(multisample_source_4x),
|
|
vk::Format::eR16G16B16A16Unorm,
|
|
Prospero::BufferFormat::k16_16_16_16UNorm,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 8, 1);
|
|
const auto color_image_4x = texture_cache.FindImage(color_desc_4x);
|
|
auto ms_depth_desc_4x = color_desc_4x;
|
|
ms_depth_desc_4x.type = BindingType::DepthTarget;
|
|
ms_depth_desc_4x.info.pixel_format = vk::Format::eD16Unorm;
|
|
ms_depth_desc_4x.info.guest_format = Prospero::BufferFormat::k16UNorm;
|
|
ms_depth_desc_4x.info.bytes_per_block = 2;
|
|
ms_depth_desc_4x.info.samples = 4;
|
|
ms_depth_desc_4x.info.type = Prospero::ImageType::kColor2D;
|
|
ms_depth_desc_4x.view_info.format = vk::Format::eD16Unorm;
|
|
ms_depth_desc_4x.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
ms_depth_desc_4x.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto ms_depth_image_4x = texture_cache.FindImage(ms_depth_desc_4x);
|
|
Require(name, "four-sample depth overlap",
|
|
ms_depth_image_4x && ms_depth_image_4x != color_image_4x &&
|
|
texture_cache.GetImage(ms_depth_image_4x).backing.samples ==
|
|
4,
|
|
"four-sample color packing did not produce a depth image");
|
|
|
|
const auto ms_depth_backing =
|
|
texture_cache.GetImage(ms_depth_image).backing.image;
|
|
const auto first_stencil_association = texture_cache.FindImageFromRange(
|
|
base + ms_stencil_offset, ms_stencil_size, false);
|
|
Require(
|
|
name, "stencil association shape",
|
|
first_stencil_association &&
|
|
first_stencil_association != ms_depth_image &&
|
|
texture_cache.GetImage(first_stencil_association)
|
|
.info.pixel_format == vk::Format::eUndefined &&
|
|
texture_cache.GetImage(first_stencil_association).backing.image ==
|
|
nullptr &&
|
|
texture_cache.GetImage(first_stencil_association).depth_id ==
|
|
ms_depth_image,
|
|
"stencil address did not create a lightweight depth association");
|
|
Require(
|
|
name, "stencil association download rejection",
|
|
!TextureCacheTestAccess::TryDownload(texture_cache,
|
|
first_stencil_association),
|
|
"a lightweight stencil association entered image download planning");
|
|
|
|
auto exact_ms_depth_alias = ms_depth_desc;
|
|
exact_ms_depth_alias.type = BindingType::Texture;
|
|
exact_ms_depth_alias.info.stencil = {};
|
|
exact_ms_depth_alias.info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
exact_ms_depth_alias.info.guest_format =
|
|
Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
exact_ms_depth_alias.info.bytes_per_block = 4;
|
|
exact_ms_depth_alias.view_info.format = vk::Format::eR8G8B8A8Unorm;
|
|
exact_ms_depth_alias.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
exact_ms_depth_alias.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto exact_ms_depth_image =
|
|
texture_cache.FindImage(exact_ms_depth_alias, true);
|
|
Require(
|
|
name, "exact multisample depth/stencil coexistence",
|
|
exact_ms_depth_image && exact_ms_depth_image != ms_depth_image &&
|
|
TextureCacheTestAccess::Contains(texture_cache, ms_depth_image) &&
|
|
texture_cache.GetImage(ms_depth_image).backing.image ==
|
|
ms_depth_backing &&
|
|
texture_cache.GetImage(first_stencil_association).depth_id ==
|
|
ms_depth_image,
|
|
"exact multisample depth/stencil lookup replaced the old image "
|
|
"or stole its stencil association");
|
|
|
|
constexpr uint64_t second_stencil_offset = 0x90000;
|
|
auto stencil_storage_desc = MakeLinearDesc(
|
|
base + second_stencil_offset, ms_stencil_size, vk::Format::eR8Uint,
|
|
Prospero::BufferFormat::k8UInt, Prospero::ImageType::kColor2D,
|
|
{256, 256, 1}, 256, 1, 1);
|
|
stencil_storage_desc.type = BindingType::Storage;
|
|
stencil_storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto stencil_storage_image =
|
|
texture_cache.FindImage(stencil_storage_desc);
|
|
(void)texture_cache.FindTexture(stencil_storage_image,
|
|
stencil_storage_desc);
|
|
auto switched_ms_depth = ms_depth_desc;
|
|
switched_ms_depth.info.stencil = {base + second_stencil_offset,
|
|
ms_stencil_size};
|
|
const auto switched_ms_depth_id =
|
|
texture_cache.FindImage(switched_ms_depth);
|
|
TextureCacheTestAccess::AssociateStencil(
|
|
texture_cache, switched_ms_depth_id, switched_ms_depth.info.stencil);
|
|
const auto second_stencil_association = texture_cache.FindImageFromRange(
|
|
base + second_stencil_offset, ms_stencil_size, false);
|
|
Require(name, "stencil association switch",
|
|
switched_ms_depth_id == ms_depth_image &&
|
|
texture_cache.GetImage(switched_ms_depth_id).backing.image ==
|
|
ms_depth_backing &&
|
|
texture_cache.FindImageFromRange(base + ms_stencil_offset,
|
|
ms_stencil_size, false) ==
|
|
first_stencil_association &&
|
|
texture_cache.GetImage(first_stencil_association).depth_id ==
|
|
ms_depth_image &&
|
|
second_stencil_association &&
|
|
second_stencil_association == stencil_storage_image &&
|
|
texture_cache.GetImage(second_stencil_association).IsTracked() &&
|
|
texture_cache.GetImage(second_stencil_association).depth_id ==
|
|
ms_depth_image,
|
|
"changing stencil address recreated depth or discarded an "
|
|
"existing lightweight association");
|
|
Require(name, "CPU write to tracked stencil association",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + second_stencil_offset) &&
|
|
!texture_cache.GetImage(second_stencil_association).IsTracked() &&
|
|
texture_cache.GetImage(second_stencil_association).IsCpuDirty() &&
|
|
texture_cache.GetImage(second_stencil_association).depth_id ==
|
|
ms_depth_image,
|
|
"CPU invalidation left a reused stencil image write-protected");
|
|
memory[second_stencil_offset] = 0x5a;
|
|
|
|
constexpr uint64_t added_stencil_depth_offset = 0x60000;
|
|
constexpr uint64_t added_stencil_offset = 0x70000;
|
|
constexpr uint64_t added_stencil_size = 0x10000;
|
|
constexpr float added_stencil_depth_value = 0.75f;
|
|
std::memcpy(memory + added_stencil_depth_offset,
|
|
&added_stencil_depth_value,
|
|
sizeof(added_stencil_depth_value));
|
|
auto depth_only_source = MakeLinearDesc(
|
|
base + added_stencil_depth_offset, 0x10000, vk::Format::eD32Sfloat,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
depth_only_source.type = BindingType::DepthTarget;
|
|
depth_only_source.view_info.format = vk::Format::eD32Sfloat;
|
|
depth_only_source.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
depth_only_source.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto depth_only_source_image =
|
|
texture_cache.FindImage(depth_only_source);
|
|
|
|
auto combined_destination = depth_only_source;
|
|
combined_destination.info.stencil = {base + added_stencil_offset,
|
|
added_stencil_size};
|
|
combined_destination.info.pixel_format = vk::Format::eD32SfloatS8Uint;
|
|
combined_destination.view_info.format = vk::Format::eD32SfloatS8Uint;
|
|
combined_destination.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
const auto combined_destination_image =
|
|
texture_cache.FindImage(combined_destination);
|
|
TextureCacheTestAccess::AssociateStencil(
|
|
texture_cache, combined_destination_image,
|
|
combined_destination.info.stencil);
|
|
const auto combined_stencil_association =
|
|
texture_cache.FindImageFromRange(base + added_stencil_offset,
|
|
added_stencil_size, false);
|
|
Require(
|
|
name, "depth-only to combined association",
|
|
combined_destination_image != depth_only_source_image &&
|
|
combined_stencil_association &&
|
|
texture_cache.GetImage(combined_stencil_association).depth_id ==
|
|
combined_destination_image &&
|
|
texture_cache.GetImage(combined_stencil_association)
|
|
.backing.image == nullptr,
|
|
"stencil addition did not recreate depth and register its "
|
|
"lightweight association");
|
|
|
|
const uint32_t mirror_value = 0xa1b2c3d4u;
|
|
std::memcpy(memory + 0x5000, &mirror_value, sizeof(mirror_value));
|
|
auto mirror_desc =
|
|
MakeLinearDesc(base + 0x5000, sizeof(mirror_value),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto mirror_image = texture_cache.FindImage(mirror_desc);
|
|
(void)texture_cache.FindTexture(mirror_image, mirror_desc);
|
|
texture_cache.MarkGpuWritten(mirror_image);
|
|
constexpr uint32_t mirror_cpu_value = 0x0f1e2d3cu;
|
|
Require(name, "CPU-dirty formatted mirror fault",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
mirror_desc.info.data.address),
|
|
"GPU image did not accept a CPU write before Buffer mirroring");
|
|
std::memcpy(memory + 0x5000, &mirror_cpu_value, sizeof(mirror_cpu_value));
|
|
auto mirror_binding = resources.GetBufferCache().ObtainBuffer(
|
|
base + 0x5000, sizeof(mirror_value), false, true);
|
|
Require(name, "CPU-dirty formatted mirror source",
|
|
mirror_binding.first != nullptr &&
|
|
!texture_cache.GetImage(mirror_image).IsBufferModified(),
|
|
"formatted mirror did not expose a readable Buffer source");
|
|
auto mirror_cpu_readback = CreateHostBuffer(
|
|
name, sizeof(mirror_cpu_value), vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>{0});
|
|
const vk::BufferCopy mirror_cpu_copy{mirror_binding.second, 0,
|
|
sizeof(mirror_cpu_value)};
|
|
command.Handle().copyBuffer(mirror_binding.first->Handle(),
|
|
mirror_cpu_readback.buffer, 1,
|
|
&mirror_cpu_copy);
|
|
HostReadBarrier(mirror_cpu_readback.buffer, mirror_cpu_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
auto mirror_refresh_desc = mirror_desc;
|
|
const auto mirror_refresh = texture_cache.FindImage(mirror_refresh_desc);
|
|
(void)texture_cache.FindTexture(mirror_refresh, mirror_refresh_desc);
|
|
Require(name, "buffer-to-image ownership",
|
|
mirror_refresh == mirror_image &&
|
|
!texture_cache.GetImage(mirror_refresh).IsBufferModified(),
|
|
"buffer-backed refresh incorrectly transferred dirty ownership "
|
|
"to the image");
|
|
texture_cache.MarkGpuWritten(mirror_refresh);
|
|
|
|
constexpr uint64_t exact_buffer_offset = 0x90000;
|
|
constexpr uint32_t exact_buffer_value = 0x3f234567u;
|
|
std::memcpy(memory + exact_buffer_offset, &exact_buffer_value,
|
|
sizeof(exact_buffer_value));
|
|
auto exact_buffer_desc =
|
|
MakeLinearDesc(base + exact_buffer_offset, sizeof(exact_buffer_value),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto exact_buffer_image =
|
|
texture_cache.FindImage(exact_buffer_desc);
|
|
(void)texture_cache.FindTexture(exact_buffer_image, exact_buffer_desc);
|
|
texture_cache.MarkGpuWritten(exact_buffer_image);
|
|
auto exact_buffer_binding = resources.GetBufferCache().ObtainBuffer(
|
|
exact_buffer_desc.info.data.address, exact_buffer_desc.info.data.size,
|
|
false, true);
|
|
Require(
|
|
name, "exact replacement Buffer synchronization",
|
|
exact_buffer_binding.first != nullptr &&
|
|
!texture_cache.GetImage(exact_buffer_image).IsBufferModified() &&
|
|
texture_cache.GetImage(exact_buffer_image).IsGpuModified(),
|
|
"exact replacement copy transferred cache ownership");
|
|
auto exact_float_desc = exact_buffer_desc;
|
|
exact_float_desc.info.pixel_format = vk::Format::eR32Sfloat;
|
|
exact_float_desc.info.guest_format = Prospero::BufferFormat::k32Float;
|
|
exact_float_desc.view_info.format = vk::Format::eR32Sfloat;
|
|
const auto exact_float_image =
|
|
texture_cache.FindImage(exact_float_desc, true);
|
|
(void)texture_cache.FindTexture(exact_float_image, exact_float_desc);
|
|
Require(
|
|
name, "Buffer-superseded exact coexistence",
|
|
exact_float_image != exact_buffer_image &&
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
exact_buffer_image) &&
|
|
texture_cache.GetImage(exact_buffer_image).IsGpuModified() &&
|
|
!texture_cache.GetImage(exact_float_image).IsBufferModified() &&
|
|
!texture_cache.GetImage(exact_float_image).IsGpuModified(),
|
|
"exact-format lookup retired its old record or transferred Buffer "
|
|
"ownership");
|
|
auto exact_buffer_readback = CreateHostBuffer(
|
|
name, sizeof(exact_buffer_value),
|
|
vk::BufferUsageFlagBits::eTransferDst, std::vector<u32>{0});
|
|
auto &exact_float_native = texture_cache.GetImage(exact_float_image);
|
|
exact_float_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
command.Handle());
|
|
vk::BufferImageCopy exact_float_copy{};
|
|
exact_float_copy.imageSubresource.aspectMask =
|
|
vk::ImageAspectFlagBits::eColor;
|
|
exact_float_copy.imageSubresource.layerCount = 1;
|
|
exact_float_copy.imageExtent = {1, 1, 1};
|
|
command.Handle().copyImageToBuffer(exact_float_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal,
|
|
exact_buffer_readback.buffer, 1,
|
|
&exact_float_copy);
|
|
HostReadBarrier(exact_buffer_readback.buffer, exact_buffer_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
const uint32_t clear_source = 0x01010101u;
|
|
std::memcpy(memory + 0x6000, &clear_source, sizeof(clear_source));
|
|
auto clear_desc = MakeLinearDesc(
|
|
base + 0x6000, sizeof(clear_source), vk::Format::eR8G8B8A8Unorm,
|
|
Prospero::BufferFormat::k8_8_8_8UNorm, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
const auto clear_image = texture_cache.FindImage(clear_desc);
|
|
auto buffer_write = resources.GetBufferCache().ObtainBuffer(
|
|
base + 0x6000, sizeof(clear_source), true, true);
|
|
texture_cache.InvalidateMemoryFromGPU(base + 0x6000,
|
|
sizeof(clear_source));
|
|
Require(name, "buffer-write ownership",
|
|
buffer_write.first != nullptr &&
|
|
texture_cache.GetImage(clear_image).IsBufferModified(),
|
|
"formatted buffer write did not supersede the image");
|
|
Require(name, "buffer-owned full image clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, base + 0x6000, sizeof(clear_source), 0xaabbccddu) &&
|
|
!texture_cache.GetImage(clear_image).IsBufferModified() &&
|
|
texture_cache.GetImage(clear_image).IsGpuModified(),
|
|
"full image clear retained dual buffer/image GPU ownership");
|
|
texture_cache.MarkGpuWritten(clear_image);
|
|
auto clear_read_desc = clear_desc;
|
|
Require(name, "post-clear image reuse",
|
|
texture_cache.FindImage(clear_read_desc) == clear_image,
|
|
"cleared image could not be read or written after ownership "
|
|
"transfer");
|
|
|
|
auto stencil_clear_desc = MakeLinearDesc(
|
|
base + 0x27e0000, 0x1000, vk::Format::eD32SfloatS8Uint,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{13, 11, 1}, 2, 4, 1);
|
|
stencil_clear_desc.info.stencil = {base + 0x27e2000, 0x1000};
|
|
stencil_clear_desc.info.resources.levels = 2;
|
|
stencil_clear_desc.info.mip_layout[0] = {0, 0x800, 13, 11};
|
|
stencil_clear_desc.info.mip_layout[1] = {0x800, 0x800, 6, 5};
|
|
stencil_clear_desc.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
const auto stencil_clear_id = texture_cache.FindImage(stencil_clear_desc);
|
|
auto &stencil_clear_image = texture_cache.GetImage(stencil_clear_id);
|
|
stencil_clear_image.Transit(vk::ImageLayout::eTransferDstOptimal,
|
|
vk::AccessFlagBits2::eTransferWrite, {}, command.Handle());
|
|
const vk::ClearDepthStencilValue initial_depth_stencil{0.75f, 0x91};
|
|
const vk::ImageSubresourceRange all_depth_stencil{
|
|
stencil_clear_desc.view_info.aspect, 0, 2, 0, 2};
|
|
command.Handle().clearDepthStencilImage(stencil_clear_image.backing.image,
|
|
vk::ImageLayout::eTransferDstOptimal, &initial_depth_stencil, 1,
|
|
&all_depth_stencil);
|
|
texture_cache.MarkGpuWritten(stencil_clear_id);
|
|
vk::ClearValue stencil_value{};
|
|
stencil_value.depthStencil = vk::ClearDepthStencilValue{0.f, 0x35};
|
|
TextureCacheTestAccess::ClearImage(texture_cache, command, stencil_clear_id,
|
|
{vk::ImageAspectFlagBits::eStencil, 1, 1, 1, 1}, stencil_value);
|
|
|
|
// Read every mip/layer of both aspects to prove that the stencil clear is local.
|
|
auto stencil_readback = CreateHostBuffer(name, 0x4000,
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
std::vector<vk::BufferImageCopy> stencil_copies;
|
|
for (uint32_t mip = 0; mip < 2; mip++) {
|
|
for (uint32_t layer = 0; layer < 2; layer++) {
|
|
for (const auto aspect : {vk::ImageAspectFlagBits::eDepth,
|
|
vk::ImageAspectFlagBits::eStencil}) {
|
|
vk::BufferImageCopy copy{};
|
|
copy.bufferOffset = stencil_copies.size() * 0x800;
|
|
copy.imageSubresource = {aspect, mip, layer, 1};
|
|
copy.imageExtent = {13u >> mip, 11u >> mip, 1};
|
|
stencil_copies.push_back(copy);
|
|
}
|
|
}
|
|
}
|
|
stencil_clear_image.Download(stencil_copies, stencil_readback.buffer, 0,
|
|
stencil_readback.size);
|
|
HostReadBarrier(stencil_readback.buffer, stencil_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
const auto stencil_words = ReadBuffer(name, stencil_readback, 0x1000);
|
|
const auto *stencil_bytes = reinterpret_cast<const uint8_t *>(stencil_words.data());
|
|
for (const auto © : stencil_copies) {
|
|
const auto &sub = copy.imageSubresource;
|
|
const auto count = copy.imageExtent.width * copy.imageExtent.height;
|
|
const auto *bytes = stencil_bytes + copy.bufferOffset;
|
|
const bool depth = sub.aspectMask == vk::ImageAspectFlagBits::eDepth;
|
|
uint32_t expected = 0x91u;
|
|
if (depth) {
|
|
expected = std::bit_cast<uint32_t>(0.75f);
|
|
} else if (sub.mipLevel == 1 && sub.baseArrayLayer == 1) {
|
|
expected = 0x35u;
|
|
}
|
|
for (uint32_t pixel = 0; pixel < count; pixel++) {
|
|
uint32_t actual = bytes[pixel];
|
|
if (depth) std::memcpy(&actual, bytes + pixel * 4, 4);
|
|
Require(name, "stencil clear preserves depth and neighbors", actual == expected,
|
|
"selected stencil clear changed depth, another mip, or another layer");
|
|
}
|
|
}
|
|
DestroyBuffer(&stencil_readback);
|
|
|
|
constexpr uint64_t partial_image_offset = 0xa000;
|
|
constexpr uint64_t partial_buffer_offset = 0xa010;
|
|
constexpr uint64_t partial_clean_offset = 0xa020;
|
|
constexpr uint32_t partial_image_value = 0x31415926u;
|
|
constexpr uint32_t partial_buffer_value = 0x27182818u;
|
|
constexpr uint32_t partial_clean_value = 0xabcdef01u;
|
|
std::memcpy(memory + partial_clean_offset, &partial_clean_value,
|
|
sizeof(partial_clean_value));
|
|
auto partial_write = resources.GetBufferCache().ObtainBuffer(
|
|
base + partial_image_offset, sizeof(partial_image_value), true);
|
|
Require(name, "partial-page buffer allocation",
|
|
partial_write.first != nullptr,
|
|
"partial-page GPU write did not create a native buffer");
|
|
resources.GetBufferCache().FillBuffer(base + partial_image_offset,
|
|
sizeof(partial_image_value),
|
|
partial_image_value, false);
|
|
resources.GetBufferCache().FillBuffer(base + partial_buffer_offset,
|
|
sizeof(partial_buffer_value),
|
|
partial_buffer_value, false);
|
|
auto partial_desc = MakeLinearDesc(
|
|
base + partial_image_offset, sizeof(partial_image_value),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto partial_image = texture_cache.FindImage(partial_desc);
|
|
Require(name, "partial-page image upload",
|
|
!texture_cache.GetImage(partial_image).IsGpuModified(),
|
|
"image upload incorrectly consumed buffer dirty ownership");
|
|
auto partial_image_mirror = resources.GetBufferCache().ObtainBuffer(
|
|
base + partial_image_offset, sizeof(partial_image_value), false,
|
|
true);
|
|
Require(name, "partial-page image mirror",
|
|
partial_image_mirror.first != nullptr &&
|
|
!texture_cache.GetImage(partial_image).IsGpuModified(),
|
|
"same-page image upload lost its buffer source");
|
|
const auto partial_clean_source =
|
|
resources.GetBufferCache().ObtainBufferForImage(
|
|
base + partial_clean_offset, sizeof(partial_clean_value));
|
|
Require(name, "partial-page clean source",
|
|
partial_clean_source.first != nullptr,
|
|
"clean same-page bytes inherited unrelated buffer ownership");
|
|
Require(name, "partial-page remaining fault",
|
|
resources.HandleFault(PageFaultAccess::Read,
|
|
base + partial_buffer_offset),
|
|
"same-page buffer ownership was lost after image transfer");
|
|
uint32_t partial_buffer_backing = 0;
|
|
std::memcpy(&partial_buffer_backing, memory + partial_buffer_offset,
|
|
sizeof(partial_buffer_backing));
|
|
uint32_t partial_image_backing = 0;
|
|
std::memcpy(&partial_image_backing, memory + partial_image_offset,
|
|
sizeof(partial_image_backing));
|
|
Require(name, "partial-page readback values",
|
|
partial_image_backing == partial_image_value &&
|
|
partial_buffer_backing == partial_buffer_value,
|
|
"same-page image/buffer readback published incorrect bytes");
|
|
constexpr uint32_t partial_cpu_refresh_value = 0x13579bdfu;
|
|
Require(name, "partial-page CPU write fault",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + partial_clean_offset),
|
|
"same-page CPU write did not invalidate cached ownership");
|
|
std::memcpy(memory + partial_clean_offset, &partial_cpu_refresh_value,
|
|
sizeof(partial_cpu_refresh_value));
|
|
constexpr std::array<uint32_t, 2> partial_neighbors{0x2468ace0u,
|
|
0xfedcba98u};
|
|
std::memcpy(memory + partial_clean_offset - sizeof(uint32_t),
|
|
&partial_neighbors[0], sizeof(uint32_t));
|
|
std::memcpy(memory + partial_clean_offset + sizeof(uint32_t),
|
|
&partial_neighbors[1], sizeof(uint32_t));
|
|
std::vector<u32> partial_cpu_refresh_expected(TRACKER_PAGE_SIZE / sizeof(u32));
|
|
std::memcpy(partial_cpu_refresh_expected.data(), memory + partial_image_offset,
|
|
TRACKER_PAGE_SIZE);
|
|
const auto partial_cpu_refresh_source =
|
|
resources.GetBufferCache().ObtainBufferForImage(
|
|
base + partial_clean_offset, sizeof(partial_cpu_refresh_value));
|
|
Require(name, "partial-page CPU refresh source",
|
|
partial_cpu_refresh_source.first != nullptr,
|
|
"CPU-dirty same-page bytes did not resolve through the cached "
|
|
"buffer");
|
|
auto partial_cpu_refresh_readback =
|
|
CreateHostBuffer(name, TRACKER_PAGE_SIZE,
|
|
vk::BufferUsageFlagBits::eTransferDst, {0});
|
|
const vk::BufferCopy partial_cpu_refresh_copy{
|
|
partial_cpu_refresh_source.first->Offset(base + partial_image_offset), 0,
|
|
TRACKER_PAGE_SIZE};
|
|
command.Handle().copyBuffer(partial_cpu_refresh_source.first->Handle(),
|
|
partial_cpu_refresh_readback.buffer, 1,
|
|
&partial_cpu_refresh_copy);
|
|
vk::BufferMemoryBarrier partial_cpu_refresh_barrier{};
|
|
partial_cpu_refresh_barrier.sType =
|
|
vk::StructureType::eBufferMemoryBarrier;
|
|
partial_cpu_refresh_barrier.srcAccessMask =
|
|
vk::AccessFlagBits::eTransferWrite;
|
|
partial_cpu_refresh_barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
partial_cpu_refresh_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
partial_cpu_refresh_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
partial_cpu_refresh_barrier.buffer = partial_cpu_refresh_readback.buffer;
|
|
partial_cpu_refresh_barrier.size = partial_cpu_refresh_readback.size;
|
|
command.Handle().pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0,
|
|
nullptr, 1, &partial_cpu_refresh_barrier,
|
|
0, nullptr);
|
|
|
|
const uint32_t fault_a = 0x01020304u;
|
|
const uint32_t fault_b = 0x11121314u;
|
|
std::memcpy(memory + 0x8000, &fault_a, sizeof(fault_a));
|
|
std::memcpy(memory + 0x8010, &fault_b, sizeof(fault_b));
|
|
auto fault_a_desc =
|
|
MakeLinearDesc(base + 0x8000, sizeof(fault_a), vk::Format::eR32Uint,
|
|
Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
auto fault_b_desc = fault_a_desc;
|
|
fault_b_desc.info.data.address = base + 0x8010;
|
|
const auto fault_a_image = texture_cache.FindImage(fault_a_desc);
|
|
const auto fault_b_image = texture_cache.FindImage(fault_b_desc);
|
|
(void)texture_cache.FindTexture(fault_a_image, fault_a_desc);
|
|
(void)texture_cache.FindTexture(fault_b_image, fault_b_desc);
|
|
texture_cache.MarkGpuWritten(fault_a_image);
|
|
texture_cache.MarkGpuWritten(fault_b_image);
|
|
Require(name, "per-image watcher installation",
|
|
texture_cache.GetImage(fault_a_image).IsTracked() &&
|
|
texture_cache.GetImage(fault_b_image).IsTracked(),
|
|
"same-page images did not install independent write watchers");
|
|
constexpr uint64_t padding_fault_offset = 0x8080;
|
|
uint32_t write_only_read_a = 0;
|
|
uint32_t write_only_read_b = 0;
|
|
std::memcpy(&write_only_read_a, memory + 0x8000,
|
|
sizeof(write_only_read_a));
|
|
std::memcpy(&write_only_read_b, memory + 0x8010,
|
|
sizeof(write_only_read_b));
|
|
Require(name, "write-only image read policy",
|
|
write_only_read_a == fault_a && write_only_read_b == fault_b &&
|
|
texture_cache.GetImage(fault_a_image).IsGpuModified() &&
|
|
texture_cache.GetImage(fault_b_image).IsGpuModified(),
|
|
"TextureCache claimed a CPU read fault instead of preserving "
|
|
"GPU image ownership");
|
|
Require(name, "same-page image write invalidation",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + padding_fault_offset) &&
|
|
texture_cache.GetImage(fault_a_image).IsGpuModified() &&
|
|
texture_cache.GetImage(fault_b_image).IsGpuModified() &&
|
|
!texture_cache.GetImage(fault_a_image).IsTracked() &&
|
|
!texture_cache.GetImage(fault_b_image).IsTracked() &&
|
|
texture_cache.GetImage(fault_a_image).IsMaybeCpuDirty() &&
|
|
texture_cache.GetImage(fault_b_image).IsMaybeCpuDirty(),
|
|
"a byte-disjoint CPU write discarded authoritative images");
|
|
const auto retracked_a = texture_cache.FindImage(fault_a_desc);
|
|
const auto retracked_b = texture_cache.FindImage(fault_b_desc);
|
|
(void)texture_cache.FindTexture(retracked_a, fault_a_desc);
|
|
(void)texture_cache.FindTexture(retracked_b, fault_b_desc);
|
|
auto fault_a_mirror = resources.GetBufferCache().ObtainBuffer(
|
|
base + 0x8000, sizeof(fault_a), false, true);
|
|
Require(name, "same-page image re-track",
|
|
retracked_a == fault_a_image && retracked_b == fault_b_image &&
|
|
texture_cache.GetImage(fault_a_image).IsTracked() &&
|
|
texture_cache.GetImage(fault_b_image).IsTracked() &&
|
|
!texture_cache.GetImage(fault_a_image).IsCpuDirty() &&
|
|
!texture_cache.GetImage(fault_b_image).IsCpuDirty() &&
|
|
fault_a_mirror.first != nullptr &&
|
|
texture_cache.GetImage(fault_a_image).IsTracked() &&
|
|
texture_cache.GetImage(fault_b_image).IsTracked() &&
|
|
texture_cache.GetImage(fault_a_image).IsGpuModified() &&
|
|
texture_cache.GetImage(fault_b_image).IsGpuModified(),
|
|
"copying one same-page image lost an authoritative owner");
|
|
Require(name, "same-page survivor write fault",
|
|
resources.HandleFault(PageFaultAccess::Write, base + 0x8010) &&
|
|
texture_cache.GetImage(fault_b_image).IsGpuModified() &&
|
|
texture_cache.GetImage(fault_b_image).IsDefinitelyCpuDirty(),
|
|
"the surviving image was not protected after its alias retired");
|
|
constexpr uint32_t fault_b_cpu = 0xa5a6a7a8u;
|
|
std::memcpy(memory + 0x8010, &fault_b_cpu, sizeof(fault_b_cpu));
|
|
const auto refreshed_b = texture_cache.FindImage(fault_b_desc);
|
|
(void)texture_cache.FindTexture(refreshed_b, fault_b_desc);
|
|
Require(
|
|
name, "same-page survivor refresh",
|
|
refreshed_b == fault_b_image &&
|
|
!texture_cache.GetImage(fault_b_image).IsDefinitelyCpuDirty() &&
|
|
texture_cache.GetImage(fault_b_image).IsGpuModified(),
|
|
"the surviving image could not reconcile its CPU write");
|
|
|
|
constexpr uint64_t publish_image_offset = 0x26000;
|
|
constexpr uint64_t publish_buffer_offset = 0x26010;
|
|
constexpr uint32_t publish_image_value = 0x31415926u;
|
|
constexpr uint32_t publish_buffer_value = 0x27182818u;
|
|
std::memcpy(memory + publish_image_offset, &publish_image_value,
|
|
sizeof(publish_image_value));
|
|
auto publish_image_desc = MakeLinearDesc(
|
|
base + publish_image_offset, sizeof(publish_image_value),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto publish_image = texture_cache.FindImage(publish_image_desc);
|
|
(void)texture_cache.FindTexture(publish_image, publish_image_desc);
|
|
texture_cache.MarkGpuWritten(publish_image);
|
|
resources.GetBufferCache().FillBuffer(base + publish_buffer_offset,
|
|
sizeof(publish_buffer_value),
|
|
publish_buffer_value, false);
|
|
auto publish_replacement_desc = publish_image_desc;
|
|
publish_replacement_desc.info.pixel_format = vk::Format::eR32Sfloat;
|
|
publish_replacement_desc.info.guest_format =
|
|
Prospero::BufferFormat::k32Float;
|
|
publish_replacement_desc.view_info.format = vk::Format::eR32Sfloat;
|
|
const auto publish_replacement =
|
|
texture_cache.FindImage(publish_replacement_desc, true);
|
|
Require(name, "exact-disjoint image replacement",
|
|
publish_replacement && publish_replacement != publish_image,
|
|
"exact-format lookup did not replace the image");
|
|
Require(name, "exact-disjoint image publication",
|
|
!resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + publish_buffer_offset, sizeof(publish_buffer_value)),
|
|
"clean neighboring buffer unexpectedly became GPU-owned");
|
|
uint32_t published_image_backing = 0;
|
|
uint32_t published_buffer_backing = 0;
|
|
std::memcpy(&published_image_backing, memory + publish_image_offset,
|
|
sizeof(published_image_backing));
|
|
std::memcpy(&published_buffer_backing, memory + publish_buffer_offset,
|
|
sizeof(published_buffer_backing));
|
|
Require(name, "exact-disjoint publication contents",
|
|
published_image_backing == publish_image_value &&
|
|
published_buffer_backing == publish_buffer_value,
|
|
"image publication or neighboring buffer readback changed bytes");
|
|
|
|
for (const auto [offset, samples] :
|
|
std::array<std::pair<uint64_t, uint32_t>, 2>{
|
|
{{0x10000, 2}, {0x11000, 4}}}) {
|
|
std::memset(memory + offset, 0, samples * sizeof(uint16_t));
|
|
auto standalone_ms = MakeLinearDesc(
|
|
base + offset, samples * sizeof(uint16_t), vk::Format::eD16Unorm,
|
|
Prospero::BufferFormat::k16UNorm, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 2, samples);
|
|
standalone_ms.type = BindingType::DepthTarget;
|
|
standalone_ms.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
standalone_ms.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto image = texture_cache.FindImage(standalone_ms);
|
|
(void)texture_cache.FindDepthTarget(image, standalone_ms);
|
|
texture_cache.MarkGpuWritten(image);
|
|
uint16_t standalone_cpu_read = 1;
|
|
std::memcpy(&standalone_cpu_read, memory + offset,
|
|
sizeof(standalone_cpu_read));
|
|
Require(
|
|
name, "standalone MSAA ownership",
|
|
texture_cache.GetImage(image).IsGpuModified() &&
|
|
standalone_cpu_read == 0 &&
|
|
texture_cache.GetImage(image).IsGpuModified() &&
|
|
resources.HandleFault(PageFaultAccess::Write, base + offset) &&
|
|
texture_cache.GetImage(image).IsGpuModified() &&
|
|
texture_cache.GetImage(image).IsDefinitelyCpuDirty(),
|
|
"fresh multisample target violated write-only ownership");
|
|
}
|
|
|
|
constexpr uint64_t metadata_data_a = 0x12000;
|
|
constexpr uint64_t metadata_data_b = 0x12100;
|
|
constexpr uint64_t metadata_a = 0x13000;
|
|
constexpr uint64_t metadata_b = 0x13100;
|
|
auto MakeMetadataDepth = [&](uint64_t data, uint64_t metadata) {
|
|
auto desc = MakeLinearDesc(
|
|
base + data, sizeof(uint32_t), vk::Format::eD32Sfloat,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
desc.type = BindingType::DepthTarget;
|
|
desc.info.metadata.kind = ImageMetadataKind::Htile;
|
|
desc.info.metadata.range = {base + metadata, 0x80};
|
|
desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
return desc;
|
|
};
|
|
auto metadata_depth_a = MakeMetadataDepth(metadata_data_a, metadata_a);
|
|
auto metadata_depth_b = MakeMetadataDepth(metadata_data_b, metadata_b);
|
|
metadata_depth_a.info.htile_clear_mask = 0;
|
|
const auto metadata_depth_a_id =
|
|
texture_cache.FindImage(metadata_depth_a);
|
|
const auto metadata_depth_b_id =
|
|
texture_cache.FindImage(metadata_depth_b);
|
|
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
|
|
Require(
|
|
name, "pending DCC/HTile separation",
|
|
!texture_cache.IsMeta(base + metadata_a),
|
|
"an unclassified PS5 DCC fill was exposed as registered metadata");
|
|
const auto metadata_depth_a_view =
|
|
texture_cache.FindDepthTarget(metadata_depth_a_id, metadata_depth_a);
|
|
const auto metadata_depth_b_view =
|
|
texture_cache.FindDepthTarget(metadata_depth_b_id, metadata_depth_b);
|
|
Require(name, "shared-page metadata state",
|
|
metadata_depth_a_view != nullptr &&
|
|
metadata_depth_b_view != nullptr &&
|
|
!texture_cache.IsMetaCleared(base + metadata_a, 0) &&
|
|
texture_cache.ClearMeta(base + metadata_a) &&
|
|
texture_cache.ClearMeta(base + metadata_b),
|
|
"shared-page metadata did not retain logical clear state");
|
|
auto metadata_alias = MakeLinearDesc(
|
|
base + metadata_a, sizeof(uint32_t), vk::Format::eR32Sfloat,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
metadata_alias.type = BindingType::RenderTarget;
|
|
metadata_alias.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto metadata_alias_id =
|
|
texture_cache.FindImage(metadata_alias, true);
|
|
const auto metadata_alias_view =
|
|
texture_cache.FindRenderTarget(metadata_alias_id, metadata_alias);
|
|
Require(
|
|
name, "exact alias metadata coexistence",
|
|
metadata_alias_view != nullptr &&
|
|
texture_cache.GetImage(metadata_depth_a_id).IsGpuModified() &&
|
|
texture_cache.GetImage(metadata_depth_b_id).IsGpuModified() &&
|
|
texture_cache.IsMeta(base + metadata_a) &&
|
|
texture_cache.IsMetaCleared(base + metadata_a, 0) &&
|
|
texture_cache.IsMeta(base + metadata_b) &&
|
|
texture_cache.IsMetaCleared(base + metadata_b, 0),
|
|
"image discovery retired aliased metadata or its live depth image");
|
|
const bool touched_metadata = texture_cache.TouchMeta(base + metadata_a, 0, false);
|
|
texture_cache.TrackDccFill(base + metadata_a, 0x80, 0);
|
|
Require(
|
|
name, "registered DCC/HTile separation",
|
|
touched_metadata && !texture_cache.IsMetaCleared(base + metadata_a, 0),
|
|
"a PS5 DCC fill changed registered HTile state");
|
|
Require(
|
|
name, "metadata first-touch state",
|
|
texture_cache.TouchMeta(base + metadata_b, 0, true) &&
|
|
texture_cache.IsMetaCleared(base + metadata_b, 0) &&
|
|
texture_cache.TouchMeta(base + metadata_b, 0, false) &&
|
|
!texture_cache.IsMetaCleared(base + metadata_b, 0),
|
|
"per-slice metadata update incorrectly required prior GPU ownership");
|
|
resources.GetBufferCache().FillBuffer(base + metadata_b, sizeof(uint32_t),
|
|
0, false);
|
|
Require(
|
|
name, "metadata buffer fill state",
|
|
texture_cache.IsMetaCleared(base + metadata_b, 0),
|
|
"BufferCache fill did not publish an exact-address metadata clear");
|
|
|
|
// R-Type Final 3 reuses an old color allocation inside a new stencil plane,
|
|
// with the same metadata address changing from DCC to HTile.
|
|
auto reused_depth = MakeMetadataDepth(0x12300, 0x13200);
|
|
reused_depth.info.pixel_format = vk::Format::eD32SfloatS8Uint;
|
|
reused_depth.info.stencil = {base + 0x12200, 0x80};
|
|
reused_depth.info.htile_clear_mask = 0;
|
|
reused_depth.view_info.format = vk::Format::eD32SfloatS8Uint;
|
|
reused_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth |
|
|
vk::ImageAspectFlagBits::eStencil;
|
|
auto reused_color = MakeLinearDesc(
|
|
base + 0x12240, sizeof(uint32_t), vk::Format::eR8G8B8A8Unorm,
|
|
Prospero::BufferFormat::k8_8_8_8UNorm, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
reused_color.type = BindingType::RenderTarget;
|
|
reused_color.info.metadata.kind = ImageMetadataKind::Dcc;
|
|
reused_color.info.metadata.range = {base + 0x13200, 0x20};
|
|
reused_color.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto reused_color_id = texture_cache.FindImage(reused_color);
|
|
(void)texture_cache.FindRenderTarget(reused_color_id, reused_color);
|
|
texture_cache.TrackDccFill(base + 0x13200, 0x80, 0);
|
|
texture_cache.InvalidateMemoryFromGPU(reused_depth.info.stencil.address,
|
|
reused_depth.info.stencil.size);
|
|
const auto reused_depth_id = texture_cache.FindImage(reused_depth);
|
|
(void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth);
|
|
uint32_t reused_fill = 0;
|
|
Require(
|
|
name, "DCC allocation reused as HTile",
|
|
TextureCacheTestAccess::Contains(texture_cache, reused_color_id) &&
|
|
texture_cache.GetImage(reused_color_id).IsBufferModified() &&
|
|
!texture_cache.GetImage(reused_color_id).IsGpuModified() &&
|
|
!texture_cache.IsMetaCleared(base + 0x13200, 0, &reused_fill) &&
|
|
reused_fill == UINT32_MAX &&
|
|
texture_cache.ClearMeta(base + 0x13200) &&
|
|
texture_cache.TouchMeta(base + 0x13200, 0, false),
|
|
"depth binding retained incompatible DCC clear state");
|
|
TextureCacheTestAccess::DeleteImage(texture_cache, reused_color_id);
|
|
(void)texture_cache.FindDepthTarget(reused_depth_id, reused_depth);
|
|
Require(
|
|
name, "reused metadata owner retirement",
|
|
!TextureCacheTestAccess::Contains(texture_cache, reused_color_id) &&
|
|
texture_cache.IsMeta(base + 0x13200) &&
|
|
!texture_cache.IsMetaCleared(base + 0x13200, 0) &&
|
|
texture_cache.IsMetaCleared(base + 0x13200, 1),
|
|
"retiring the old DCC image erased the live HTile slice state");
|
|
texture_cache.UnmapMemory(reused_depth.info.data.address,
|
|
reused_depth.info.data.size);
|
|
Require(name, "reused metadata final retirement",
|
|
!texture_cache.IsMeta(base + 0x13200),
|
|
"retiring the HTile image left its metadata registered");
|
|
|
|
constexpr uint64_t partial_unmap_image_offset = 0x2700000;
|
|
auto partial_unmap_image =
|
|
MakeLinearDesc(base + partial_unmap_image_offset, 0x2000,
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {2048, 1, 1}, 1, 4, 1);
|
|
const auto partial_unmap_image_id =
|
|
texture_cache.FindImage(partial_unmap_image);
|
|
texture_cache.UnmapMemory(partial_unmap_image.info.data.address, 0x1000);
|
|
Require(name, "partial image unmap tracking",
|
|
partial_unmap_image_id &&
|
|
!texture_cache.FindImageFromRange(
|
|
partial_unmap_image.info.data.address, 0x2000, false),
|
|
"partial unmap left the deleted image's mapped tail tracked");
|
|
|
|
constexpr uint64_t unformatted_alias_offset = 0x2500000;
|
|
auto unformatted_alias =
|
|
MakeLinearDesc(base + unformatted_alias_offset, sizeof(uint32_t),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto unformatted_alias_image =
|
|
texture_cache.FindImage(unformatted_alias);
|
|
(void)texture_cache.FindTexture(unformatted_alias_image,
|
|
unformatted_alias);
|
|
texture_cache.MarkGpuWritten(unformatted_alias_image);
|
|
auto unformatted_alias_buffer = resources.GetBufferCache().ObtainBuffer(
|
|
unformatted_alias.info.data.address, unformatted_alias.info.data.size,
|
|
false);
|
|
Require(
|
|
name, "unformatted buffer/image alias",
|
|
unformatted_alias_buffer.first != nullptr &&
|
|
texture_cache.GetImage(unformatted_alias_image).IsGpuModified() &&
|
|
!texture_cache.GetImage(unformatted_alias_image)
|
|
.IsBufferModified(),
|
|
"read-only unformatted buffer alias did not follow ordinary "
|
|
"BufferCache acquisition");
|
|
|
|
constexpr uint64_t layered_offset = 0x14000;
|
|
constexpr uint64_t layered_guest_size = 0x800;
|
|
const std::array<float, 10> layered_values{0.0f, 0.125f, 0.25f, 0.375f,
|
|
0.5f, 0.625f, 0.75f, 0.875f,
|
|
1.0f, 0.0625f};
|
|
std::memset(memory + layered_offset, 0, layered_guest_size);
|
|
const auto layered_layout = TextureCalcUploadLayout(
|
|
Prospero::BufferFormat::k32Float, 2, 2, 2, 2,
|
|
Prospero::TileMode::kLinear, layered_guest_size, true, false,
|
|
"UnifiedTextureCacheFlow");
|
|
const auto layered_upload_regions =
|
|
TextureBuildImageCopies(layered_layout);
|
|
Require(name, "layered guest layout", layered_upload_regions.size() == 4,
|
|
"unexpected layered/mipped upload-region count");
|
|
for (const auto ®ion : layered_upload_regions) {
|
|
for (uint32_t y = 0; y < region.imageExtent.height; y++) {
|
|
for (uint32_t x = 0; x < region.imageExtent.width; x++) {
|
|
const uint32_t logical =
|
|
region.imageSubresource.mipLevel == 0
|
|
? region.imageSubresource.baseArrayLayer * 4 + y * 2 + x
|
|
: 8 + region.imageSubresource.baseArrayLayer;
|
|
const uint64_t byte_offset =
|
|
region.bufferOffset +
|
|
(static_cast<uint64_t>(y) * region.bufferRowLength + x) *
|
|
sizeof(float);
|
|
Require(name, "layered guest layout bounds",
|
|
byte_offset + sizeof(float) <= layered_guest_size,
|
|
"layered guest texel lies outside its backing range");
|
|
std::memcpy(memory + layered_offset + byte_offset,
|
|
&layered_values[logical], sizeof(float));
|
|
}
|
|
}
|
|
}
|
|
auto layered_color = MakeLinearDesc(
|
|
base + layered_offset, layered_guest_size, vk::Format::eR32Sfloat,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{2, 2, 1}, 2, 4, 1);
|
|
layered_color.info.resources.levels = 2;
|
|
layered_color.info.mip_layout[0] = {0, 32, 2, 2};
|
|
layered_color.info.mip_layout[1] = {32, 8, 1, 1};
|
|
layered_color.view_info.level_count = 2;
|
|
const auto layered_color_image = texture_cache.FindImage(layered_color);
|
|
(void)texture_cache.FindTexture(layered_color_image, layered_color);
|
|
texture_cache.MarkGpuWritten(layered_color_image);
|
|
auto layered_depth = layered_color;
|
|
layered_depth.type = BindingType::DepthTarget;
|
|
layered_depth.info.resources = {1, 4};
|
|
layered_depth.view_info.level_count = 1;
|
|
layered_depth.info.pixel_format = vk::Format::eD32Sfloat;
|
|
layered_depth.view_info.format = vk::Format::eD32Sfloat;
|
|
layered_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
layered_depth.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto layered_depth_image = texture_cache.FindImage(layered_depth);
|
|
const auto &layered_native = texture_cache.GetImage(layered_depth_image);
|
|
Require(name, "layered mipped depth alias",
|
|
layered_depth_image != layered_color_image &&
|
|
layered_native.backing.layers == 2 &&
|
|
layered_native.backing.mip_levels == 2 &&
|
|
layered_native.info.resources ==
|
|
layered_color.info.resources &&
|
|
layered_native.IsGpuModified(),
|
|
"depth/color conversion did not use the lexicographic "
|
|
"resource maximum");
|
|
const volatile auto layered_guest_byte =
|
|
*reinterpret_cast<const volatile uint8_t *>(memory + layered_offset);
|
|
(void)layered_guest_byte;
|
|
Require(name, "layered depth write-only read policy",
|
|
texture_cache.GetImage(layered_depth_image).IsGpuModified(),
|
|
"a CPU read invalidated layered depth GPU ownership");
|
|
|
|
constexpr uint64_t replacement_offset = 0x18000;
|
|
constexpr uint64_t replacement_size = 0x2000;
|
|
constexpr uint32_t replacement_pixel = 0xcafebabeu;
|
|
constexpr uint32_t replacement_tail = 0x0badf00du;
|
|
std::memset(memory + replacement_offset, 0, replacement_size);
|
|
std::memcpy(memory + replacement_offset, &replacement_pixel,
|
|
sizeof(replacement_pixel));
|
|
std::memcpy(memory + replacement_offset + replacement_size -
|
|
sizeof(replacement_tail),
|
|
&replacement_tail, sizeof(replacement_tail));
|
|
auto replacement_source =
|
|
MakeLinearDesc(base + replacement_offset, sizeof(uint32_t),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto replacement_source_image =
|
|
texture_cache.FindImage(replacement_source);
|
|
(void)texture_cache.FindTexture(replacement_source_image,
|
|
replacement_source);
|
|
texture_cache.MarkGpuWritten(replacement_source_image);
|
|
auto replacement_desc =
|
|
MakeLinearDesc(base + replacement_offset, replacement_size,
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 2, 4, 1);
|
|
const auto replacement = texture_cache.FindImage(replacement_desc);
|
|
const bool replacement_changed = replacement != replacement_source_image;
|
|
const bool replacement_gpu_before =
|
|
texture_cache.GetImage(replacement).IsGpuModified();
|
|
const bool replacement_fault = resources.HandleFault(
|
|
PageFaultAccess::Write, base + replacement_offset + 0x1000);
|
|
const bool replacement_gpu_after =
|
|
texture_cache.GetImage(replacement).IsGpuModified();
|
|
uint32_t replacement_pixel_after = 0;
|
|
uint32_t replacement_tail_after = 0;
|
|
std::memcpy(&replacement_pixel_after, memory + replacement_offset,
|
|
sizeof(replacement_pixel_after));
|
|
std::memcpy(&replacement_tail_after,
|
|
memory + replacement_offset + replacement_size -
|
|
sizeof(replacement_tail_after),
|
|
sizeof(replacement_tail_after));
|
|
Require(name, "replacement tracking",
|
|
replacement_changed && replacement_gpu_before &&
|
|
replacement_fault && replacement_gpu_after &&
|
|
texture_cache.GetImage(replacement).IsDefinitelyCpuDirty() &&
|
|
replacement_pixel_after == replacement_pixel &&
|
|
replacement_tail_after == replacement_tail,
|
|
"replacement write invalidation lost native authority or guest "
|
|
"backing");
|
|
|
|
const uint32_t compressed_value = 0x55667788u;
|
|
std::memcpy(memory + 0xc000, &compressed_value, sizeof(compressed_value));
|
|
auto compressed_desc = MakeLinearDesc(
|
|
base + 0xc000, sizeof(compressed_value), vk::Format::eR8G8B8A8Srgb,
|
|
Prospero::BufferFormat::k8_8_8_8Srgb, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
compressed_desc.type = BindingType::RenderTarget;
|
|
compressed_desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
|
compressed_desc.info.metadata.range = {base + 0xd000, 0};
|
|
compressed_desc.info.metadata.compression =
|
|
VideoOutCompression::Dcc256_256_0;
|
|
compressed_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto compressed_image = texture_cache.FindImage(compressed_desc);
|
|
(void)texture_cache.FindRenderTarget(compressed_image, compressed_desc);
|
|
uint32_t compressed_cpu_read = 0;
|
|
std::memcpy(&compressed_cpu_read, memory + 0xc000,
|
|
sizeof(compressed_cpu_read));
|
|
Require(name, "compressed write-only read policy",
|
|
compressed_cpu_read == compressed_value &&
|
|
texture_cache.GetImage(compressed_image).IsGpuModified(),
|
|
"compressed image incorrectly claimed a CPU read fault");
|
|
Require(name, "compressed download rejection",
|
|
!TextureCacheTestAccess::TryDownload(texture_cache,
|
|
compressed_image) &&
|
|
texture_cache.GetImage(compressed_image).IsGpuModified() &&
|
|
!texture_cache.GetImage(compressed_image).IsBufferModified(),
|
|
"a compressed image escaped the unified download guard");
|
|
auto compressed_video_desc = compressed_desc;
|
|
compressed_video_desc.type = BindingType::VideoOut;
|
|
compressed_video_desc.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto compressed_video =
|
|
texture_cache.FindImage(compressed_video_desc);
|
|
const auto &compressed_video_image =
|
|
texture_cache.GetImage(compressed_video);
|
|
Require(
|
|
name, "cache-only video-out discovery",
|
|
compressed_video == compressed_image &&
|
|
!compressed_video_image.binding.is_bound &&
|
|
!compressed_video_image.binding.is_target &&
|
|
!compressed_video_image.binding.needs_rebind &&
|
|
!compressed_video_image.usage.video_out,
|
|
"FindImage claimed caller-owned video-out usage or binding state");
|
|
|
|
constexpr uint64_t mixed_source_offset = 0x20000;
|
|
constexpr uint64_t mixed_source_size = 0x1004;
|
|
constexpr uint32_t mixed_source_width = 1025;
|
|
constexpr uint32_t mixed_cpu_value = 0x1234abcdu;
|
|
constexpr uint32_t mixed_gpu_value = 0x9876fedcu;
|
|
auto mixed_owner = resources.GetBufferCache().ObtainBuffer(
|
|
base + mixed_source_offset, mixed_source_size, true);
|
|
Require(name, "mixed-page source allocation",
|
|
mixed_owner.first != nullptr,
|
|
"mixed CPU/GPU image source did not create a containing buffer");
|
|
Require(name, "mixed-page CPU write fault",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + mixed_source_offset),
|
|
"mixed image source could not dirty its first page");
|
|
std::memcpy(memory + mixed_source_offset, &mixed_cpu_value,
|
|
sizeof(mixed_cpu_value));
|
|
resources.GetBufferCache().FillBuffer(base + mixed_source_offset + 0x1000,
|
|
sizeof(mixed_gpu_value),
|
|
mixed_gpu_value, false);
|
|
auto mixed_source_desc = MakeLinearDesc(
|
|
base + mixed_source_offset, mixed_source_size, vk::Format::eR32Uint,
|
|
Prospero::BufferFormat::k32UInt, Prospero::ImageType::kColor2D,
|
|
{mixed_source_width, 1, 1}, 1, 4, 1);
|
|
const auto mixed_source_image =
|
|
texture_cache.FindImage(mixed_source_desc);
|
|
(void)texture_cache.FindTexture(mixed_source_image, mixed_source_desc);
|
|
Require(name, "mixed-page image upload",
|
|
!texture_cache.GetImage(mixed_source_image).IsGpuModified(),
|
|
"clean Buffer source manufactured GPU image ownership");
|
|
auto mixed_source_readback = CreateHostBuffer(
|
|
name, mixed_source_size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(mixed_source_size / sizeof(u32), 0));
|
|
auto &mixed_source_native = texture_cache.GetImage(mixed_source_image);
|
|
mixed_source_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
command.Handle());
|
|
vk::BufferImageCopy mixed_source_copy{};
|
|
mixed_source_copy.bufferRowLength = mixed_source_width;
|
|
mixed_source_copy.imageSubresource.aspectMask =
|
|
vk::ImageAspectFlagBits::eColor;
|
|
mixed_source_copy.imageSubresource.layerCount = 1;
|
|
mixed_source_copy.imageExtent = {mixed_source_width, 1, 1};
|
|
command.Handle().copyImageToBuffer(mixed_source_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal,
|
|
mixed_source_readback.buffer, 1,
|
|
&mixed_source_copy);
|
|
HostReadBarrier(mixed_source_readback.buffer, mixed_source_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
constexpr uint64_t bridged_gpu_offset = 0x101000;
|
|
constexpr uint64_t bridged_cpu_offset = 0x105000;
|
|
constexpr uint64_t bridged_texel_size = 16;
|
|
constexpr uint64_t bridged_source_size =
|
|
bridged_cpu_offset - bridged_gpu_offset + bridged_texel_size;
|
|
constexpr uint32_t bridged_source_width =
|
|
bridged_source_size / bridged_texel_size;
|
|
constexpr uint32_t bridged_gpu_value = 0x10293847u;
|
|
constexpr uint32_t bridged_cpu_value = 0x56473829u;
|
|
constexpr uint32_t bridged_gpu_guest_value = 0xa5a5a5a5u;
|
|
std::array<uint32_t, bridged_texel_size / sizeof(uint32_t)>
|
|
bridged_cpu_values{};
|
|
bridged_cpu_values.fill(bridged_cpu_value);
|
|
std::memcpy(memory + bridged_cpu_offset, bridged_cpu_values.data(),
|
|
sizeof(bridged_cpu_values));
|
|
auto bridged_cpu_owner = resources.GetBufferCache().ObtainBuffer(
|
|
base + bridged_cpu_offset, bridged_texel_size, true);
|
|
Require(name, "bridged CPU owner allocation",
|
|
bridged_cpu_owner.first != nullptr,
|
|
"bridged image source did not create its CPU-side cache owner");
|
|
resources.GetBufferCache().FillBuffer(base + bridged_cpu_offset,
|
|
bridged_texel_size, 0, false);
|
|
Require(
|
|
name, "bridged CPU write fault",
|
|
resources.HandleFault(PageFaultAccess::Write,
|
|
base + bridged_cpu_offset),
|
|
"bridged image source could not transfer its second owner to CPU");
|
|
std::memcpy(memory + bridged_cpu_offset, bridged_cpu_values.data(),
|
|
sizeof(bridged_cpu_values));
|
|
|
|
std::array<uint32_t, bridged_texel_size / sizeof(uint32_t)>
|
|
bridged_gpu_guest_values{};
|
|
bridged_gpu_guest_values.fill(bridged_gpu_guest_value);
|
|
std::memcpy(memory + bridged_gpu_offset, bridged_gpu_guest_values.data(),
|
|
sizeof(bridged_gpu_guest_values));
|
|
|
|
auto bridged_gpu_owner = resources.GetBufferCache().ObtainBuffer(
|
|
base + bridged_gpu_offset, bridged_texel_size, true);
|
|
Require(name, "bridged GPU owner allocation",
|
|
bridged_gpu_owner.first != nullptr &&
|
|
bridged_gpu_owner.first != bridged_cpu_owner.first,
|
|
"bridged image source did not retain two disjoint cache owners");
|
|
resources.GetBufferCache().FillBuffer(base + bridged_gpu_offset,
|
|
bridged_texel_size,
|
|
bridged_gpu_value, false);
|
|
Require(name, "bridged ownership split",
|
|
resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + bridged_gpu_offset, bridged_texel_size) &&
|
|
!resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + bridged_cpu_offset, bridged_texel_size) &&
|
|
resources.GetBufferCache().IsRegionCpuModified(
|
|
base + bridged_cpu_offset, bridged_texel_size),
|
|
"bridged image source did not retain distinct GPU and CPU "
|
|
"ownership domains");
|
|
|
|
auto bridged_source_desc = MakeLinearDesc(
|
|
base + bridged_gpu_offset, bridged_source_size,
|
|
vk::Format::eR32G32B32A32Uint,
|
|
Prospero::BufferFormat::k32_32_32_32UInt,
|
|
Prospero::ImageType::kColor2D, {bridged_source_width, 1, 1}, 1,
|
|
bridged_texel_size, 1);
|
|
const auto bridged_source_image =
|
|
texture_cache.FindImage(bridged_source_desc);
|
|
(void)texture_cache.FindTexture(bridged_source_image,
|
|
bridged_source_desc);
|
|
auto bridged_source_readback = CreateHostBuffer(
|
|
name, bridged_source_size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(bridged_source_size / sizeof(u32), 0));
|
|
auto &bridged_source_native =
|
|
texture_cache.GetImage(bridged_source_image);
|
|
bridged_source_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
scheduler.Current().Handle());
|
|
vk::BufferImageCopy bridged_source_copy{};
|
|
bridged_source_copy.bufferRowLength = bridged_source_width;
|
|
bridged_source_copy.imageSubresource.aspectMask =
|
|
vk::ImageAspectFlagBits::eColor;
|
|
bridged_source_copy.imageSubresource.layerCount = 1;
|
|
bridged_source_copy.imageExtent = {bridged_source_width, 1, 1};
|
|
scheduler.Current().Handle().copyImageToBuffer(
|
|
bridged_source_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal, bridged_source_readback.buffer,
|
|
1, &bridged_source_copy);
|
|
HostReadBarrier(bridged_source_readback.buffer,
|
|
bridged_source_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
constexpr uint64_t byte_mirror_page_offset = 0x24000;
|
|
constexpr uint64_t byte_mirror_offset = byte_mirror_page_offset + 1;
|
|
const std::array<uint8_t, 4> byte_mirror_guest{0xacu, 0x5au, 0xbdu,
|
|
0xceu};
|
|
std::memcpy(memory + byte_mirror_page_offset, byte_mirror_guest.data(),
|
|
byte_mirror_guest.size());
|
|
auto byte_mirror_desc =
|
|
MakeLinearDesc(base + byte_mirror_offset, 1, vk::Format::eR8Unorm,
|
|
Prospero::BufferFormat::k8UNorm,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 1, 1);
|
|
const auto byte_mirror_image = texture_cache.FindImage(byte_mirror_desc);
|
|
(void)texture_cache.FindTexture(byte_mirror_image, byte_mirror_desc);
|
|
texture_cache.MarkGpuWritten(byte_mirror_image);
|
|
auto byte_mirror = resources.GetBufferCache().ObtainBuffer(
|
|
base + byte_mirror_offset, 1, false, true);
|
|
Require(
|
|
name, "byte image mirror",
|
|
byte_mirror.first != nullptr &&
|
|
!texture_cache.GetImage(byte_mirror_image).IsBufferModified() &&
|
|
texture_cache.GetImage(byte_mirror_image).IsGpuModified(),
|
|
"one-byte image copy transferred cache ownership");
|
|
|
|
const std::array<uint16_t, 4> bgra16_guest{0x3c00u, 0x4000u, 0x4200u,
|
|
0x4400u};
|
|
std::memcpy(memory + 0xb000, bgra16_guest.data(), sizeof(bgra16_guest));
|
|
auto bgra16_desc = MakeLinearDesc(
|
|
base + 0xb000, sizeof(bgra16_guest), vk::Format::eR16G16B16A16Sfloat,
|
|
Prospero::BufferFormat::k16_16_16_16Float,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 8, 1);
|
|
bgra16_desc.type = BindingType::RenderTarget;
|
|
bgra16_desc.info.bgra16 = true;
|
|
bgra16_desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
const auto bgra16_image = texture_cache.FindImage(bgra16_desc);
|
|
(void)texture_cache.FindRenderTarget(bgra16_image, bgra16_desc);
|
|
auto bgra16_readback =
|
|
CreateHostBuffer(name, sizeof(bgra16_guest),
|
|
vk::BufferUsageFlagBits::eTransferDst, {0, 0});
|
|
auto &bgra16_native = texture_cache.GetImage(bgra16_image);
|
|
bgra16_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
command.Handle());
|
|
vk::BufferImageCopy bgra16_copy{};
|
|
bgra16_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
bgra16_copy.imageSubresource.layerCount = 1;
|
|
bgra16_copy.imageExtent = {1, 1, 1};
|
|
command.Handle().copyImageToBuffer(
|
|
bgra16_native.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
|
bgra16_readback.buffer, 1, &bgra16_copy);
|
|
HostReadBarrier(bgra16_readback.buffer, bgra16_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
auto layered_readback = CreateHostBuffer(
|
|
name, sizeof(layered_values), vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(sizeof(layered_values) / sizeof(u32), 0));
|
|
auto &layered_depth_native = texture_cache.GetImage(layered_depth_image);
|
|
layered_depth_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
command.Handle());
|
|
std::array<vk::BufferImageCopy, 2> layered_copies{};
|
|
layered_copies[0].bufferOffset = 0;
|
|
layered_copies[0].imageSubresource.aspectMask =
|
|
vk::ImageAspectFlagBits::eDepth;
|
|
layered_copies[0].imageSubresource.mipLevel = 0;
|
|
layered_copies[0].imageSubresource.layerCount = 2;
|
|
layered_copies[0].imageExtent = {2, 2, 1};
|
|
layered_copies[1].bufferOffset = 32;
|
|
layered_copies[1].imageSubresource.aspectMask =
|
|
vk::ImageAspectFlagBits::eDepth;
|
|
layered_copies[1].imageSubresource.mipLevel = 1;
|
|
layered_copies[1].imageSubresource.layerCount = 2;
|
|
layered_copies[1].imageExtent = {1, 1, 1};
|
|
command.Handle().copyImageToBuffer(
|
|
layered_depth_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal, layered_readback.buffer,
|
|
static_cast<uint32_t>(layered_copies.size()), layered_copies.data());
|
|
HostReadBarrier(layered_readback.buffer, layered_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
|
|
vk::ShaderModuleCreateInfo module_info{};
|
|
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
|
module_info.codeSize = sizeof(GPU_TEST_MS_DEPTH_SPV);
|
|
module_info.pCode = GPU_TEST_MS_DEPTH_SPV;
|
|
vk::ShaderModule ms_depth_module = nullptr;
|
|
RequireVk(
|
|
name, "MS depth content",
|
|
m_device.createShaderModule(&module_info, nullptr, &ms_depth_module),
|
|
"vkCreateShaderModule");
|
|
std::array<vk::DescriptorSetLayoutBinding, 2> observer_bindings{};
|
|
observer_bindings[0].binding = 0;
|
|
observer_bindings[0].descriptorType = vk::DescriptorType::eSampledImage;
|
|
observer_bindings[0].descriptorCount = 1;
|
|
observer_bindings[0].stageFlags = vk::ShaderStageFlagBits::eCompute;
|
|
observer_bindings[1].binding = 1;
|
|
observer_bindings[1].descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
observer_bindings[1].descriptorCount = 1;
|
|
observer_bindings[1].stageFlags = vk::ShaderStageFlagBits::eCompute;
|
|
vk::DescriptorSetLayoutCreateInfo observer_layout_info{};
|
|
observer_layout_info.sType =
|
|
vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
|
observer_layout_info.bindingCount =
|
|
static_cast<uint32_t>(observer_bindings.size());
|
|
observer_layout_info.pBindings = observer_bindings.data();
|
|
vk::DescriptorSetLayout observer_set_layout = nullptr;
|
|
RequireVk(name, "MS depth content",
|
|
m_device.createDescriptorSetLayout(
|
|
&observer_layout_info, nullptr, &observer_set_layout),
|
|
"vkCreateDescriptorSetLayout");
|
|
const vk::PushConstantRange observer_push{
|
|
vk::ShaderStageFlagBits::eCompute, 0, sizeof(uint32_t)};
|
|
vk::PipelineLayoutCreateInfo observer_pipeline_layout_info{};
|
|
observer_pipeline_layout_info.sType =
|
|
vk::StructureType::ePipelineLayoutCreateInfo;
|
|
observer_pipeline_layout_info.setLayoutCount = 1;
|
|
observer_pipeline_layout_info.pSetLayouts = &observer_set_layout;
|
|
observer_pipeline_layout_info.pushConstantRangeCount = 1;
|
|
observer_pipeline_layout_info.pPushConstantRanges = &observer_push;
|
|
vk::PipelineLayout observer_pipeline_layout = nullptr;
|
|
RequireVk(name, "MS depth content",
|
|
m_device.createPipelineLayout(&observer_pipeline_layout_info,
|
|
nullptr,
|
|
&observer_pipeline_layout),
|
|
"vkCreatePipelineLayout");
|
|
vk::PipelineShaderStageCreateInfo observer_stage{};
|
|
observer_stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
|
observer_stage.stage = vk::ShaderStageFlagBits::eCompute;
|
|
observer_stage.module = ms_depth_module;
|
|
observer_stage.pName = "main";
|
|
vk::ComputePipelineCreateInfo observer_pipeline_info{};
|
|
observer_pipeline_info.sType =
|
|
vk::StructureType::eComputePipelineCreateInfo;
|
|
observer_pipeline_info.stage = observer_stage;
|
|
observer_pipeline_info.layout = observer_pipeline_layout;
|
|
vk::Pipeline observer_depth_pipeline = nullptr;
|
|
RequireVk(
|
|
name, "MS depth content",
|
|
m_device.createComputePipelines(nullptr, 1, &observer_pipeline_info,
|
|
nullptr, &observer_depth_pipeline),
|
|
"vkCreateComputePipelines");
|
|
const std::array<vk::DescriptorPoolSize, 2> observer_pool_sizes{{
|
|
{vk::DescriptorType::eSampledImage, 2},
|
|
{vk::DescriptorType::eStorageBuffer, 2},
|
|
}};
|
|
vk::DescriptorPoolCreateInfo observer_pool_info{};
|
|
observer_pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
|
observer_pool_info.maxSets = 2;
|
|
observer_pool_info.poolSizeCount =
|
|
static_cast<uint32_t>(observer_pool_sizes.size());
|
|
observer_pool_info.pPoolSizes = observer_pool_sizes.data();
|
|
vk::DescriptorPool observer_pool = nullptr;
|
|
RequireVk(name, "MS depth content",
|
|
m_device.createDescriptorPool(&observer_pool_info, nullptr,
|
|
&observer_pool),
|
|
"vkCreateDescriptorPool");
|
|
const std::array<vk::DescriptorSetLayout, 2> observer_layouts{
|
|
observer_set_layout, observer_set_layout};
|
|
std::array<vk::DescriptorSet, 2> observer_sets{};
|
|
vk::DescriptorSetAllocateInfo observer_allocate{};
|
|
observer_allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
|
observer_allocate.descriptorPool = observer_pool;
|
|
observer_allocate.descriptorSetCount =
|
|
static_cast<uint32_t>(observer_sets.size());
|
|
observer_allocate.pSetLayouts = observer_layouts.data();
|
|
RequireVk(name, "MS depth content",
|
|
m_device.allocateDescriptorSets(&observer_allocate,
|
|
observer_sets.data()),
|
|
"vkAllocateDescriptorSets");
|
|
std::array<Buffer, 2> ms_observer_outputs{
|
|
CreateHostBuffer(name, 4 * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eStorageBuffer,
|
|
std::vector<u32>(4, 0)),
|
|
CreateHostBuffer(name, 4 * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eStorageBuffer,
|
|
std::vector<u32>(4, 0))};
|
|
const std::array<ImageId, 2> observed_images{ms_depth_image,
|
|
ms_depth_image_4x};
|
|
const std::array<uint32_t, 2> observed_samples{2, 4};
|
|
for (uint32_t index = 0; index < observed_images.size(); index++) {
|
|
auto &observed = texture_cache.GetImage(observed_images[index]);
|
|
observed.Transit(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, {},
|
|
command.Handle());
|
|
ImageViewInfo view_info{};
|
|
view_info.format = observed.backing.format;
|
|
view_info.type = vk::ImageViewType::e2D;
|
|
view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
view_info.mapping = {vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eR,
|
|
vk::ComponentSwizzle::eR,
|
|
vk::ComponentSwizzle::eR};
|
|
view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto view = observed.FindView(view_info);
|
|
const vk::DescriptorImageInfo image_info{
|
|
nullptr, view, vk::ImageLayout::eShaderReadOnlyOptimal};
|
|
const vk::DescriptorBufferInfo buffer_info{
|
|
ms_observer_outputs[index].buffer, 0,
|
|
ms_observer_outputs[index].size};
|
|
std::array<vk::WriteDescriptorSet, 2> writes{};
|
|
writes[0].sType = vk::StructureType::eWriteDescriptorSet;
|
|
writes[0].dstSet = observer_sets[index];
|
|
writes[0].dstBinding = 0;
|
|
writes[0].descriptorCount = 1;
|
|
writes[0].descriptorType = vk::DescriptorType::eSampledImage;
|
|
writes[0].pImageInfo = &image_info;
|
|
writes[1].sType = vk::StructureType::eWriteDescriptorSet;
|
|
writes[1].dstSet = observer_sets[index];
|
|
writes[1].dstBinding = 1;
|
|
writes[1].descriptorCount = 1;
|
|
writes[1].descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
writes[1].pBufferInfo = &buffer_info;
|
|
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()),
|
|
writes.data(), 0, nullptr);
|
|
command.Handle().bindPipeline(vk::PipelineBindPoint::eCompute,
|
|
observer_depth_pipeline);
|
|
command.Handle().bindDescriptorSets(vk::PipelineBindPoint::eCompute,
|
|
observer_pipeline_layout, 0, 1,
|
|
&observer_sets[index], 0, nullptr);
|
|
command.Handle().pushConstants(
|
|
observer_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
|
|
sizeof(observed_samples[index]), &observed_samples[index]);
|
|
command.Handle().dispatch(1, 1, 1);
|
|
HostReadBarrier(ms_observer_outputs[index].buffer,
|
|
ms_observer_outputs[index].size,
|
|
vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::AccessFlagBits::eShaderWrite);
|
|
}
|
|
|
|
scheduler.Finish();
|
|
|
|
const auto mip_prefix_words = ReadBuffer(
|
|
name, mip_prefix_readback, mip_prefix_size / sizeof(uint32_t));
|
|
const auto volume_words =
|
|
ReadBuffer(name, volume_readback, volume_guest.size());
|
|
Require(name, "formatted volume content",
|
|
volume_words[0] == volume_values[0] &&
|
|
volume_words[volume_slice_words] == volume_values[1],
|
|
"volume synchronization lost a padded depth slice");
|
|
DestroyBuffer(&volume_readback);
|
|
const auto mip_formatted_words = ReadBuffer(
|
|
name, mip_formatted_readback, mip_guest_size / sizeof(uint32_t));
|
|
const auto mip0_word = mip_sizes[0].offset / sizeof(uint32_t);
|
|
Require(name, "formatted mip prefix content",
|
|
mip0_word < mip_prefix_words.size() &&
|
|
mip0_word < mip_native.size() &&
|
|
mip_prefix_words[mip0_word] == mip_native[mip0_word],
|
|
"fitting mip-prefix synchronization copied "
|
|
"stale guest backing");
|
|
Require(name, "formatted Buffer content",
|
|
mip0_word < mip_formatted_words.size() &&
|
|
mip0_word < mip_native.size() &&
|
|
mip_formatted_words[mip0_word] == mip_native[mip0_word],
|
|
"formatted Buffer read bypassed authoritative "
|
|
"native image mip data");
|
|
Require(name, "CPU-dirty formatted mirror content",
|
|
ReadBuffer(name, mirror_cpu_readback, 1) ==
|
|
std::vector<u32>{mirror_cpu_value},
|
|
"formatted Buffer mirror published stale "
|
|
"native image bytes");
|
|
Require(name, "Buffer-superseded exact content",
|
|
ReadBuffer(name, exact_buffer_readback, 1) ==
|
|
std::vector<u32>{exact_buffer_value},
|
|
"exact-format recreation initialized from "
|
|
"stale guest bytes");
|
|
Require(name, "partial-page CPU refresh content",
|
|
ReadBuffer(name, partial_cpu_refresh_readback,
|
|
partial_cpu_refresh_expected.size()) ==
|
|
partial_cpu_refresh_expected,
|
|
"cached buffer uploaded bytes outside the exact "
|
|
"staged guest range");
|
|
const auto mixed_source_words = ReadBuffer(
|
|
name, mixed_source_readback, mixed_source_size / sizeof(u32));
|
|
Require(name, "mixed-page image content",
|
|
mixed_source_words.front() == mixed_cpu_value &&
|
|
mixed_source_words[0x1000 / sizeof(u32)] == mixed_gpu_value,
|
|
"mixed CPU/GPU source upload lost one "
|
|
"ownership domain");
|
|
const auto bridged_source_words = ReadBuffer(
|
|
name, bridged_source_readback, bridged_source_size / sizeof(u32));
|
|
Require(name, "bridged-owner image content",
|
|
bridged_source_words.front() == bridged_gpu_value &&
|
|
bridged_source_words.back() == bridged_cpu_value,
|
|
"image upload bridging separate cache owners lost a GPU or CPU "
|
|
"ownership domain");
|
|
Require(name, "BGRA16 content",
|
|
ReadBuffer(name, bgra16_readback, 2) ==
|
|
std::vector<u32>{0x40004200u, 0x44003c00u},
|
|
"GPU BGRA16 swap did not publish RGBA half-word order");
|
|
scheduler.Finish();
|
|
const auto layered_words =
|
|
ReadBuffer(name, layered_readback, layered_values.size());
|
|
const std::array<float, 10> layered_expected{
|
|
layered_values[0], layered_values[1], layered_values[2],
|
|
layered_values[3], layered_values[4], layered_values[5],
|
|
layered_values[6], layered_values[7], layered_values[8],
|
|
layered_values[9]};
|
|
bool layered_content = layered_words.size() == layered_expected.size();
|
|
for (uint32_t index = 0;
|
|
layered_content && index < layered_expected.size(); index++) {
|
|
layered_content &=
|
|
layered_words[index] == std::bit_cast<u32>(layered_expected[index]);
|
|
}
|
|
Require(name, "layered mipped depth content", layered_content,
|
|
"depth/color conversion changed a mip or array-layer value");
|
|
const std::array<std::array<float, 4>, 2> expected_ms_depth{{
|
|
{0x2000 / 65535.0f, 0xe000 / 65535.0f, 0.0f, 0.0f},
|
|
{0.0f, 0x4000 / 65535.0f, 0x8000 / 65535.0f, 1.0f},
|
|
}};
|
|
for (uint32_t image = 0; image < expected_ms_depth.size(); image++) {
|
|
const auto words = ReadBuffer(name, ms_observer_outputs[image], 4);
|
|
bool content = words.size() == 4;
|
|
for (uint32_t sample = 0; content && sample < observed_samples[image];
|
|
sample++) {
|
|
const float actual = std::bit_cast<float>(words[sample]);
|
|
content &= std::abs(actual - expected_ms_depth[image][sample]) <=
|
|
1.5f / 65535.0f;
|
|
}
|
|
Require(name,
|
|
image == 0 ? "two-sample depth content"
|
|
: "four-sample depth content",
|
|
content,
|
|
"color-to-multisample-depth changed a packed sample value");
|
|
}
|
|
|
|
auto unrelated_stencil_alias = MakeLinearDesc(
|
|
base + second_stencil_offset, ms_stencil_size, vk::Format::eR32Uint,
|
|
Prospero::BufferFormat::k32UInt, Prospero::ImageType::kColor2D,
|
|
{static_cast<uint32_t>(ms_stencil_size / sizeof(uint32_t)), 1, 1}, 1,
|
|
4, 1);
|
|
const auto unrelated_stencil_image =
|
|
texture_cache.FindImage(unrelated_stencil_alias);
|
|
Require(name, "stencil association filtering",
|
|
unrelated_stencil_image &&
|
|
unrelated_stencil_image != ms_depth_image &&
|
|
!texture_cache.GetImage(unrelated_stencil_image).depth_id,
|
|
"an unrelated color alias was hijacked by the stencil "
|
|
"association record");
|
|
|
|
texture_cache.UnmapMemory(base + second_stencil_offset, ms_stencil_size);
|
|
Require(name, "stencil proxy unmap",
|
|
!texture_cache.FindImageFromRange(base + second_stencil_offset,
|
|
ms_stencil_size, false) &&
|
|
texture_cache.GetImage(ms_depth_image).backing.image ==
|
|
ms_depth_backing,
|
|
"stencil unmap removed the depth backing or retained its proxy");
|
|
const auto reassociated_depth =
|
|
texture_cache.FindImage(switched_ms_depth);
|
|
TextureCacheTestAccess::AssociateStencil(
|
|
texture_cache, reassociated_depth, switched_ms_depth.info.stencil);
|
|
const auto reassociated_proxy = texture_cache.FindImageFromRange(
|
|
base + second_stencil_offset, ms_stencil_size, false);
|
|
Require(name, "stencil proxy reassociation",
|
|
reassociated_depth == ms_depth_image && reassociated_proxy &&
|
|
texture_cache.GetImage(reassociated_proxy).depth_id ==
|
|
ms_depth_image,
|
|
"stencil proxy did not re-associate with the surviving depth");
|
|
texture_cache.UnmapMemory(ms_depth_desc.info.data.address,
|
|
ms_depth_desc.info.data.size);
|
|
Require(name, "depth unmap proxy retirement",
|
|
!texture_cache.FindImageFromRange(base + second_stencil_offset,
|
|
ms_stencil_size, false),
|
|
"unmapping depth retained its re-associated stencil proxy");
|
|
|
|
constexpr uint64_t exact_image_offset = 0x334100;
|
|
constexpr uint64_t dirty_sibling_offset = 0x334200;
|
|
constexpr uint32_t dirty_sibling_value = 0xc001d00du;
|
|
auto dirty_sibling = resources.GetBufferCache().ObtainBuffer(
|
|
base + dirty_sibling_offset, sizeof(dirty_sibling_value), true);
|
|
Require(name, "same-page dirty sibling allocation",
|
|
dirty_sibling.first != nullptr,
|
|
"failed to create the disjoint same-page Buffer owner");
|
|
resources.GetBufferCache().FillBuffer(base + dirty_sibling_offset,
|
|
sizeof(dirty_sibling_value),
|
|
dirty_sibling_value, false);
|
|
auto exact_image_desc =
|
|
MakeLinearDesc(base + exact_image_offset, sizeof(uint32_t),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
const auto exact_image = texture_cache.FindImage(exact_image_desc);
|
|
(void)texture_cache.FindTexture(exact_image, exact_image_desc);
|
|
texture_cache.MarkGpuWritten(exact_image);
|
|
Require(name, "same-page exact alias ownership",
|
|
resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + dirty_sibling_offset, sizeof(dirty_sibling_value)) &&
|
|
!resources.GetBufferCache().HasGpuDirtyBytes(
|
|
base + exact_image_offset, sizeof(uint32_t)) &&
|
|
texture_cache.GetImage(exact_image).IsGpuModified(),
|
|
"image ownership discarded or conflicted with disjoint dirty "
|
|
"Buffer bytes on the same tracker page");
|
|
auto exact_buffer = resources.GetBufferCache().ObtainBuffer(
|
|
base + exact_image_offset, sizeof(uint32_t), true);
|
|
Require(name, "overlapping buffer ownership allocation",
|
|
exact_buffer.first != nullptr,
|
|
"failed to create the overlapping dirty Buffer owner");
|
|
exact_buffer.first->Fill(exact_buffer.second, sizeof(uint32_t),
|
|
dirty_sibling_value);
|
|
Libs::Graphics::Buffer exact_download(
|
|
m_runtime_context, scheduler, MemoryUsage::DeviceLocal,
|
|
base + exact_image_offset, AllFlags, sizeof(uint32_t));
|
|
Require(name, "overlapping buffer ownership rejection",
|
|
!texture_cache.FindImageFromRange(base + exact_image_offset,
|
|
sizeof(uint32_t)) &&
|
|
!BufferCacheTestAccess::SynchronizeBufferFromImage(
|
|
resources.GetBufferCache(), exact_download,
|
|
base + exact_image_offset, sizeof(uint32_t)),
|
|
"dirty Buffer bytes did not block the stale image source");
|
|
resources.GetBufferCache().ReadMemory(base + exact_image_offset,
|
|
sizeof(uint32_t));
|
|
resources.GetBufferCache().ReadMemory(base + dirty_sibling_offset,
|
|
sizeof(dirty_sibling_value));
|
|
|
|
constexpr std::array<uint64_t, 2> gc_image_offsets{0x330000, 0x332000};
|
|
constexpr std::array<uint32_t, 2> gc_image_values{0x76543210u,
|
|
0x89abcdefu};
|
|
constexpr std::array<uint32_t, 2> gc_stale_values{0x10293847u,
|
|
0x56473829u};
|
|
for (size_t index = 0; index < gc_image_offsets.size(); index++) {
|
|
std::memcpy(memory + gc_image_offsets[index], &gc_image_values[index],
|
|
sizeof(uint32_t));
|
|
}
|
|
auto gc_image_desc_a =
|
|
MakeLinearDesc(base + gc_image_offsets[0], sizeof(uint32_t),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
auto gc_image_desc_b = gc_image_desc_a;
|
|
gc_image_desc_b.info.data.address = base + gc_image_offsets[1];
|
|
auto clean_buffer_alias = resources.GetBufferCache().ObtainBuffer(
|
|
gc_image_desc_a.info.data.address, gc_image_desc_a.info.data.size,
|
|
true);
|
|
Require(name, "clean pre-image buffer alias",
|
|
clean_buffer_alias.first != nullptr,
|
|
"failed to create the clean cached Buffer alias");
|
|
resources.GetBufferCache().ReadMemory(gc_image_desc_a.info.data.address,
|
|
gc_image_desc_a.info.data.size);
|
|
const std::array gc_images{texture_cache.FindImage(gc_image_desc_a),
|
|
texture_cache.FindImage(gc_image_desc_b)};
|
|
Require(
|
|
name, "non-GPU image range validity",
|
|
!texture_cache.FindImageFromRange(gc_image_desc_a.info.data.address,
|
|
gc_image_desc_a.info.data.size),
|
|
"FindImageFromRange accepted an image without current GPU "
|
|
"contents");
|
|
(void)texture_cache.FindTexture(gc_images[0], gc_image_desc_a);
|
|
(void)texture_cache.FindTexture(gc_images[1], gc_image_desc_b);
|
|
for (const auto image : gc_images) {
|
|
texture_cache.MarkGpuWritten(image);
|
|
}
|
|
Require(name, "GPU image range validity",
|
|
texture_cache.FindImageFromRange(
|
|
gc_image_desc_a.info.data.address,
|
|
gc_image_desc_a.info.data.size) == gc_images[0],
|
|
"FindImageFromRange rejected a clean GPU-current image");
|
|
texture_cache.GetImage(gc_images[0])
|
|
.InvalidateCpuWrite(gc_image_desc_a.info.data.address,
|
|
gc_image_desc_a.info.data.size);
|
|
Require(
|
|
name, "CPU-dirty image range validity",
|
|
!texture_cache.FindImageFromRange(gc_image_desc_a.info.data.address,
|
|
gc_image_desc_a.info.data.size),
|
|
"FindImageFromRange accepted CPU-dirty native contents");
|
|
texture_cache.MarkGpuWritten(gc_images[0]);
|
|
for (size_t index = 0; index < gc_image_offsets.size(); index++) {
|
|
Libs::LibKernel::Memory::WriteBacking(base + gc_image_offsets[index],
|
|
&gc_stale_values[index],
|
|
sizeof(uint32_t));
|
|
}
|
|
TextureCacheTestAccess::ConfigureGarbageCollection(
|
|
texture_cache, gc_images, 17, UINT64_MAX);
|
|
texture_cache.RunGarbageCollector();
|
|
Require(name, "downloadable image pre-pressure retention",
|
|
std::ranges::all_of(gc_images,
|
|
[&](ImageId image) {
|
|
return TextureCacheTestAccess::Contains(
|
|
texture_cache, image);
|
|
}),
|
|
"GC retired safely downloadable GPU images before pressure");
|
|
const auto gc_batch_tick = scheduler.CurrentTick();
|
|
TextureCacheTestAccess::ConfigureGarbageCollection(texture_cache,
|
|
gc_images, 81, 0);
|
|
texture_cache.RunGarbageCollector();
|
|
std::array<uint32_t, 2> gc_before_completion{};
|
|
for (size_t index = 0; index < gc_image_offsets.size(); index++) {
|
|
Libs::LibKernel::Memory::TryReadBacking(base + gc_image_offsets[index],
|
|
&gc_before_completion[index],
|
|
sizeof(uint32_t));
|
|
}
|
|
Require(name, "batched image pressure retirement",
|
|
std::ranges::none_of(gc_images,
|
|
[&](ImageId image) {
|
|
return TextureCacheTestAccess::Contains(
|
|
texture_cache, image);
|
|
}) &&
|
|
scheduler.CurrentTick() == gc_batch_tick &&
|
|
gc_before_completion == gc_stale_values,
|
|
"GC submitted per image or published a readback before GPU "
|
|
"completion");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
auto refreshed_buffer_alias = resources.GetBufferCache().ObtainBuffer(
|
|
gc_image_desc_a.info.data.address, gc_image_desc_a.info.data.size,
|
|
false);
|
|
Require(name, "post-publication Buffer reacquire",
|
|
refreshed_buffer_alias.first != nullptr,
|
|
"Buffer lookup failed after the retired image publication");
|
|
std::array<uint32_t, 2> gc_after_completion{};
|
|
for (size_t index = 0; index < gc_image_offsets.size(); index++) {
|
|
Libs::LibKernel::Memory::TryReadBacking(base + gc_image_offsets[index],
|
|
&gc_after_completion[index],
|
|
sizeof(uint32_t));
|
|
}
|
|
Require(name, "batched image readback publication",
|
|
scheduler.CurrentTick() == gc_batch_tick + 1 &&
|
|
gc_after_completion == gc_image_values,
|
|
"one submission did not publish both deferred image readbacks");
|
|
Require(name, "refreshed post-image buffer alias",
|
|
refreshed_buffer_alias.first != nullptr,
|
|
"failed to reacquire the cached Buffer alias after image "
|
|
"publication");
|
|
auto alias_readback = CreateHostBuffer(
|
|
name, sizeof(uint32_t), vk::BufferUsageFlagBits::eTransferDst, {0});
|
|
const vk::BufferCopy alias_copy{refreshed_buffer_alias.second, 0,
|
|
sizeof(uint32_t)};
|
|
scheduler.Current().Handle().copyBuffer(
|
|
refreshed_buffer_alias.first->Handle(), alias_readback.buffer, 1,
|
|
&alias_copy);
|
|
HostReadBarrier(alias_readback.buffer, alias_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
Require(name, "post-image Buffer alias content",
|
|
ReadBuffer(name, alias_readback, 1) ==
|
|
std::vector<u32>{gc_image_values[0]},
|
|
"a clean cached Buffer alias survived image ownership with "
|
|
"stale native bytes");
|
|
DestroyBuffer(&alias_readback);
|
|
|
|
constexpr uint64_t submit_readback_offset = 0x336000;
|
|
constexpr uint32_t submit_readback_value = 0x13579bdfu;
|
|
constexpr uint32_t submit_readback_stale = 0x2468ace0u;
|
|
auto submit_readback_desc =
|
|
MakeLinearDesc(base + submit_readback_offset, sizeof(uint32_t),
|
|
vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt,
|
|
Prospero::ImageType::kColor2D, {1, 1, 1}, 1, 4, 1);
|
|
submit_readback_desc.type = BindingType::Storage;
|
|
submit_readback_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, true);
|
|
const auto submit_readback_image =
|
|
texture_cache.FindImage(submit_readback_desc);
|
|
Require(name, "linear submit image clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, submit_readback_desc.info.data.address,
|
|
submit_readback_desc.info.data.size, submit_readback_value),
|
|
"failed to create GPU-current linear storage contents");
|
|
TextureCacheTestAccess::TrackDownload(texture_cache,
|
|
submit_readback_image);
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
submit_readback_desc.info.data.address, &submit_readback_stale,
|
|
sizeof(submit_readback_stale));
|
|
const auto submit_readback_tick = scheduler.CurrentTick();
|
|
texture_cache.ProcessDownloadImages();
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, false);
|
|
uint32_t submit_before_completion = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(
|
|
submit_readback_desc.info.data.address, &submit_before_completion,
|
|
sizeof(submit_before_completion));
|
|
Require(name, "linear submit deferred publication",
|
|
submit_before_completion == submit_readback_stale &&
|
|
scheduler.CurrentTick() == submit_readback_tick,
|
|
"submit-time linear readback published synchronously");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
uint32_t submit_after_completion = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(
|
|
submit_readback_desc.info.data.address, &submit_after_completion,
|
|
sizeof(submit_after_completion));
|
|
Require(name, "linear submit readback reuse",
|
|
submit_after_completion == submit_readback_value &&
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
submit_readback_image) &&
|
|
texture_cache.GetImage(submit_readback_image).IsGpuModified(),
|
|
"submit-time readback did not publish or retained no reusable "
|
|
"GPU-current image");
|
|
|
|
constexpr uint64_t linear_depth_offset = 0x33a000;
|
|
constexpr uint64_t linear_stencil_offset = 0x33b000;
|
|
constexpr uint32_t linear_depth_width = 3;
|
|
constexpr uint32_t linear_depth_height = 2;
|
|
constexpr uint32_t linear_depth_pitch = 4;
|
|
constexpr uint32_t linear_depth_layers = 2;
|
|
constexpr uint32_t linear_depth_words =
|
|
linear_depth_pitch * linear_depth_height * linear_depth_layers;
|
|
constexpr uint32_t linear_depth_clear = 0x3f400000u;
|
|
std::array<uint32_t, linear_depth_words> linear_depth_guest{};
|
|
for (uint32_t index = 0; index < linear_depth_words; index++) {
|
|
linear_depth_guest[index] = 0x51000000u + index;
|
|
}
|
|
constexpr std::array<uint8_t, 8> linear_stencil_guest{
|
|
0x91, 0x82, 0x73, 0x64, 0x55, 0x46, 0x37, 0x28};
|
|
std::memcpy(memory + linear_depth_offset, linear_depth_guest.data(),
|
|
sizeof(linear_depth_guest));
|
|
std::memcpy(memory + linear_stencil_offset, linear_stencil_guest.data(),
|
|
sizeof(linear_stencil_guest));
|
|
auto linear_depth_desc = MakeLinearDesc(
|
|
base + linear_depth_offset, sizeof(linear_depth_guest),
|
|
vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k32Float,
|
|
Prospero::ImageType::kColor2D,
|
|
{linear_depth_width, linear_depth_height, 1}, linear_depth_layers, 4,
|
|
1);
|
|
linear_depth_desc.type = BindingType::DepthTarget;
|
|
linear_depth_desc.info.pitch = linear_depth_pitch;
|
|
linear_depth_desc.info.stencil = {base + linear_stencil_offset,
|
|
sizeof(linear_stencil_guest)};
|
|
linear_depth_desc.info.mip_layout[0] = {0, sizeof(linear_depth_guest),
|
|
linear_depth_pitch,
|
|
linear_depth_height};
|
|
linear_depth_desc.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
linear_depth_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto linear_depth_image =
|
|
texture_cache.FindImage(linear_depth_desc);
|
|
Require(name, "linear depth native clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, linear_depth_desc.info.data.address,
|
|
linear_depth_desc.info.data.size, linear_depth_clear),
|
|
"failed to create GPU-current padded linear depth contents");
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, true);
|
|
TextureCacheTestAccess::TrackDownload(texture_cache, linear_depth_image);
|
|
const auto linear_depth_tick = scheduler.CurrentTick();
|
|
texture_cache.ProcessDownloadImages();
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, false);
|
|
std::array<uint32_t, linear_depth_words> linear_depth_before{};
|
|
std::memcpy(linear_depth_before.data(), memory + linear_depth_offset,
|
|
sizeof(linear_depth_before));
|
|
Require(name, "linear depth deferred publication",
|
|
linear_depth_before == linear_depth_guest &&
|
|
scheduler.CurrentTick() == linear_depth_tick,
|
|
"linear depth readback published before GPU completion");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
std::array<uint32_t, linear_depth_words> linear_depth_after{};
|
|
std::array<uint8_t, linear_stencil_guest.size()> linear_stencil_after{};
|
|
std::memcpy(linear_depth_after.data(), memory + linear_depth_offset,
|
|
sizeof(linear_depth_after));
|
|
std::memcpy(linear_stencil_after.data(), memory + linear_stencil_offset,
|
|
sizeof(linear_stencil_after));
|
|
bool linear_depth_matches = true;
|
|
uint32_t linear_depth_mismatch = UINT32_MAX;
|
|
for (uint32_t layer = 0; layer < linear_depth_layers; layer++) {
|
|
for (uint32_t y = 0; y < linear_depth_height; y++) {
|
|
for (uint32_t x = 0; x < linear_depth_pitch; x++) {
|
|
const auto index =
|
|
layer * linear_depth_pitch * linear_depth_height +
|
|
y * linear_depth_pitch + x;
|
|
const auto expected = x < linear_depth_width
|
|
? linear_depth_clear
|
|
: linear_depth_guest[index];
|
|
if (linear_depth_after[index] != expected &&
|
|
linear_depth_mismatch == UINT32_MAX) {
|
|
linear_depth_mismatch = index;
|
|
}
|
|
linear_depth_matches &= linear_depth_after[index] == expected;
|
|
}
|
|
}
|
|
}
|
|
Require(name, "linear depth native contents",
|
|
linear_depth_matches &&
|
|
linear_stencil_after == linear_stencil_guest &&
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
linear_depth_image) &&
|
|
texture_cache.GetImage(linear_depth_image).IsGpuModified(),
|
|
fmt::format(
|
|
"linear depth mismatch={} actual=0x{:08x} expected=0x{:08x} "
|
|
"stencil={} contains={} gpu={}",
|
|
linear_depth_mismatch,
|
|
linear_depth_mismatch == UINT32_MAX
|
|
? 0
|
|
: linear_depth_after[linear_depth_mismatch],
|
|
linear_depth_mismatch == UINT32_MAX
|
|
? 0
|
|
: (linear_depth_mismatch % linear_depth_pitch <
|
|
linear_depth_width
|
|
? linear_depth_clear
|
|
: linear_depth_guest[linear_depth_mismatch]),
|
|
linear_stencil_after == linear_stencil_guest,
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
linear_depth_image),
|
|
texture_cache.GetImage(linear_depth_image).IsGpuModified())
|
|
.c_str());
|
|
|
|
constexpr uint64_t tiled_depth_offset = 0x380000;
|
|
constexpr uint32_t tiled_depth_width = 3;
|
|
constexpr uint32_t tiled_depth_height = 2;
|
|
constexpr uint32_t tiled_depth_pitch = 4;
|
|
constexpr uint32_t tiled_depth_layers = 2;
|
|
constexpr uint64_t tiled_depth_slice = 0x10000;
|
|
constexpr uint64_t tiled_depth_size =
|
|
tiled_depth_slice * tiled_depth_layers;
|
|
constexpr uint32_t tiled_depth_clear = 0x3f200000u;
|
|
constexpr uint32_t tiled_depth_stale = 0xdeadbeefu;
|
|
std::vector<uint32_t> tiled_depth_guest(
|
|
tiled_depth_size / sizeof(uint32_t), tiled_depth_stale);
|
|
std::memcpy(memory + tiled_depth_offset, tiled_depth_guest.data(),
|
|
tiled_depth_size);
|
|
auto tiled_depth_desc = MakeLinearDesc(
|
|
base + tiled_depth_offset, tiled_depth_size, vk::Format::eD32Sfloat,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{tiled_depth_width, tiled_depth_height, 1}, tiled_depth_layers, 4, 1);
|
|
tiled_depth_desc.type = BindingType::DepthTarget;
|
|
tiled_depth_desc.info.pitch = tiled_depth_pitch;
|
|
tiled_depth_desc.info.tile_mode = Prospero::TileMode::kDepth;
|
|
tiled_depth_desc.info.mip_layout[0] = {
|
|
0, tiled_depth_size, tiled_depth_pitch, tiled_depth_height};
|
|
tiled_depth_desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
tiled_depth_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto tiled_depth_image = texture_cache.FindImage(tiled_depth_desc);
|
|
Require(name, "tiled depth native clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, tiled_depth_desc.info.data.address,
|
|
tiled_depth_desc.info.data.size, tiled_depth_clear),
|
|
"failed to create GPU-current layered tiled depth contents");
|
|
const auto [tiled_prefix, tiled_prefix_offset] =
|
|
TextureCacheTestAccess::MapDownload(texture_cache, 64, 64);
|
|
Require(name, "tiled depth download prefix",
|
|
tiled_prefix != nullptr && tiled_prefix_offset != 0,
|
|
"failed to force a nonzero reusable-download offset");
|
|
const auto tiled_depth_tick = scheduler.CurrentTick();
|
|
Require(
|
|
name, "tiled depth download queue",
|
|
TextureCacheTestAccess::TryDownload(texture_cache, tiled_depth_image),
|
|
"layered tiled depth readback was rejected");
|
|
std::vector<uint32_t> tiled_depth_before(tiled_depth_guest.size());
|
|
std::memcpy(tiled_depth_before.data(), memory + tiled_depth_offset,
|
|
tiled_depth_size);
|
|
Require(name, "tiled depth deferred publication",
|
|
tiled_depth_before == tiled_depth_guest &&
|
|
scheduler.CurrentTick() == tiled_depth_tick,
|
|
"tiled depth readback published before GPU completion");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
std::vector<uint32_t> tiled_depth_after(tiled_depth_guest.size());
|
|
std::memcpy(tiled_depth_after.data(), memory + tiled_depth_offset,
|
|
tiled_depth_size);
|
|
const auto active_tiled_words =
|
|
tiled_depth_width * tiled_depth_height * tiled_depth_layers;
|
|
const auto clear_tiled_words =
|
|
std::ranges::count(tiled_depth_after, tiled_depth_clear);
|
|
const auto stale_tiled_words =
|
|
std::ranges::count(tiled_depth_after, tiled_depth_stale);
|
|
Require(
|
|
name, "tiled depth backing preservation",
|
|
clear_tiled_words == active_tiled_words &&
|
|
stale_tiled_words ==
|
|
tiled_depth_after.size() - active_tiled_words,
|
|
fmt::format("tiled depth readback changed clear={}/{} stale={}/{}",
|
|
clear_tiled_words, active_tiled_words, stale_tiled_words,
|
|
tiled_depth_after.size() - active_tiled_words)
|
|
.c_str());
|
|
|
|
TileBlockLayout depth_block{};
|
|
Require(name, "tiled depth block layout",
|
|
TileGetBlockLayout(TileBlockFamily::Depth64KB, sizeof(uint32_t),
|
|
depth_block),
|
|
"failed to describe the tiled depth test surface");
|
|
std::array<GpuTileInfo, tiled_depth_layers> tiled_depth_tiles{};
|
|
for (uint32_t layer = 0; layer < tiled_depth_layers; layer++) {
|
|
const uint64_t offset = tiled_depth_slice * layer;
|
|
tiled_depth_tiles[layer] = {depth_block.family,
|
|
depth_block.bytes_per_element,
|
|
offset,
|
|
tiled_depth_slice,
|
|
offset,
|
|
tiled_depth_slice,
|
|
0,
|
|
tiled_depth_width,
|
|
tiled_depth_height,
|
|
1,
|
|
tiled_depth_pitch};
|
|
tiled_depth_tiles[layer].surface_z = layer;
|
|
}
|
|
auto tiled_depth_input =
|
|
CreateHostBuffer(name, tiled_depth_size, AllFlags, tiled_depth_after);
|
|
auto tiled_depth_linear =
|
|
TextureCacheTestAccess::Tiler(texture_cache)
|
|
.Detile(tiled_depth_input.buffer, 0, tiled_depth_size,
|
|
tiled_depth_size, tiled_depth_tiles);
|
|
auto tiled_depth_output =
|
|
CreateHostBuffer(name, tiled_depth_size, AllFlags,
|
|
std::vector<uint32_t>(tiled_depth_guest.size(), 0));
|
|
const vk::BufferCopy tiled_depth_copy{tiled_depth_linear.offset, 0,
|
|
tiled_depth_size};
|
|
scheduler.Current().Handle().copyBuffer(tiled_depth_linear.buffer,
|
|
tiled_depth_output.buffer, 1,
|
|
&tiled_depth_copy);
|
|
HostReadBarrier(tiled_depth_output.buffer, tiled_depth_size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
const auto tiled_depth_words =
|
|
ReadBuffer(name, tiled_depth_output, tiled_depth_guest.size());
|
|
bool tiled_depth_matches = true;
|
|
for (uint32_t layer = 0; layer < tiled_depth_layers; layer++) {
|
|
for (uint32_t y = 0; y < tiled_depth_height; y++) {
|
|
for (uint32_t x = 0; x < tiled_depth_width; x++) {
|
|
const auto index = layer * (tiled_depth_slice / sizeof(uint32_t)) +
|
|
y * tiled_depth_pitch + x;
|
|
tiled_depth_matches &=
|
|
tiled_depth_words[index] == tiled_depth_clear;
|
|
}
|
|
}
|
|
}
|
|
Require(name, "tiled depth layered contents",
|
|
tiled_depth_matches &&
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
tiled_depth_image) &&
|
|
texture_cache.GetImage(tiled_depth_image).IsGpuModified(),
|
|
"tiled depth readback lost a padded slice or retired its image");
|
|
DestroyBuffer(&tiled_depth_output);
|
|
DestroyBuffer(&tiled_depth_input);
|
|
|
|
constexpr uint64_t tiled_d16_offset = 0x3a0000;
|
|
constexpr uint64_t tiled_d16_stencil_offset = 0x3c0000;
|
|
constexpr uint16_t tiled_d16_clear = 0x8000u;
|
|
constexpr uint16_t tiled_d16_stale = 0xbeefu;
|
|
std::vector<uint16_t> tiled_d16_guest(tiled_depth_size / sizeof(uint16_t),
|
|
tiled_d16_stale);
|
|
constexpr std::array<uint8_t, 16> tiled_d16_stencil{
|
|
0x10, 0x21, 0x32, 0x43, 0x54, 0x65, 0x76, 0x87,
|
|
0x98, 0xa9, 0xba, 0xcb, 0xdc, 0xed, 0xfe, 0x0f};
|
|
std::memcpy(memory + tiled_d16_offset, tiled_d16_guest.data(),
|
|
tiled_depth_size);
|
|
std::memcpy(memory + tiled_d16_stencil_offset, tiled_d16_stencil.data(),
|
|
tiled_d16_stencil.size());
|
|
auto tiled_d16_desc = MakeLinearDesc(
|
|
base + tiled_d16_offset, tiled_depth_size,
|
|
vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k16UNorm,
|
|
Prospero::ImageType::kColor2D,
|
|
{tiled_depth_width, tiled_depth_height, 1}, tiled_depth_layers, 2, 1);
|
|
tiled_d16_desc.type = BindingType::DepthTarget;
|
|
tiled_d16_desc.info.pitch = tiled_depth_pitch;
|
|
tiled_d16_desc.info.tile_mode = Prospero::TileMode::kDepth;
|
|
tiled_d16_desc.info.stencil = {base + tiled_d16_stencil_offset,
|
|
tiled_d16_stencil.size()};
|
|
tiled_d16_desc.info.mip_layout[0] = {
|
|
0, tiled_depth_size, tiled_depth_pitch, tiled_depth_height};
|
|
tiled_d16_desc.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
tiled_d16_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto tiled_d16_image = texture_cache.FindImage(tiled_d16_desc);
|
|
Require(name, "tiled D16 native clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, tiled_d16_desc.info.data.address,
|
|
tiled_d16_desc.info.data.size,
|
|
EncodeD16AsD32(tiled_d16_clear)),
|
|
"failed to clear a layered tiled D16 fallback image");
|
|
Require(
|
|
name, "tiled D16 download queue",
|
|
TextureCacheTestAccess::TryDownload(texture_cache, tiled_d16_image),
|
|
"layered tiled D16 fallback readback was rejected");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
std::vector<uint16_t> tiled_d16_after(tiled_d16_guest.size());
|
|
std::array<uint8_t, tiled_d16_stencil.size()> tiled_d16_stencil_after{};
|
|
std::memcpy(tiled_d16_after.data(), memory + tiled_d16_offset,
|
|
tiled_depth_size);
|
|
std::memcpy(tiled_d16_stencil_after.data(),
|
|
memory + tiled_d16_stencil_offset,
|
|
tiled_d16_stencil_after.size());
|
|
Require(name, "tiled D16 backing preservation",
|
|
std::ranges::count(tiled_d16_after, tiled_d16_clear) ==
|
|
active_tiled_words &&
|
|
std::ranges::count(tiled_d16_after, tiled_d16_stale) ==
|
|
tiled_d16_after.size() - active_tiled_words &&
|
|
tiled_d16_stencil_after == tiled_d16_stencil,
|
|
"tiled D16 fallback overwrote inactive depth or stencil backing");
|
|
|
|
std::vector<uint32_t> tiled_d16_words(tiled_depth_size /
|
|
sizeof(uint32_t));
|
|
std::memcpy(tiled_d16_words.data(), tiled_d16_after.data(),
|
|
tiled_depth_size);
|
|
std::array<GpuTileInfo, tiled_depth_layers> tiled_d16_tiles{};
|
|
Require(name, "tiled D16 block layout",
|
|
TileGetBlockLayout(TileBlockFamily::Depth64KB, sizeof(uint16_t),
|
|
depth_block),
|
|
"failed to describe the tiled D16 test surface");
|
|
for (uint32_t layer = 0; layer < tiled_depth_layers; layer++) {
|
|
const uint64_t offset = tiled_depth_slice * layer;
|
|
tiled_d16_tiles[layer] = {depth_block.family,
|
|
depth_block.bytes_per_element,
|
|
offset,
|
|
tiled_depth_slice,
|
|
offset,
|
|
tiled_depth_slice,
|
|
0,
|
|
tiled_depth_width,
|
|
tiled_depth_height,
|
|
1,
|
|
tiled_depth_pitch};
|
|
tiled_d16_tiles[layer].surface_z = layer;
|
|
}
|
|
auto tiled_d16_input =
|
|
CreateHostBuffer(name, tiled_depth_size, AllFlags, tiled_d16_words);
|
|
auto tiled_d16_linear =
|
|
TextureCacheTestAccess::Tiler(texture_cache)
|
|
.Detile(tiled_d16_input.buffer, 0, tiled_depth_size,
|
|
tiled_depth_size, tiled_d16_tiles);
|
|
auto tiled_d16_output =
|
|
CreateHostBuffer(name, tiled_depth_size, AllFlags,
|
|
std::vector<uint32_t>(tiled_d16_words.size(), 0));
|
|
const vk::BufferCopy tiled_d16_copy{tiled_d16_linear.offset, 0,
|
|
tiled_depth_size};
|
|
scheduler.Current().Handle().copyBuffer(
|
|
tiled_d16_linear.buffer, tiled_d16_output.buffer, 1, &tiled_d16_copy);
|
|
HostReadBarrier(tiled_d16_output.buffer, tiled_depth_size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
const auto tiled_d16_linear_words =
|
|
ReadBuffer(name, tiled_d16_output, tiled_d16_words.size());
|
|
std::vector<uint16_t> tiled_d16_linear_values(tiled_d16_guest.size());
|
|
std::memcpy(tiled_d16_linear_values.data(), tiled_d16_linear_words.data(),
|
|
tiled_depth_size);
|
|
bool tiled_d16_matches = true;
|
|
for (uint32_t layer = 0; layer < tiled_depth_layers; layer++) {
|
|
for (uint32_t y = 0; y < tiled_depth_height; y++) {
|
|
for (uint32_t x = 0; x < tiled_depth_width; x++) {
|
|
const auto index = layer * (tiled_depth_slice / sizeof(uint16_t)) +
|
|
y * tiled_depth_pitch + x;
|
|
tiled_d16_matches &=
|
|
tiled_d16_linear_values[index] == tiled_d16_clear;
|
|
}
|
|
}
|
|
}
|
|
Require(name, "tiled D16 layered contents",
|
|
tiled_d16_matches &&
|
|
TextureCacheTestAccess::Contains(texture_cache,
|
|
tiled_d16_image) &&
|
|
texture_cache.GetImage(tiled_d16_image).IsGpuModified(),
|
|
"tiled D16 fallback lost a converted layer or retired its image");
|
|
DestroyBuffer(&tiled_d16_output);
|
|
DestroyBuffer(&tiled_d16_input);
|
|
|
|
constexpr uint64_t d16_fallback_offset = 0x33c000;
|
|
constexpr uint64_t d16_fallback_stencil_offset = 0x33d000;
|
|
constexpr std::array<uint16_t, 4> d16_fallback_values{0x0000u, 0x2468u,
|
|
0xabcdu, 0xffffu};
|
|
std::memcpy(memory + d16_fallback_offset, d16_fallback_values.data(),
|
|
sizeof(d16_fallback_values));
|
|
std::memset(memory + d16_fallback_stencil_offset, 0x6d, 4);
|
|
auto d16_depth_desc = MakeLinearDesc(
|
|
base + d16_fallback_offset, sizeof(d16_fallback_values),
|
|
vk::Format::eD24UnormS8Uint, Prospero::BufferFormat::k16UNorm,
|
|
Prospero::ImageType::kColor2D, {4, 1, 1}, 1, 2, 1);
|
|
d16_depth_desc.type = BindingType::DepthTarget;
|
|
d16_depth_desc.info.stencil = {base + d16_fallback_stencil_offset, 4};
|
|
d16_depth_desc.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
d16_depth_desc.view_info.usage =
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto d16_depth_image = texture_cache.FindImage(d16_depth_desc);
|
|
(void)texture_cache.FindDepthTarget(d16_depth_image, d16_depth_desc);
|
|
texture_cache.MarkGpuWritten(d16_depth_image);
|
|
auto d16_storage_desc = d16_depth_desc;
|
|
d16_storage_desc.type = BindingType::Storage;
|
|
d16_storage_desc.info.stencil = {};
|
|
d16_storage_desc.info.pixel_format = vk::Format::eR16Unorm;
|
|
d16_storage_desc.view_info.format = vk::Format::eR16Unorm;
|
|
d16_storage_desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
d16_storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto d16_storage_image =
|
|
texture_cache.FindImage(d16_storage_desc, true);
|
|
auto d16_storage_readback = CreateHostBuffer(
|
|
name, sizeof(d16_fallback_values),
|
|
vk::BufferUsageFlagBits::eTransferDst, std::vector<u32>(2, 0));
|
|
auto &d16_storage_native = texture_cache.GetImage(d16_storage_image);
|
|
d16_storage_native.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
scheduler.Current().Handle());
|
|
vk::BufferImageCopy d16_storage_copy{};
|
|
d16_storage_copy.bufferRowLength = 4;
|
|
d16_storage_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0,
|
|
0, 1};
|
|
d16_storage_copy.imageExtent = {4, 1, 1};
|
|
scheduler.Current().Handle().copyImageToBuffer(
|
|
d16_storage_native.backing.image,
|
|
vk::ImageLayout::eTransferSrcOptimal, d16_storage_readback.buffer, 1,
|
|
&d16_storage_copy);
|
|
HostReadBarrier(d16_storage_readback.buffer, d16_storage_readback.size,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
const auto d16_storage_words = ReadBuffer(name, d16_storage_readback, 2);
|
|
std::array<uint16_t, 4> d16_storage_values{};
|
|
std::memcpy(d16_storage_values.data(), d16_storage_words.data(),
|
|
sizeof(d16_storage_values));
|
|
Require(
|
|
name, "D16 fallback depth/storage copy",
|
|
d16_storage_values == d16_fallback_values &&
|
|
d16_storage_image != d16_depth_image &&
|
|
texture_cache.GetImage(d16_storage_image).IsGpuModified(),
|
|
"D16 fallback reinterpreted four-byte host depth as two-byte color");
|
|
DestroyBuffer(&d16_storage_readback);
|
|
|
|
if (!texture_cache.GetImage(combined_destination_image).IsGpuModified()) {
|
|
texture_cache.MarkGpuWritten(combined_destination_image);
|
|
}
|
|
constexpr float stale_added_stencil_depth = 0.125f;
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
combined_destination.info.data.address, &stale_added_stencil_depth,
|
|
sizeof(stale_added_stencil_depth));
|
|
const auto depth_gc_tick = scheduler.CurrentTick();
|
|
TextureCacheTestAccess::ConfigureGarbageCollection(
|
|
texture_cache, std::array{combined_destination_image}, 81, 0);
|
|
texture_cache.RunGarbageCollector();
|
|
float depth_before_completion = 0.0f;
|
|
Libs::LibKernel::Memory::TryReadBacking(
|
|
combined_destination.info.data.address, &depth_before_completion,
|
|
sizeof(depth_before_completion));
|
|
const bool depth_image_retired = !TextureCacheTestAccess::Contains(
|
|
texture_cache, combined_destination_image);
|
|
const bool depth_proxy_retired = !texture_cache.FindImageFromRange(
|
|
base + added_stencil_offset, added_stencil_size, false);
|
|
Require(
|
|
name, "depth/stencil deferred pressure retirement",
|
|
depth_image_retired && depth_proxy_retired &&
|
|
scheduler.CurrentTick() == depth_gc_tick &&
|
|
depth_before_completion == stale_added_stencil_depth,
|
|
fmt::format(
|
|
"GC failed to retire/defer depth: image={} proxy={} tick={}/{} "
|
|
"depth={}/{}",
|
|
depth_image_retired, depth_proxy_retired, scheduler.CurrentTick(),
|
|
depth_gc_tick, depth_before_completion, stale_added_stencil_depth)
|
|
.c_str());
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
float depth_after_completion = 0.0f;
|
|
Libs::LibKernel::Memory::TryReadBacking(
|
|
combined_destination.info.data.address, &depth_after_completion,
|
|
sizeof(depth_after_completion));
|
|
Require(name, "depth/stencil depth-plane preservation",
|
|
depth_after_completion == added_stencil_depth_value,
|
|
"GC discarded the current depth plane of a depth/stencil "
|
|
"image");
|
|
|
|
constexpr size_t gc_depth_pair_count = 6;
|
|
constexpr uint64_t gc_depth_pair_offset = 0x350000;
|
|
constexpr uint64_t gc_depth_pair_stride = 0x1000;
|
|
std::array<ImageId, gc_depth_pair_count> gc_depth_images{};
|
|
std::array<ImageId, gc_depth_pair_count> gc_stencil_images{};
|
|
std::array<ImageId, gc_depth_pair_count * 2> gc_depth_lru{};
|
|
for (size_t index = 0; index < gc_depth_pair_count; index++) {
|
|
auto depth = MakeLinearDesc(
|
|
base + gc_depth_pair_offset + index * gc_depth_pair_stride,
|
|
sizeof(float), vk::Format::eD32SfloatS8Uint,
|
|
Prospero::BufferFormat::k32Float, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
depth.type = BindingType::DepthTarget;
|
|
depth.info.stencil = {depth.info.data.address +
|
|
gc_depth_pair_stride / 2,
|
|
sizeof(uint32_t)};
|
|
depth.view_info.format = vk::Format::eD32SfloatS8Uint;
|
|
depth.view_info.aspect =
|
|
vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil;
|
|
depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
gc_depth_images[index] = texture_cache.FindImage(depth);
|
|
TextureCacheTestAccess::AssociateStencil(
|
|
texture_cache, gc_depth_images[index], depth.info.stencil);
|
|
gc_stencil_images[index] = texture_cache.FindImageFromRange(
|
|
depth.info.stencil.address, depth.info.stencil.size, false);
|
|
gc_depth_lru[index * 2] = gc_depth_images[index];
|
|
gc_depth_lru[index * 2 + 1] = gc_stencil_images[index];
|
|
}
|
|
Require(
|
|
name, "depth/stencil GC fixtures",
|
|
std::ranges::all_of(gc_depth_images,
|
|
[](ImageId image) { return bool{image}; }) &&
|
|
std::ranges::all_of(gc_stencil_images,
|
|
[](ImageId image) { return bool{image}; }),
|
|
"failed to create six depth/stencil association pairs");
|
|
TextureCacheTestAccess::ConfigureGarbageCollection(
|
|
texture_cache, gc_depth_lru, 81, UINT64_MAX);
|
|
texture_cache.RunGarbageCollector();
|
|
bool gc_depth_budget = true;
|
|
for (size_t index = 0; index < gc_depth_pair_count; index++) {
|
|
const bool expected_live = index == gc_depth_pair_count - 1;
|
|
gc_depth_budget &=
|
|
TextureCacheTestAccess::Contains(
|
|
texture_cache, gc_depth_images[index]) == expected_live &&
|
|
TextureCacheTestAccess::Contains(
|
|
texture_cache, gc_stencil_images[index]) == expected_live;
|
|
}
|
|
Require(name, "depth/stencil GC traversal budget", gc_depth_budget,
|
|
"recursive association deletion stopped LRU traversal or "
|
|
"exceeded the ten-entry deletion budget");
|
|
|
|
constexpr uint64_t large_offset = 0x400000;
|
|
constexpr uint32_t large_width = 4096;
|
|
constexpr uint32_t large_height = 2047;
|
|
constexpr uint64_t large_size =
|
|
uint64_t{large_width} * large_height * sizeof(uint32_t);
|
|
static_assert(large_size < (32ull << 20));
|
|
std::memset(memory + large_offset, 0, static_cast<size_t>(large_size));
|
|
auto large_desc = MakeLinearDesc(
|
|
base + large_offset, large_size, vk::Format::eR8G8B8A8Unorm,
|
|
Prospero::BufferFormat::k8_8_8_8UNorm, Prospero::ImageType::kColor2D,
|
|
{large_width, large_height, 1}, 1, 4, 1);
|
|
const auto RunLargeReadback = [&](uint32_t clear_value) {
|
|
const auto image = texture_cache.FindImage(large_desc);
|
|
Require(name, "near-capacity image clear",
|
|
texture_cache.ClearImageFromBuffer(
|
|
command, large_desc.info.data.address,
|
|
large_desc.info.data.size, clear_value),
|
|
"failed to write the near-capacity readback source image");
|
|
const std::array<uint64_t, 3> sample_offsets{
|
|
0, large_size / 2, large_size - sizeof(uint32_t)};
|
|
constexpr uint32_t stale = 0;
|
|
for (const auto offset : sample_offsets) {
|
|
Libs::LibKernel::Memory::WriteBacking(base + large_offset + offset,
|
|
&stale, sizeof(stale));
|
|
}
|
|
TextureCacheTestAccess::ConfigureGarbageCollection(
|
|
texture_cache, std::array{image}, 81, 0);
|
|
const auto tick = scheduler.CurrentTick();
|
|
texture_cache.RunGarbageCollector();
|
|
const auto handle =
|
|
BufferCacheTestAccess::DownloadBuffer(resources.GetBufferCache())
|
|
.Handle();
|
|
Require(
|
|
name, "near-capacity deferred retirement",
|
|
!TextureCacheTestAccess::Contains(texture_cache, image) &&
|
|
scheduler.CurrentTick() == tick,
|
|
"near-capacity readback was rejected or synchronously submitted");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
bool content = true;
|
|
for (const auto offset : sample_offsets) {
|
|
uint32_t value = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(base + large_offset + offset,
|
|
&value, sizeof(value));
|
|
content &= value == clear_value;
|
|
}
|
|
Require(
|
|
name, "near-capacity readback content", content,
|
|
"near-capacity image readback did not publish its GPU contents");
|
|
return handle;
|
|
};
|
|
const auto large_download = RunLargeReadback(0xa5a5a5a5u);
|
|
const auto reused_large_download = RunLargeReadback(0x5a5a5a5au);
|
|
Require(name, "near-capacity shared download reuse",
|
|
large_download == reused_large_download,
|
|
"successive near-capacity image transfers replaced the shared "
|
|
"download buffer");
|
|
|
|
constexpr uint64_t tile_alias_offset = 0x2000000;
|
|
constexpr uint64_t tile_alias_size = 0x400000;
|
|
constexpr uint32_t tile_alias_extent = 1024;
|
|
std::memset(memory + tile_alias_offset, 0,
|
|
static_cast<size_t>(tile_alias_size));
|
|
auto render_target_alias = MakeLinearDesc(
|
|
base + tile_alias_offset, tile_alias_size, vk::Format::eR8G8B8A8Unorm,
|
|
Prospero::BufferFormat::k8_8_8_8UNorm, Prospero::ImageType::kColor2D,
|
|
{tile_alias_extent, tile_alias_extent, 1}, 1, 4, 1);
|
|
render_target_alias.type = BindingType::Storage;
|
|
render_target_alias.info.tile_mode = Prospero::TileMode::kRenderTarget;
|
|
render_target_alias.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto render_target_alias_image =
|
|
texture_cache.FindImage(render_target_alias);
|
|
|
|
auto standard_4kb_alias = render_target_alias;
|
|
standard_4kb_alias.type = BindingType::Texture;
|
|
standard_4kb_alias.info.tile_mode = Prospero::TileMode::kStandard4KB;
|
|
standard_4kb_alias.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto standard_4kb_alias_image =
|
|
texture_cache.FindImage(standard_4kb_alias);
|
|
auto repeated_standard_4kb_alias = standard_4kb_alias;
|
|
const auto repeated_standard_4kb_alias_image =
|
|
texture_cache.FindImage(repeated_standard_4kb_alias);
|
|
Require(
|
|
name, "equal-size tile-mode alias",
|
|
render_target_alias_image && standard_4kb_alias_image &&
|
|
standard_4kb_alias_image != render_target_alias_image &&
|
|
repeated_standard_4kb_alias_image == standard_4kb_alias_image &&
|
|
texture_cache.GetImage(render_target_alias_image)
|
|
.info.tile_mode == Prospero::TileMode::kRenderTarget &&
|
|
texture_cache.GetImage(standard_4kb_alias_image).info.tile_mode ==
|
|
Prospero::TileMode::kStandard4KB,
|
|
"equal address/size lookup reused an incompatible "
|
|
"tiled backing");
|
|
|
|
for (auto &output : ms_observer_outputs) {
|
|
DestroyBuffer(&output);
|
|
}
|
|
DestroyBuffer(&layered_readback);
|
|
DestroyBuffer(&bgra16_readback);
|
|
DestroyBuffer(&mixed_source_readback);
|
|
DestroyBuffer(&bridged_source_readback);
|
|
DestroyBuffer(&mip_formatted_readback);
|
|
DestroyBuffer(&mip_prefix_readback);
|
|
DestroyBuffer(&partial_cpu_refresh_readback);
|
|
DestroyBuffer(&mirror_cpu_readback);
|
|
DestroyBuffer(&exact_buffer_readback);
|
|
m_device.destroyDescriptorPool(observer_pool, nullptr);
|
|
m_device.destroyPipeline(observer_depth_pipeline, nullptr);
|
|
m_device.destroyPipelineLayout(observer_pipeline_layout, nullptr);
|
|
m_device.destroyDescriptorSetLayout(observer_set_layout, nullptr);
|
|
m_device.destroyShaderModule(ms_depth_module, nullptr);
|
|
|
|
constexpr uint64_t mapped_end = base + allocation_size;
|
|
constexpr uint64_t final_word_address = mapped_end - sizeof(uint32_t);
|
|
auto *final_word = reinterpret_cast<volatile uint32_t *>(final_word_address);
|
|
auto *final_byte = reinterpret_cast<volatile uint8_t *>(mapped_end - 1);
|
|
Require(name, "fault boundary containment",
|
|
resources.IsMapped(final_word_address, sizeof(uint32_t)) &&
|
|
!resources.IsMapped(final_word_address, sizeof(uint64_t)) &&
|
|
!resources.HandleFault(PageFaultAccess::Write, mapped_end) &&
|
|
!resources.HandleFault(PageFaultAccess::Read, mapped_end),
|
|
"fault handling changed the mapped range's exclusive end");
|
|
Require(name, "fault boundary setup",
|
|
resources.InvalidateMemory(final_word_address, sizeof(uint32_t)),
|
|
"the final mapped word could not be initialized");
|
|
*final_word = 0;
|
|
auto final_word_desc = MakeLinearDesc(
|
|
final_word_address, sizeof(uint32_t), vk::Format::eR32Uint,
|
|
Prospero::BufferFormat::k32UInt, Prospero::ImageType::kColor2D,
|
|
{1, 1, 1}, 1, 4, 1);
|
|
const auto final_word_image = texture_cache.FindImage(final_word_desc);
|
|
(void)texture_cache.FindTexture(final_word_image, final_word_desc);
|
|
Require(name, "fault boundary image tracking",
|
|
texture_cache.GetImage(final_word_image).IsTracked() &&
|
|
!texture_cache.GetImage(final_word_image).IsCpuDirty(),
|
|
"the final mapped word's image was not clean before its CPU store");
|
|
*final_word = 45;
|
|
Require(name, "fault boundary CPU store",
|
|
*final_word == 45 &&
|
|
texture_cache.GetImage(final_word_image).IsDefinitelyCpuDirty(),
|
|
"a valid four-byte store at the mapping end did not invalidate its image");
|
|
|
|
constexpr uint32_t final_gpu_value = 0xa1b2c3d4u;
|
|
auto &boundary_buffer_cache = resources.GetBufferCache();
|
|
(void)boundary_buffer_cache.ObtainBuffer(final_word_address, sizeof(uint32_t), true);
|
|
boundary_buffer_cache.FillBuffer(final_word_address, sizeof(uint32_t),
|
|
final_gpu_value, false);
|
|
Require(name, "fault boundary GPU ownership",
|
|
boundary_buffer_cache.HasGpuDirtyBytes(final_word_address, sizeof(uint32_t)) &&
|
|
!HostMemoryIsReadable(mapped_end - 1),
|
|
"the final mapped byte was not protected by GPU ownership");
|
|
const uint8_t final_gpu_byte = *final_byte;
|
|
Require(name, "fault boundary GPU readback",
|
|
final_gpu_byte == static_cast<uint8_t>(final_gpu_value >> 24u) &&
|
|
*final_word == final_gpu_value &&
|
|
!boundary_buffer_cache.HasGpuDirtyBytes(final_word_address, sizeof(uint32_t)),
|
|
"a final-byte CPU read did not publish the GPU-owned final word");
|
|
|
|
resources.SetGpu(nullptr);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
LibKernel::Memory::InstallGpuResources(nullptr);
|
|
}
|
|
context.ShutdownGpu();
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"cache direct-memory mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"cache direct-memory allocation release failed");
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckBgra16Readback() {
|
|
constexpr const char *name = "Bgra16Readback";
|
|
constexpr uintptr_t base = 0x0000000204000000ull;
|
|
constexpr uint64_t allocation_size = 0x200000;
|
|
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
|
|
constexpr auto tile = Prospero::TileMode::kRenderTarget;
|
|
EnsureRuntimeContext();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
|
"BGRA16 direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_size) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"BGRA16 fixed mapping failed");
|
|
|
|
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
{
|
|
GpuResourceManager resources(m_runtime_context, scheduler);
|
|
resources.MapMemory(base, allocation_size);
|
|
const uint32_t pitch = TileGetTexturePitch(format, 1, tile);
|
|
TileSizeAlign total{};
|
|
TileSizeOffset mip{};
|
|
TilePaddedSize padded{};
|
|
TileGetTextureSize(format, 1, 1, 1, tile, &total, &mip, &padded);
|
|
Require(name, "tiled layout",
|
|
total.size >= 8 && total.size <= allocation_size,
|
|
"BGRA16 tiled layout exceeds its guest allocation");
|
|
std::memset(mapped, 0x5a, total.size);
|
|
|
|
ImageDesc desc{};
|
|
desc.type = BindingType::RenderTarget;
|
|
desc.info.data = {base, total.size};
|
|
desc.info.pixel_format = vk::Format::eR16G16B16A16Sfloat;
|
|
desc.info.guest_format = format;
|
|
desc.info.type = Prospero::ImageType::kColor2D;
|
|
desc.info.extent = {1, 1, 1};
|
|
desc.info.resources = {1, 1};
|
|
desc.info.pitch = pitch;
|
|
desc.info.bytes_per_block = 8;
|
|
desc.info.samples = 1;
|
|
desc.info.tile_mode = tile;
|
|
desc.info.bgra16 = true;
|
|
desc.info.mip_layout[0] = {mip.offset, mip.size, pitch, 1};
|
|
desc.view_info.format = desc.info.pixel_format;
|
|
desc.view_info.type = vk::ImageViewType::e2D;
|
|
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
|
|
auto &cache = resources.GetTextureCache();
|
|
const auto id = cache.FindImage(desc);
|
|
auto &image = cache.GetImage(id);
|
|
image.Transit(vk::ImageLayout::eTransferDstOptimal,
|
|
vk::AccessFlagBits2::eTransferWrite, {},
|
|
scheduler.Current().Handle());
|
|
vk::ClearColorValue clear{};
|
|
clear.float32[0] = 1.0f;
|
|
clear.float32[1] = 2.0f;
|
|
clear.float32[2] = 3.0f;
|
|
clear.float32[3] = 4.0f;
|
|
const vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0,
|
|
1, 0, 1};
|
|
scheduler.Current().Handle().clearColorImage(
|
|
image.backing.image, vk::ImageLayout::eTransferDstOptimal, clear,
|
|
range);
|
|
cache.MarkGpuWritten(id);
|
|
Require(name, "guest readback queue",
|
|
TextureCacheTestAccess::TryDownload(cache, id),
|
|
"tiled BGRA16 guest readback was rejected");
|
|
auto mirror = resources.GetBufferCache().ObtainBuffer(base, total.size,
|
|
false, true);
|
|
Require(name, "mirror owner", mirror.first != nullptr,
|
|
"tiled BGRA16 mirror has no BufferCache owner");
|
|
auto mirror_readback = CreateHostBuffer(
|
|
name, 8, vk::BufferUsageFlagBits::eTransferDst, {0, 0});
|
|
const vk::BufferCopy copy{mirror.second, 0, 8};
|
|
scheduler.Current().Handle().copyBuffer(mirror.first->Handle(),
|
|
mirror_readback.buffer, 1, ©);
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = mirror_readback.buffer;
|
|
barrier.size = mirror_readback.size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0,
|
|
nullptr);
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
|
|
std::vector<uint8_t> backing(total.size);
|
|
Require(name, "guest backing",
|
|
Libs::LibKernel::Memory::TryReadBacking(base, backing.data(),
|
|
backing.size()),
|
|
"tiled BGRA16 guest backing is unreadable");
|
|
const std::array<uint16_t, 4> expected{0x4200u, 0x4000u, 0x3c00u,
|
|
0x4400u};
|
|
std::array<uint16_t, 4> observed{};
|
|
std::memcpy(observed.data(), backing.data(), sizeof(observed));
|
|
Require(name, "guest component order",
|
|
observed == expected &&
|
|
std::all_of(backing.begin() + sizeof(observed), backing.end(),
|
|
[](uint8_t value) { return value == 0x5a; }),
|
|
"tiled BGRA16 guest readback changed component order or padding");
|
|
Require(name, "mirror component order",
|
|
ReadBuffer(name, mirror_readback, 2) ==
|
|
std::vector<u32>{0x40004200u, 0x44003c00u},
|
|
"tiled BGRA16 Buffer mirror changed guest component order");
|
|
DestroyBuffer(&mirror_readback);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
Require(name, "unmap",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"BGRA16 fixed mapping release failed");
|
|
Require(name, "release",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"BGRA16 direct-memory release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckRenderExecutorColorDiscovery() {
|
|
constexpr const char *name = "RenderExecutorColorDiscovery";
|
|
constexpr uintptr_t base = 0x0000000203c00000ull;
|
|
constexpr uint32_t width = 512;
|
|
constexpr uint32_t height = 1;
|
|
constexpr uint32_t bytes_per_element = 4;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
EnsureRuntimeContext();
|
|
|
|
const auto pitch =
|
|
TileGetRenderTargetPitch(width, bytes_per_element, 0);
|
|
TileSizeAlign layout{};
|
|
Require(name, "tile layout",
|
|
pitch != 0 && TileGetRenderTargetSize(
|
|
width, height, pitch, bytes_per_element, layout, 0),
|
|
"1D render-target layout is unavailable");
|
|
struct TargetCase {
|
|
uint32_t width, height, dimension, pitch;
|
|
uint64_t size;
|
|
Prospero::TileMode tile;
|
|
};
|
|
const std::array cases{
|
|
TargetCase{width, height, 0, pitch, layout.size,
|
|
Prospero::TileMode::kRenderTarget},
|
|
// PPSA28068: the 1440x720 agreement target must alias its sampled texture.
|
|
TargetCase{1440, 720, 1, 1472, 0x40b000, Prospero::TileMode::kLinear},
|
|
};
|
|
constexpr uint64_t allocation_size = 0x410000;
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"color direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"color fixed mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
|
|
for (const auto &target : cases) {
|
|
const auto width = target.width;
|
|
const auto height = target.height;
|
|
const bool is_1d = target.dimension == 0;
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetColorBase(0, {.addr = base});
|
|
registers.SetColorInfo(
|
|
0, {.format = Prospero::ChannelLayout::k8_8_8_8,
|
|
.channel_type = Prospero::ChannelType::kSrgb,
|
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
|
registers.SetColorAttrib2(0, {.height = height - 1, .width = width - 1});
|
|
registers.SetColorAttrib3(0,
|
|
{.tile_mode = target.tile,
|
|
.dimension = target.dimension,
|
|
.metadata_pipe_aligned = true});
|
|
registers.SetRenderTargetMask(0x0f);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
|
|
RenderColorInfo color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), color, 0);
|
|
const auto attachment =
|
|
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
|
const auto &image = texture_cache.GetImage(color.image_id);
|
|
RenderDepthInfo no_depth{};
|
|
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &color, 1, no_depth);
|
|
scheduler.Current().BeginRendering(rendering);
|
|
scheduler.Current().EndRendering();
|
|
Require(name, "captured color target",
|
|
color.image_id && attachment != nullptr &&
|
|
rendering.color_attachments[0].image_view != nullptr &&
|
|
color.desc.info.type == (is_1d ? Prospero::ImageType::kColor1D
|
|
: Prospero::ImageType::kColor2D) &&
|
|
color.desc.info.extent == vk::Extent3D{width, height, 1} &&
|
|
color.desc.info.resources == ImageSubresources{1, 1} &&
|
|
color.desc.info.pitch == target.pitch &&
|
|
color.desc.info.data.size == target.size &&
|
|
color.desc.info.mip_layout[0].offset == 0 &&
|
|
color.desc.info.mip_layout[0].size == target.size &&
|
|
color.desc.view_info.type == (is_1d ? vk::ImageViewType::e1D
|
|
: vk::ImageViewType::e2D) &&
|
|
image.backing.image_type == (is_1d ? vk::ImageType::e1D
|
|
: vk::ImageType::e2D) &&
|
|
rendering.width == width && rendering.height == height &&
|
|
rendering.num_layers == 1 &&
|
|
rendering.num_color_attachments == 1,
|
|
"color attachment lost its dimensions or padded guest layout");
|
|
|
|
if (target.tile == Prospero::TileMode::kLinear) {
|
|
vk::ClearValue clear{};
|
|
clear.color.float32 = std::array{1.0f, 0.0f, 1.0f, 1.0f};
|
|
TextureCacheTestAccess::ClearImage(
|
|
texture_cache, scheduler.Current(), color.image_id,
|
|
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear);
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
ShaderTextureResource descriptor{{
|
|
static_cast<uint32_t>(base >> 8u),
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k8_8_8_8Srgb) << 20u) |
|
|
(((width - 1u) & 3u) << 30u),
|
|
((width - 1u) >> 2u) | ((height - 1u) << 14u),
|
|
DstSel(4, 5, 6, 7) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u),
|
|
0, 0x00700000u, 0, 0}};
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 8;
|
|
std::copy_n(descriptor.fields, 8, value.dwords.begin());
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = true;
|
|
const auto sampled =
|
|
RenderExecutorTestAccess::ResolveTexture(executor, resource, value);
|
|
Require(name, "linear target sampled alias",
|
|
sampled.image_id == color.image_id &&
|
|
sampled.desc.info.pitch == target.pitch &&
|
|
sampled.desc.info.data.size == target.size &&
|
|
sampled.desc.info.mip_layout == color.desc.info.mip_layout &&
|
|
texture_cache.FindTexture(sampled.image_id, sampled.desc) != nullptr,
|
|
"sampling the linear agreement target created a second image");
|
|
Require(name, "linear target sampled contents",
|
|
ReadCachedTexel(name, context, sampled.image_id,
|
|
{1439, 719, 0}) == std::vector<u32>{0xffff00ffu},
|
|
"the sampled linear target lost its GPU-written final texel");
|
|
}
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"color direct mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"color direct-memory allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckRenderExecutorColorVolumeDiscovery() {
|
|
constexpr const char *name = "RenderExecutorColorVolumeDiscovery";
|
|
constexpr uintptr_t base = 0x0000000203e00000ull;
|
|
constexpr uint64_t color_size = 0x200000;
|
|
constexpr uint64_t metadata_size = 0x20000;
|
|
constexpr uint64_t allocation_size = color_size + metadata_size;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
EnsureRuntimeContext();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"color-volume direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"color-volume fixed mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
constexpr uint64_t slice_size = 0x10000;
|
|
std::memset(static_cast<uint8_t *>(mapped) + 31 * slice_size, 0x5a,
|
|
slice_size);
|
|
|
|
{
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetColorBase(0, {.addr = base});
|
|
registers.SetColorInfo(
|
|
0, {.dcc_compression_enable = true,
|
|
.format = Prospero::ChannelLayout::k10_10_10_2,
|
|
.channel_type = Prospero::ChannelType::kUNorm,
|
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
|
registers.SetColorAttrib2(0, {.height = 31, .width = 31});
|
|
registers.SetColorAttrib3(0,
|
|
{.depth = 31,
|
|
.tile_mode = Prospero::TileMode::kRenderTarget,
|
|
.dimension = 2,
|
|
.metadata_pipe_aligned = true});
|
|
constexpr uint64_t dcc_address = base + color_size;
|
|
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
|
registers.SetColorClearWord0(0, {.word0 = 0});
|
|
registers.SetColorClearWord1(0, {.word1 = 0});
|
|
registers.SetRenderTargetMask(0x0f);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
|
|
Require(name, "deferred DCC clear",
|
|
!texture_cache.IsMeta(dcc_address),
|
|
"a pre-registration DCC clear was not deferred");
|
|
|
|
RenderColorInfo color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), color, 0);
|
|
const auto attachment =
|
|
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
|
const auto &image = texture_cache.GetImage(color.image_id);
|
|
uint32_t dcc_clear_value = 0xffffffffu;
|
|
Require(
|
|
name, "captured 3D target",
|
|
color.image_id && attachment != nullptr &&
|
|
color.desc.info.type == Prospero::ImageType::kColor3D &&
|
|
color.desc.info.extent == vk::Extent3D{32, 32, 32} &&
|
|
color.desc.info.resources == ImageSubresources{1, 1} &&
|
|
color.desc.info.pitch == 128 &&
|
|
color.desc.info.data.size == color_size &&
|
|
color.desc.info.mip_layout[0].size == 0x10000 &&
|
|
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
|
color.desc.info.metadata.range.address == dcc_address &&
|
|
texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) &&
|
|
dcc_clear_value == 0 && !texture_cache.ClearMeta(dcc_address) &&
|
|
color.desc.view_info.type == vk::ImageViewType::e2D &&
|
|
color.desc.view_info.layer_count == 1 &&
|
|
image.backing.image_type == vk::ImageType::e3D &&
|
|
static_cast<bool>(image.backing.flags &
|
|
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
|
image.usage.render_target && image.IsGpuModified(),
|
|
"dimension=2/depth=31 did not create the expected 32x32x32 "
|
|
"backing and 2D "
|
|
"attachment slice");
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
registers.SetColorView(
|
|
0, {.base_array_slice_index = 7, .last_array_slice_index = 7});
|
|
RenderColorInfo sliced_color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), sliced_color, 0);
|
|
RenderDepthInfo no_depth{};
|
|
const auto sliced_rendering =
|
|
RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &sliced_color, 1, no_depth);
|
|
Require(
|
|
name, "3D slice transition",
|
|
sliced_color.image_id == color.image_id &&
|
|
sliced_rendering.color_attachments[0].image_view != nullptr &&
|
|
sliced_color.desc.view_info.base_layer == 7 &&
|
|
sliced_rendering.num_color_attachments == 1 &&
|
|
sliced_rendering.num_layers == 1 &&
|
|
!texture_cache.IsMetaCleared(dcc_address, 7),
|
|
"a nonzero 3D attachment slice was treated as a Vulkan array layer");
|
|
Require(name, "3D slice clear and preservation",
|
|
ReadCachedTexel(name, context, color.image_id, {0, 0, 7}) ==
|
|
std::vector<u32>{0} &&
|
|
ReadCachedTexel(name, context, color.image_id, {0, 0, 31}) ==
|
|
std::vector<u32>{0x5a5a5a5a},
|
|
"clearing one volume slice changed untouched slice contents");
|
|
auto storage_desc = color.desc;
|
|
storage_desc.type = BindingType::Storage;
|
|
storage_desc.info.metadata = {};
|
|
storage_desc.info.guest_format = Prospero::BufferFormat::k10_10_10_2UNorm;
|
|
storage_desc.view_info.type = vk::ImageViewType::e3D;
|
|
storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
const auto storage_id = texture_cache.FindImage(storage_desc);
|
|
const auto storage_view =
|
|
texture_cache.FindTexture(storage_id, storage_desc);
|
|
const auto &shared_image = texture_cache.GetImage(storage_id);
|
|
Require(name, "storage alias reuse",
|
|
storage_id == color.image_id && storage_view != nullptr &&
|
|
shared_image.IsGpuModified() &&
|
|
shared_image.usage.render_target,
|
|
"the matching 3D storage binding did not reuse the live "
|
|
"render-target image");
|
|
|
|
Require(
|
|
name, "volume readback queue",
|
|
TextureCacheTestAccess::TryDownload(texture_cache, storage_id),
|
|
"the 3D render target could not be queued for guest-layout readback");
|
|
auto mirror = resources.GetBufferCache().ObtainBuffer(
|
|
base, color_size, false, true);
|
|
Require(name, "volume mirror", mirror.first != nullptr,
|
|
"the 3D render-target readback has no BufferCache owner");
|
|
auto slice_probe =
|
|
CreateHostBuffer(name, 4, vk::BufferUsageFlagBits::eTransferDst, {0});
|
|
const vk::BufferCopy copy{mirror.second + 31 * slice_size, 0, 4};
|
|
scheduler.Current().Handle().copyBuffer(mirror.first->Handle(),
|
|
slice_probe.buffer, 1, ©);
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = slice_probe.buffer;
|
|
barrier.size = slice_probe.size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0,
|
|
nullptr);
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
Require(name, "volume Z transfer",
|
|
ReadBuffer(name, slice_probe, 1) == std::vector<u32>{0x5a5a5a5a},
|
|
"render-target upload/readback lost the final Z slice");
|
|
DestroyBuffer(&slice_probe);
|
|
|
|
dcc_clear_value = 0;
|
|
texture_cache.TrackDccFill(dcc_address, metadata_size, 0xffffffffu);
|
|
const bool uncompressed =
|
|
!texture_cache.IsMetaCleared(dcc_address, 7, &dcc_clear_value) &&
|
|
dcc_clear_value == 0xffffffffu;
|
|
texture_cache.TrackDccFill(dcc_address, metadata_size, 0);
|
|
Require(
|
|
name, "registered DCC code state",
|
|
uncompressed && texture_cache.IsMetaCleared(dcc_address, 7),
|
|
"DCC fill codes were not classified as clear or uncompressed");
|
|
|
|
for (const uint32_t base_slice : {0u, 7u}) {
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
registers.SetColorView(0, {.base_array_slice_index = base_slice,
|
|
.last_array_slice_index = 32});
|
|
RenderColorInfo volume_color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), volume_color, 0);
|
|
const auto volume_rendering =
|
|
RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &volume_color, 1, no_depth);
|
|
Require(name, "3D export bound exceeds depth",
|
|
volume_color.image_id == color.image_id &&
|
|
volume_color.desc.info.extent == vk::Extent3D{32, 32, 32} &&
|
|
volume_color.desc.info.data.size == color_size &&
|
|
volume_color.desc.view_info.type == vk::ImageViewType::e2DArray &&
|
|
volume_color.desc.view_info.base_layer == base_slice &&
|
|
volume_color.desc.view_info.layer_count == 32 - base_slice &&
|
|
volume_rendering.num_layers == 32 - base_slice &&
|
|
registers.GetRenderTarget(0).view.last_array_slice_index == 32,
|
|
"3D export bounds changed the backing or exceeded its attachment depth");
|
|
scheduler.Current().BeginRendering(volume_rendering);
|
|
scheduler.Current().EndRendering();
|
|
}
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"color-volume direct mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"color-volume direct-memory allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
std::vector<u32> ReadCachedTexel(const char *name, RenderContext &context,
|
|
ImageId id, vk::Offset3D offset = {},
|
|
vk::Extent3D extent = {1, 1, 1}) {
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
auto &image = context.GetTextureCache().GetImage(id);
|
|
const auto bytes = image.info.bytes_per_block * extent.width *
|
|
extent.height * extent.depth;
|
|
auto probe = CreateHostBuffer(name, bytes,
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
scheduler.Current().EndRendering();
|
|
image.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead, {},
|
|
scheduler.Current().Handle());
|
|
vk::BufferImageCopy copy{};
|
|
copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
|
: vk::ImageAspectFlagBits::eColor, 0, 0, 1};
|
|
copy.imageOffset = offset;
|
|
copy.imageExtent = extent;
|
|
scheduler.Current().Handle().copyImageToBuffer(
|
|
image.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
|
probe.buffer, 1, ©);
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = probe.buffer;
|
|
barrier.size = probe.size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0,
|
|
nullptr);
|
|
scheduler.Finish();
|
|
auto result = ReadBuffer(name, probe, bytes / 4);
|
|
DestroyBuffer(&probe);
|
|
return result;
|
|
}
|
|
|
|
void CheckRenderExecutorDccFixedClearFloat() {
|
|
constexpr const char *name = "RenderExecutorDccFixedClearFloat";
|
|
constexpr uintptr_t base = 0x0000000204100000ull;
|
|
constexpr uint64_t allocation_size = 0x200000;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
constexpr uint64_t dcc_address = base + 0x100000;
|
|
struct FillCase {
|
|
uint32_t fill;
|
|
std::array<uint32_t, 2> texel;
|
|
};
|
|
constexpr std::array cases{
|
|
FillCase{0x40404040u, {0, 0x3c000000u}},
|
|
FillCase{0x80808080u, {0x3c003c00u, 0x00003c00u}},
|
|
};
|
|
EnsureRuntimeContext();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"fixed-clear direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"fixed-clear direct mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
|
|
for (const auto &fill_case : cases) {
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetColorBase(0, {.addr = base});
|
|
registers.SetColorInfo(
|
|
0, {.dcc_compression_enable = true,
|
|
.format = Prospero::ChannelLayout::k16_16_16_16,
|
|
.channel_type = Prospero::ChannelType::kFloat,
|
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
|
registers.SetColorAttrib2(0, {.height = 255, .width = 255});
|
|
registers.SetColorAttrib3(0,
|
|
{.tile_mode = Prospero::TileMode::kRenderTarget,
|
|
.dimension = 1,
|
|
.metadata_pipe_aligned = true});
|
|
registers.SetColorDccAddr(0, {.addr = dcc_address});
|
|
registers.SetColorClearWord0(0, {.word0 = 0});
|
|
registers.SetColorClearWord1(0, {.word1 = 0});
|
|
registers.SetRenderTargetMask(0x0f);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
texture_cache.TrackDccFill(dcc_address, 0x1000, fill_case.fill);
|
|
Require(name, "deferred fixed-code fill",
|
|
!texture_cache.IsMeta(dcc_address),
|
|
"a pre-registration DCC fill was not deferred");
|
|
|
|
RenderColorInfo color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), color, 0);
|
|
RenderDepthInfo no_depth{};
|
|
const auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &color, 1, no_depth);
|
|
Require(name, "fixed clear on a float target",
|
|
color.image_id &&
|
|
color.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
|
color.desc.info.metadata.range.address == dcc_address &&
|
|
rendering.num_color_attachments == 1 &&
|
|
!texture_cache.IsMetaCleared(dcc_address, 0),
|
|
"a DCC fixed clear code was not materialised on an RGBA16F "
|
|
"target");
|
|
Require(name, "GPU float clear value",
|
|
ReadCachedTexel(name, context, color.image_id) ==
|
|
std::vector<u32>(fill_case.texel.begin(), fill_case.texel.end()),
|
|
"RGBA16F native image did not receive the fixed clear value");
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"fixed-clear direct mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"fixed-clear direct-memory allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckSampledDccClear() {
|
|
constexpr const char *name = "SampledDccClear";
|
|
constexpr uintptr_t base = 0x0000000205000000ull;
|
|
constexpr uint64_t color_size = 0x3fc0000;
|
|
constexpr uint64_t metadata_size = 0x48000;
|
|
constexpr uint64_t dcc_address = base + color_size;
|
|
constexpr uint64_t allocation_size = 0x4020000;
|
|
constexpr uint64_t alignment = 0x10000;
|
|
EnsureRuntimeContext();
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, alignment, 0, &direct_offset) == 0,
|
|
"captured scene allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset, alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"captured scene mapping failed");
|
|
for (const auto initial_size : {metadata_size - 0x1000, metadata_size}) {
|
|
std::memset(mapped, 0x5a, color_size);
|
|
std::memset(reinterpret_cast<void *>(dcc_address), 0x40, metadata_size);
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &resources = context.GetGpuResources();
|
|
auto &cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
cache.TrackDccFill(dcc_address, initial_size, 0x40404040u);
|
|
Require(name, "deferred metadata fill",
|
|
!cache.IsMeta(dcc_address),
|
|
"unknown metadata was consumed before descriptor discovery");
|
|
|
|
// PPSA19244's first sampled scene descriptor, with relocated allocations.
|
|
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
|
|
0xc4700000u, 0x021bc3bfu, 0x91b00facu, 0, 0x00700000u,
|
|
0x006b0000u | (static_cast<uint32_t>(dcc_address >> 8u) << 24u),
|
|
static_cast<uint32_t>(dcc_address >> 16u)}};
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 8;
|
|
std::copy_n(descriptor.fields, 8, value.dwords.begin());
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = true;
|
|
auto binding = RenderExecutorTestAccess::ResolveTexture(executor, resource, value);
|
|
Require(name, "sampled metadata discovery",
|
|
binding.desc.info.extent == vk::Extent3D{3840, 2160, 1} &&
|
|
binding.desc.info.data.size == color_size &&
|
|
binding.desc.info.metadata.kind == ImageMetadataKind::Dcc &&
|
|
binding.desc.info.metadata.range.address == dcc_address &&
|
|
binding.desc.info.metadata.range.size == metadata_size,
|
|
"captured sampled descriptor lost its DCC allocation");
|
|
Require(name, "sampled view", cache.FindTexture(binding.image_id, binding.desc) != nullptr,
|
|
"captured scene did not produce a sampled view");
|
|
if (initial_size != metadata_size) {
|
|
Require(name, "partial metadata coverage",
|
|
ReadCachedTexel(name, context, binding.image_id) ==
|
|
std::vector<u32>{0x5a5a5a5au, 0x5a5a5a5au},
|
|
"an incomplete metadata fill cleared the whole scene");
|
|
cache.TrackDccFill(dcc_address, metadata_size, 0x40404040u);
|
|
(void)cache.FindTexture(binding.image_id, binding.desc);
|
|
}
|
|
Require(name, "opaque black before first sample",
|
|
ReadCachedTexel(name, context, binding.image_id, {3839, 2159, 0}) ==
|
|
std::vector<u32>{0, 0x3c000000u} &&
|
|
!cache.IsMetaCleared(dcc_address, 0),
|
|
"sampling read stale color bytes instead of the DCC opaque black clear");
|
|
cache.TrackDccFill(dcc_address, metadata_size, 0x80808080u);
|
|
(void)cache.FindTexture(binding.image_id, binding.desc);
|
|
Require(name, "later sampled clear",
|
|
ReadCachedTexel(name, context, binding.image_id) ==
|
|
std::vector<u32>{0x3c003c00u, 0x00003c00u},
|
|
"an existing sampled image retained the previous DCC clear");
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
Require(name, "unmap",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"captured scene mapping release failed");
|
|
Require(name, "release",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"captured scene allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckPackedTextureComponents() {
|
|
constexpr const char *name = "PackedTextureComponents";
|
|
constexpr uintptr_t base = 0x0000000204200000ull;
|
|
constexpr uint64_t allocation_size = 0x10000;
|
|
struct FormatCase {
|
|
Prospero::BufferFormat format;
|
|
std::array<uint16_t, 4> words;
|
|
std::array<std::array<uint32_t, 4>, 4> channels;
|
|
std::array<uint32_t, 4> maxima;
|
|
};
|
|
// Packed format channels start at the least significant bit.
|
|
// PPSA24515's blue, white, and masked black exposed the 5551 reversal.
|
|
constexpr std::array cases{
|
|
FormatCase{Prospero::BufferFormat::k5_5_5_1UNorm,
|
|
{0x7c00, 0x7fff, 0x8000, 0x8c41},
|
|
{{{0, 0, 31, 0}, {31, 31, 31, 0}, {0, 0, 0, 1}, {1, 2, 3, 1}}},
|
|
{31, 31, 31, 1}},
|
|
FormatCase{Prospero::BufferFormat::k4_4_4_4UNorm,
|
|
{0x000f, 0x00f0, 0x0f00, 0x4321},
|
|
{{{15, 0, 0, 0}, {0, 15, 0, 0}, {0, 0, 15, 0}, {1, 2, 3, 4}}},
|
|
{15, 15, 15, 15}},
|
|
};
|
|
const auto argb = TextureGetComponentMapping(DstSel(5, 1, 7, 0),
|
|
Prospero::ColorMappingArgb);
|
|
Require(name, "inverse mapping",
|
|
argb == vk::ComponentMapping{vk::ComponentSwizzle::eB,
|
|
vk::ComponentSwizzle::eOne, vk::ComponentSwizzle::eR,
|
|
vk::ComponentSwizzle::eZero},
|
|
"cyclic host mapping reordered output slots or descriptor constants");
|
|
const auto target = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k1_5_5_5, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Require(name, "1555 render target",
|
|
target.format == vk::Format::eR5G5B5A1UnormPack16 &&
|
|
target.export_mapping == Prospero::ColorMappingAbgr,
|
|
"shared format resolution changed the existing 1555 render target");
|
|
EnsureRuntimeContext();
|
|
int64_t direct_offset = -1;
|
|
Require(name, "allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
|
"packed texture allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
|
|
"packed texture mapping failed");
|
|
for (const auto &format : cases) {
|
|
std::memset(mapped, 0, allocation_size);
|
|
std::memcpy(mapped, format.words.data(), sizeof(format.words));
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &resources = context.GetGpuResources();
|
|
auto &cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
for (const auto swizzle : {DstSel(4, 5, 6, 7), DstSel(7, 1, 4, 0)}) {
|
|
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
|
|
(static_cast<uint32_t>(format.format) << 20u) | (3u << 30u), 0,
|
|
swizzle | (static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u),
|
|
0, 0x00700000u, 0, 0}};
|
|
TestCase test;
|
|
test.name = name;
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = swizzle;
|
|
std::copy_n(descriptor.fields, 8, test.user_data.begin());
|
|
test.user_data[50] = 16 * sizeof(uint32_t);
|
|
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_LOAD,
|
|
ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM};
|
|
for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) {
|
|
AppendVMovU32(&test.code, 20, pixel);
|
|
AppendVMovU32(&test.code, 21, 0);
|
|
test.code.push_back(EncodeMimg0(0x00, 0xf));
|
|
test.code.push_back(EncodeMimg1(0, 20));
|
|
for (uint32_t channel = 0; channel < 4; ++channel) {
|
|
AppendStoreVgpr(&test.code, channel, pixel * 4 + channel);
|
|
}
|
|
}
|
|
AppendEnd(&test.code);
|
|
const auto compiled = CompileCase(test, SubgroupSize());
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 8;
|
|
std::copy_n(descriptor.fields, 8, value.dwords.begin());
|
|
const auto binding = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, compiled.program.info.images.at(0), value);
|
|
Image sampled;
|
|
sampled.view = cache.FindTexture(binding.image_id, binding.desc);
|
|
sampled.layout = cache.GetImage(binding.image_id).backing.state.layout;
|
|
scheduler.Finish();
|
|
auto output = CreateStorageBuffer(name, {}, 16);
|
|
Dispatch(test, compiled, output, nullptr, &sampled);
|
|
const auto actual = ReadBuffer(name, output, 16);
|
|
for (uint32_t pixel = 0; pixel < format.words.size(); ++pixel) {
|
|
for (uint32_t channel = 0; channel < 4; ++channel) {
|
|
const auto selector = GetDstSel(swizzle, channel);
|
|
const auto maximum = selector < 4 ? 1u : format.maxima[selector - 4];
|
|
const auto expected = selector < 4 ? selector : format.channels[pixel][selector - 4];
|
|
const float observed = std::bit_cast<float>(actual[pixel * 4 + channel]);
|
|
// Recover the source bits without testing the host's UNORM rounding precision.
|
|
const bool matches = maximum == 1 ? observed == expected
|
|
: observed >= 0.0f && observed <= 1.0f &&
|
|
std::round(observed * maximum) == expected;
|
|
if (!matches) {
|
|
std::ostringstream message;
|
|
message << "format=" << static_cast<uint32_t>(format.format)
|
|
<< " swizzle=" << swizzle << " word=" << format.words[pixel]
|
|
<< " channel=" << channel << " expected=" << expected
|
|
<< " actual=" << observed;
|
|
Fail(name, "GPU components", message.str());
|
|
}
|
|
}
|
|
}
|
|
DestroyBuffer(&output);
|
|
}
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
Require(name, "release",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0 &&
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"packed texture backing release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckComparisonDepthTexture() {
|
|
constexpr const char *name = "ComparisonDepthTexture";
|
|
constexpr uintptr_t base = 0x0000000204200000ull;
|
|
constexpr uint64_t allocation_size = 0x20000;
|
|
constexpr uint64_t ordinary_address = base + 0x10000;
|
|
constexpr std::array<uint16_t, 6> depths{0, 13107, 26214, 39321, 52428, 65535};
|
|
constexpr std::array<float, 5> references{0.1f, 0.3f, 0.5f, 0.7f, 0.9f};
|
|
EnsureRuntimeContext();
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
|
"comparison texture allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_size) == 0 && mapped == reinterpret_cast<void *>(base),
|
|
"comparison texture mapping failed");
|
|
// Sea of Stars' 1D comparison texture and the existing six-face cube.
|
|
const auto check_layout = [&](bool cube) {
|
|
const uint32_t layers = cube ? depths.size() : 1;
|
|
const auto depth_at = [&](uint32_t layer, bool reversed) {
|
|
const auto index = cube ? layer : 2u;
|
|
return depths[reversed ? depths.size() - 1u - index : index];
|
|
};
|
|
std::memset(mapped, 0, allocation_size);
|
|
for (uint32_t face = 0; face < layers; face++) {
|
|
const auto depth = depth_at(face, false);
|
|
for (const auto address : {base, ordinary_address}) {
|
|
std::memcpy(reinterpret_cast<void *>(address + face * 0x100),
|
|
&depth, sizeof(depth));
|
|
}
|
|
}
|
|
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &resources = context.GetGpuResources();
|
|
auto &cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
ShaderTextureResource descriptor{{static_cast<uint32_t>(base >> 8u),
|
|
0x00700000u, 0, cube ? 0xb0100204u : 0x81800924u,
|
|
layers - 1u, 0x00700000u, 0, 0}};
|
|
ShaderSamplerResource sampler_descriptor{{1u << 12u, 0, 1u << 24u, 0}};
|
|
TestCase test;
|
|
test.name = name;
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = descriptor.DstSelXYZW();
|
|
std::copy_n(descriptor.fields, 8, test.user_data.begin());
|
|
std::copy_n(sampler_descriptor.fields, 4, test.user_data.begin() + 8);
|
|
test.user_data[50] = layers * references.size() * sizeof(uint32_t);
|
|
test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_SAMPLE,
|
|
ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM};
|
|
test.required_spirv = {"OpImageSampleDrefExplicitLod"};
|
|
std::vector<uint32_t> promoted_expected;
|
|
for (uint32_t face = 0; face < layers; face++) {
|
|
for (const auto reference : references) {
|
|
AppendVMovLiteral(&test.code, 20, std::bit_cast<uint32_t>(reference));
|
|
AppendVMovLiteral(&test.code, 21,
|
|
std::bit_cast<uint32_t>(cube ? 1.5f : 0.5f));
|
|
if (cube) {
|
|
AppendVMovLiteral(&test.code, 22, std::bit_cast<uint32_t>(1.5f));
|
|
AppendVMovLiteral(&test.code, 23,
|
|
std::bit_cast<uint32_t>(static_cast<float>(face)));
|
|
}
|
|
test.code.push_back(EncodeMimg0(0x2f, 1, 0, false, cube ? 3 : 0));
|
|
test.code.push_back(EncodeMimg1(0, 20, 0, 2));
|
|
AppendStoreVgpr(&test.code, 0, static_cast<uint32_t>(test.expected.size()));
|
|
test.expected.push_back(
|
|
reference < static_cast<float>(depth_at(face, false)) / 65535.f
|
|
? 0x3f800000u : 0u);
|
|
promoted_expected.push_back(
|
|
reference < static_cast<float>(depth_at(face, true)) / 65535.f
|
|
? 0x3f800000u : 0u);
|
|
}
|
|
}
|
|
AppendEnd(&test.code);
|
|
const auto compiled = CompileCase(test, SubgroupSize());
|
|
Require(name, "comparison resource", compiled.program.info.images.size() == 1 &&
|
|
compiled.program.info.images[0].depth_compare &&
|
|
compiled.program.info.images[0].cube == cube &&
|
|
compiled.program.info.images[0].dimension ==
|
|
(cube ? ShaderRecompiler::Decoder::ImageDimension::Dim2DArray
|
|
: ShaderRecompiler::Decoder::ImageDimension::Dim1D),
|
|
"native comparison did not retain its resource semantics");
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 8;
|
|
std::copy_n(descriptor.fields, 8, value.dwords.begin());
|
|
auto binding = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, compiled.program.info.images[0], value);
|
|
const auto view = cache.FindTexture(binding.image_id, binding.desc);
|
|
auto &image = cache.GetImage(binding.image_id);
|
|
Require(name, "fresh depth backing", view != nullptr &&
|
|
image.info.pixel_format == vk::Format::eD16Unorm &&
|
|
image.info.guest_format == Prospero::BufferFormat::k16UNorm &&
|
|
image.info.tile_mode == descriptor.TileMode() &&
|
|
image.info.type == (cube ? Prospero::ImageType::kColor2D
|
|
: Prospero::ImageType::kColor1D) &&
|
|
image.info.data.size == (cube ? 0x600u : 0x10000u) &&
|
|
image.info.resources.layers == layers,
|
|
"comparison texture lost its guest layout or depth representation");
|
|
auto ordinary_resource = compiled.program.info.images[0];
|
|
ordinary_resource.depth_compare = false;
|
|
value.dwords[0] = static_cast<uint32_t>(ordinary_address >> 8u);
|
|
auto ordinary = RenderExecutorTestAccess::ResolveTexture(executor, ordinary_resource, value);
|
|
(void)cache.FindTexture(ordinary.image_id, ordinary.desc);
|
|
auto &color = cache.GetImage(ordinary.image_id);
|
|
Require(name, "ordinary R16", color.info.pixel_format == vk::Format::eR16Unorm,
|
|
"ordinary R16 sampling unexpectedly acquired a depth format");
|
|
scheduler.Finish();
|
|
|
|
auto output = CreateStorageBuffer(name, {}, test.expected.size());
|
|
const auto sampler = context.GetSamplerCache().GetSampler(sampler_descriptor);
|
|
Image sampled;
|
|
sampled.view = view;
|
|
sampled.layout = image.backing.state.layout;
|
|
Dispatch(test, compiled, output, nullptr, &sampled, nullptr, nullptr, sampler);
|
|
Require(name, "native comparisons",
|
|
ReadBuffer(name, output, test.expected.size()) == test.expected,
|
|
"comparison samples lost a depth value or guest layer stride");
|
|
|
|
image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
|
|
{}, scheduler.Current().Handle());
|
|
color.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
|
|
{}, scheduler.Current().Handle());
|
|
for (uint32_t face = 0; face < layers; face++) {
|
|
const vk::ImageSubresourceRange depth_range{vk::ImageAspectFlagBits::eDepth, 0, 1, face, 1};
|
|
const vk::ClearDepthStencilValue clear_depth{
|
|
static_cast<float>(depth_at(face, true)) / 65535.f, 0};
|
|
scheduler.Current().Handle().clearDepthStencilImage(image.backing.image,
|
|
vk::ImageLayout::eTransferDstOptimal, &clear_depth, 1, &depth_range);
|
|
const vk::ImageSubresourceRange color_range{vk::ImageAspectFlagBits::eColor, 0, 1, face, 1};
|
|
const vk::ClearColorValue clear_color{std::array<float, 4>{
|
|
static_cast<float>(depth_at(face, true)) / 65535.f, 0.f, 0.f, 0.f}};
|
|
scheduler.Current().Handle().clearColorImage(color.backing.image,
|
|
vk::ImageLayout::eTransferDstOptimal, &clear_color, 1, &color_range);
|
|
}
|
|
cache.MarkGpuWritten(binding.image_id);
|
|
cache.MarkGpuWritten(ordinary.image_id);
|
|
Require(name, "depth readback", TextureCacheTestAccess::TryDownload(cache, binding.image_id),
|
|
"ordinary tiled depth readback did not use the shared transfer path");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
for (uint32_t face = 0; face < layers; face++) {
|
|
uint16_t actual = 0;
|
|
std::memcpy(&actual, reinterpret_cast<const void *>(base + face * 0x100), sizeof(actual));
|
|
Require(name, "depth guest roundtrip", actual == depth_at(face, true),
|
|
"GPU depth clear was not written to the correct guest face");
|
|
}
|
|
|
|
auto promoted = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, compiled.program.info.images[0], value);
|
|
sampled.view = cache.FindTexture(promoted.image_id, promoted.desc);
|
|
auto &promoted_image = cache.GetImage(promoted.image_id);
|
|
Require(name, "GPU color owner promotion", promoted.image_id != ordinary.image_id &&
|
|
promoted_image.info.pixel_format == vk::Format::eD16Unorm &&
|
|
promoted_image.IsGpuModified(),
|
|
"comparison binding did not preserve the GPU-current color owner");
|
|
sampled.layout = promoted_image.backing.state.layout;
|
|
scheduler.Finish();
|
|
Dispatch(test, compiled, output, nullptr, &sampled, nullptr, nullptr, sampler);
|
|
Require(name, "promoted comparisons",
|
|
ReadBuffer(name, output, promoted_expected.size()) == promoted_expected,
|
|
"color-to-depth ownership conversion changed comparison results");
|
|
DestroyBuffer(&output);
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
};
|
|
check_layout(false);
|
|
check_layout(true);
|
|
Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"comparison texture unmap failed");
|
|
Require(name, "release", Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"comparison texture release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
|
|
void CheckRenderExecutorColorStandardTileDiscovery() {
|
|
constexpr const char *name = "RenderExecutorColorStandardTile";
|
|
constexpr uintptr_t base = 0x0000000203b00000ull;
|
|
constexpr uint64_t allocation_size = 0x190000;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
struct TileCase {
|
|
Prospero::TileMode tile;
|
|
uint64_t target_offset;
|
|
uint64_t alignment;
|
|
};
|
|
constexpr std::array cases{
|
|
TileCase{Prospero::TileMode::kStandard4KB, 0x4000, 0x1000},
|
|
TileCase{Prospero::TileMode::kStandard64KB, 0x10000, 0x10000},
|
|
};
|
|
EnsureRuntimeContext();
|
|
|
|
const auto odd_pitch =
|
|
TileGetTexturePitch(format, 257, Prospero::TileMode::kStandard4KB);
|
|
TileSizeAlign odd_slice{};
|
|
TileGetTextureSize(format, 257, 131, 1, Prospero::TileMode::kStandard4KB,
|
|
&odd_slice, nullptr, nullptr);
|
|
Require(name, "tile layout",
|
|
odd_pitch == 288 && odd_slice.size == 0x2d000 &&
|
|
odd_slice.align == 0x1000,
|
|
"Standard4KB pitch, footprint, or block alignment is incorrect");
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"standard-tiled color allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"standard-tiled color mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
|
|
for (const auto &tile_case : cases) {
|
|
const auto pitch = TileGetTexturePitch(format, 256, tile_case.tile);
|
|
TileSizeAlign slice{};
|
|
TileGetTextureSize(format, 256, 256, 1, tile_case.tile, &slice, nullptr,
|
|
nullptr);
|
|
Require(
|
|
name, "tile layout",
|
|
pitch == 256 && slice.size == 0x40000 &&
|
|
slice.align == tile_case.alignment,
|
|
"standard tile pitch, footprint, or block alignment is incorrect");
|
|
|
|
const auto target_address = base + tile_case.target_offset;
|
|
const auto target_size = slice.size * 6ull;
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetColorBase(0, {.addr = target_address});
|
|
registers.SetColorInfo(
|
|
0, {.format = Prospero::ChannelLayout::k32,
|
|
.channel_type = Prospero::ChannelType::kFloat,
|
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
|
registers.SetColorView(
|
|
0, {.base_array_slice_index = 5, .last_array_slice_index = 5});
|
|
registers.SetColorAttrib2(0, {.height = 255, .width = 255});
|
|
registers.SetColorAttrib3(0, {.tile_mode = tile_case.tile,
|
|
.dimension = 1,
|
|
.metadata_pipe_aligned = true});
|
|
registers.SetRenderTargetMask(0x0f);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
|
|
RenderColorInfo color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), color, 0);
|
|
const auto attachment =
|
|
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
|
const auto &image = texture_cache.GetImage(color.image_id);
|
|
Require(name, "captured target",
|
|
color.image_id && attachment != nullptr &&
|
|
color.guest_array_layer == 5 &&
|
|
color.desc.view_info.base_layer == 5 &&
|
|
color.desc.info.data.address == target_address &&
|
|
color.desc.info.data.size == target_size &&
|
|
color.desc.info.tile_mode == tile_case.tile &&
|
|
color.desc.info.extent == vk::Extent3D{256, 256, 1} &&
|
|
color.desc.info.resources == ImageSubresources{1, 6} &&
|
|
color.desc.info.pitch == pitch &&
|
|
color.desc.info.mip_layout[0].offset == 0 &&
|
|
color.desc.info.mip_layout[0].size == target_size &&
|
|
color.desc.view_info.type == vk::ImageViewType::e2D &&
|
|
color.desc.view_info.base_layer == 5 &&
|
|
color.desc.view_info.layer_count == 1 &&
|
|
(target_address & (tile_case.alignment - 1)) == 0 &&
|
|
image.usage.render_target && image.IsGpuModified(),
|
|
"slice 5 standard-tiled render target was not resolved");
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"standard-tiled color mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"standard-tiled color allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckRenderExecutorColorDepthTileDiscovery() {
|
|
constexpr const char *name = "RenderExecutorColorDepthTile";
|
|
constexpr uintptr_t base = 0x0000000203d00000ull;
|
|
constexpr uint64_t allocation_size = 0x10000;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
EnsureRuntimeContext();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"depth-tiled color allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"depth-tiled color mapping failed");
|
|
constexpr uint32_t width = 64;
|
|
constexpr uint32_t height = 64;
|
|
TileTextureBlockLayout tile_layout{};
|
|
Require(name, "tile layout",
|
|
TileGetTextureBlockLayout(Prospero::BufferFormat::k32Float,
|
|
Prospero::TileMode::kDepth, false,
|
|
tile_layout),
|
|
"depth-tiled color block layout is unavailable");
|
|
uint32_t block_xor = 0;
|
|
Require(name, "block xor",
|
|
TileGetBlockXor(tile_layout.block, 0, 0, 0, block_xor),
|
|
"depth-tiled color block XOR is unavailable");
|
|
std::vector<uint32_t> expected(allocation_size / sizeof(uint32_t));
|
|
for (uint32_t y = 0; y < height; ++y) {
|
|
for (uint32_t x = 0; x < width; ++x) {
|
|
uint32_t offset = 0;
|
|
Require(name, "texel offset",
|
|
TileGetBlockOffset(tile_layout.block, x, y, 0, offset),
|
|
"depth-tiled color texel offset is unavailable");
|
|
const auto index = (offset ^ block_xor) / sizeof(uint32_t);
|
|
Require(name, "texel address", index < expected.size(),
|
|
"depth-tiled color texel address exceeds its backing");
|
|
expected[index] = 0x3f000000u + y * width + x;
|
|
}
|
|
}
|
|
std::memcpy(mapped, expected.data(), allocation_size);
|
|
|
|
{
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetColorBase(0, {.addr = base});
|
|
registers.SetColorInfo(
|
|
0, {.format = Prospero::ChannelLayout::k32,
|
|
.channel_type = Prospero::ChannelType::kFloat,
|
|
.channel_order = Prospero::ChannelOrder::kStandard});
|
|
registers.SetColorAttrib2(0, {.height = 63, .width = 63});
|
|
registers.SetColorAttrib3(0, {.tile_mode = Prospero::TileMode::kDepth,
|
|
.dimension = 1,
|
|
.metadata_pipe_aligned = true});
|
|
registers.SetRenderTargetMask(0x0f);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
|
|
RenderColorInfo color{};
|
|
RenderExecutorTestAccess::ResolveRenderColorTarget(
|
|
executor, scheduler.Current(), color, 0);
|
|
const auto attachment =
|
|
texture_cache.FindRenderTarget(color.image_id, color.desc);
|
|
const auto &image = texture_cache.GetImage(color.image_id);
|
|
Require(name, "captured target",
|
|
color.image_id && attachment != nullptr &&
|
|
color.desc.info.tile_mode == Prospero::TileMode::kDepth &&
|
|
color.desc.info.pitch == 128 &&
|
|
color.desc.info.data.size == allocation_size &&
|
|
color.desc.info.mip_layout[0].size == allocation_size &&
|
|
image.usage.render_target && image.IsGpuModified(),
|
|
"supported depth-tiled color target was not resolved");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
std::vector<uint8_t> cleared(allocation_size);
|
|
Require(name, "clear guest backing",
|
|
Libs::LibKernel::Memory::TryWriteBacking(base, cleared.data(),
|
|
allocation_size),
|
|
"depth-tiled color backing could not be cleared");
|
|
Require(
|
|
name, "readback queue",
|
|
TextureCacheTestAccess::TryDownload(texture_cache, color.image_id),
|
|
"depth-tiled color target could not be queued for readback");
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
std::vector<uint32_t> observed(expected.size());
|
|
Require(name, "readback backing",
|
|
Libs::LibKernel::Memory::TryReadBacking(base, observed.data(),
|
|
allocation_size),
|
|
"depth-tiled color readback is unavailable");
|
|
bool recovered = true;
|
|
for (uint32_t y = 0; y < height; ++y) {
|
|
for (uint32_t x = 0; x < width; ++x) {
|
|
uint32_t offset = 0;
|
|
const auto valid =
|
|
TileGetBlockOffset(tile_layout.block, x, y, 0, offset);
|
|
const auto index = (offset ^ block_xor) / sizeof(uint32_t);
|
|
recovered &= valid && index < observed.size() &&
|
|
observed[index] == expected[index];
|
|
}
|
|
}
|
|
Require(name, "round trip", recovered,
|
|
"depth-tiled color upload/readback changed active texels");
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"depth-tiled color mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"depth-tiled color allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckRenderExecutorStencilBindingDiscovery() {
|
|
constexpr const char *name = "RenderExecutorStencilBindingDiscovery";
|
|
constexpr uintptr_t base = 0x0000000203600000ull;
|
|
constexpr uint64_t allocation_size = 0x200000;
|
|
constexpr uint64_t allocation_alignment = 0x10000;
|
|
constexpr uint64_t depth_address = base + 0x40000;
|
|
constexpr uint64_t stencil_address = base + 0x70000;
|
|
EnsureRuntimeContext();
|
|
|
|
int64_t direct_offset = -1;
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_alignment, 0, &direct_offset) == 0,
|
|
"descriptor discovery direct-memory allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(base);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_alignment) == 0 &&
|
|
mapped == reinterpret_cast<void *>(base),
|
|
"descriptor discovery fixed mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
|
|
{
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &resources = context.GetGpuResources();
|
|
auto &texture_cache = resources.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
resources.MapMemory(base, allocation_size);
|
|
std::vector<PipelineCache::Pipeline> descriptor_pipelines;
|
|
|
|
// The virtual-texture atlas is written as raw BC3 blocks, then sampled as BC3.
|
|
constexpr uint64_t block_alias_address = base + 0xf0000;
|
|
constexpr std::array<uint32_t, 4> block_alias_data{
|
|
0x01234567u, 0x89abcdefu, 0xfedcba98u, 0x76543210u};
|
|
const auto resolve_block_alias = [&](bool compressed) {
|
|
const uint32_t side = compressed ? 256 : 64;
|
|
const auto format = compressed ? Prospero::BufferFormat::kBc3UNorm
|
|
: Prospero::BufferFormat::k32_32_32_32UInt;
|
|
ShaderRecompiler::IR::DescriptorValue value{};
|
|
value.dword_count = 8;
|
|
value.dwords = {static_cast<uint32_t>(block_alias_address >> 8u),
|
|
(static_cast<uint32_t>(format) << 20u) | (3u << 30u),
|
|
((side - 1u) >> 2u) | ((side - 1u) << 14u),
|
|
0x90900facu, 0, 0x00700000u, 0, 0};
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = compressed
|
|
? ShaderRecompiler::IR::ImageResourceClass::Sampled
|
|
: ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
resource.numeric_class = compressed ? Prospero::TextureNumericClass::Float
|
|
: Prospero::TextureNumericClass::Uint;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = compressed;
|
|
resource.written = !compressed;
|
|
return RenderExecutorTestAccess::ResolveTexture(executor, resource, value);
|
|
};
|
|
auto raw_blocks = resolve_block_alias(false);
|
|
(void)texture_cache.FindTexture(raw_blocks.image_id, raw_blocks.desc);
|
|
auto &raw_blocks_native = texture_cache.GetImage(raw_blocks.image_id);
|
|
raw_blocks_native.Transit(vk::ImageLayout::eTransferDstOptimal,
|
|
vk::AccessFlagBits2::eTransferWrite, {},
|
|
scheduler.Current().Handle());
|
|
vk::ClearColorValue block_clear{};
|
|
std::copy(block_alias_data.begin(), block_alias_data.end(),
|
|
block_clear.uint32.begin());
|
|
const vk::ImageSubresourceRange block_range{
|
|
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
|
scheduler.Current().Handle().clearColorImage(
|
|
raw_blocks_native.backing.image, vk::ImageLayout::eTransferDstOptimal,
|
|
&block_clear, 1, &block_range);
|
|
texture_cache.MarkGpuWritten(raw_blocks.image_id);
|
|
auto compressed_blocks = resolve_block_alias(true);
|
|
(void)texture_cache.FindTexture(compressed_blocks.image_id,
|
|
compressed_blocks.desc);
|
|
auto raw_blocks_again = resolve_block_alias(false);
|
|
Require(name, "compressed atlas storage reuse",
|
|
compressed_blocks.image_id != raw_blocks.image_id &&
|
|
raw_blocks_again.image_id == compressed_blocks.image_id &&
|
|
texture_cache.FindTexture(raw_blocks_again.image_id,
|
|
raw_blocks_again.desc) != nullptr,
|
|
"BC3 sampling replaced the GPU atlas on its next raw-block write");
|
|
Require(name, "compressed atlas download",
|
|
TextureCacheTestAccess::TryDownload(texture_cache,
|
|
raw_blocks_again.image_id),
|
|
"the retained BC3 atlas could not publish its native contents");
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
const auto *block_words = reinterpret_cast<const uint32_t *>(block_alias_address);
|
|
Require(name, "compressed atlas GPU contents",
|
|
std::equal(block_alias_data.begin(), block_alias_data.end(), block_words) &&
|
|
std::equal(block_alias_data.begin(), block_alias_data.end(),
|
|
block_words + 0x10000 / sizeof(uint32_t) - 4),
|
|
"compressed atlas aliases reloaded stale CPU bytes over GPU-written blocks");
|
|
|
|
const auto allocate_bindings =
|
|
[&](ShaderRecompiler::IR::Program &program) {
|
|
program.shader_info_complete = true;
|
|
ShaderRecompiler::IR::AllocateBindings(program);
|
|
};
|
|
|
|
{
|
|
ShaderRecompiler::IR::Program buffer_ir{};
|
|
buffer_ir.stage = ShaderType::Compute;
|
|
buffer_ir.resource_tracking_complete = true;
|
|
buffer_ir.info.buffers.resize(1);
|
|
buffer_ir.info.buffers[0].read = true;
|
|
allocate_bindings(buffer_ir);
|
|
ShaderRecompiler::IR::CompiledShaderInfo buffer_program{};
|
|
buffer_program.stage = buffer_ir.stage;
|
|
buffer_program.info = std::move(buffer_ir.info);
|
|
buffer_program.bindings = std::move(buffer_ir.bindings);
|
|
|
|
constexpr uint64_t buffer_address = base + allocation_size - 0x5000;
|
|
ShaderBufferResource buffer_descriptor{};
|
|
buffer_descriptor.UpdateAddress48(buffer_address);
|
|
buffer_descriptor.fields[2] = 0x8000;
|
|
ShaderStageRuntime buffer_runtime{.program = &buffer_program};
|
|
auto &value = buffer_runtime.resources.buffers.emplace_back();
|
|
std::memcpy(value.dwords.data(), buffer_descriptor.fields,
|
|
sizeof(buffer_descriptor.fields));
|
|
value.dword_count = 4;
|
|
auto buffer_bindings = executor.PrepareBindings(buffer_runtime);
|
|
executor.FindBuffers(buffer_bindings);
|
|
const auto original_id = buffer_bindings.buffer_sources[0].id;
|
|
|
|
auto &buffer_cache = resources.GetBufferCache();
|
|
const auto merged_id =
|
|
buffer_cache.FindBuffer(base + allocation_size - 0x10000, 0x10000);
|
|
Require(name, "buffer discovery invalidation", merged_id != original_id,
|
|
"overlapping buffer discovery did not replace its smaller owner");
|
|
executor.RebindBuffers(buffer_bindings);
|
|
const auto &binding = buffer_bindings.buffers[0];
|
|
const auto &owner = buffer_cache.GetBuffer(merged_id);
|
|
Require(name, "clamped buffer rebind",
|
|
binding.buffer == owner.Handle() &&
|
|
binding.offset == owner.Offset(buffer_address) &&
|
|
binding.range == 0x5000,
|
|
"descriptor rebind lost its clamped guest range or retained a stale host owner");
|
|
}
|
|
|
|
constexpr auto stencil_format = Prospero::BufferFormat::k8UInt;
|
|
constexpr auto linear = Prospero::TileMode::kLinear;
|
|
TileSizeAlign stencil_layout{};
|
|
TileGetTextureTotalSize(stencil_format, 1, 1, 1, 1, linear, false,
|
|
stencil_layout);
|
|
Require(name, "stencil footprint",
|
|
stencil_layout.size != 0 && stencil_layout.align != 0 &&
|
|
(stencil_address & (stencil_layout.align - 1u)) == 0,
|
|
"linear stencil descriptor produced an invalid footprint");
|
|
|
|
ShaderRecompiler::IR::Program null_program{};
|
|
null_program.stage = ShaderType::Compute;
|
|
null_program.resource_tracking_complete = true;
|
|
ShaderRecompiler::IR::ImageResource null_resource{};
|
|
null_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
null_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
null_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
null_resource.read = true;
|
|
null_program.info.images.push_back(null_resource);
|
|
auto null_volume = null_resource;
|
|
null_volume.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim3D;
|
|
null_program.info.images.push_back(null_volume);
|
|
auto null_storage = null_resource;
|
|
null_storage.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
null_storage.read = false;
|
|
null_storage.written = true;
|
|
null_program.info.images.push_back(null_storage);
|
|
ShaderRecompiler::IR::ResourceSnapshot null_snapshot{};
|
|
ShaderRecompiler::IR::DescriptorValue null_descriptor{};
|
|
null_descriptor.dword_count = 8;
|
|
null_snapshot.images.assign(3, null_descriptor);
|
|
ShaderRecompiler::IR::ResourceSpecialization null_specialization{};
|
|
null_specialization.images.resize(3);
|
|
for (auto &image : null_specialization.images) {
|
|
image.numeric_class = Prospero::TextureNumericClass::Float;
|
|
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
}
|
|
ShaderRecompiler::IR::ApplyResourceSpecialization(
|
|
null_program, null_specialization);
|
|
allocate_bindings(null_program);
|
|
ShaderRecompiler::IR::CompiledShaderInfo null_info{};
|
|
null_info.stage = null_program.stage;
|
|
null_info.info = std::move(null_program.info);
|
|
null_info.bindings = std::move(null_program.bindings);
|
|
ShaderStageRuntime null_runtime{&null_info, std::move(null_snapshot)};
|
|
auto null_bindings = executor.PrepareBindings(null_runtime);
|
|
executor.RebindImages(null_bindings);
|
|
Require(name, "null descriptor count",
|
|
null_bindings.images.size() == 3,
|
|
"null descriptor preparation lost an image binding");
|
|
const auto null_image_id = null_bindings.images[0].image_id;
|
|
const auto &null_image = texture_cache.GetImage(null_image_id);
|
|
Require(name, "one null image per format",
|
|
null_bindings.images[1].image_id == null_image_id &&
|
|
null_bindings.images[2].image_id == null_image_id &&
|
|
TextureCacheTestAccess::NullImageCount(texture_cache) == 1,
|
|
"null view dimension or usage created another image allocation");
|
|
Require(name, "null descriptors share final cache acquisition",
|
|
null_image.info.data.Empty() && !null_image.binding.is_bound &&
|
|
!null_image.binding.is_target &&
|
|
!null_image.binding.needs_rebind &&
|
|
null_image.usage.texture && null_image.usage.storage &&
|
|
null_image.IsGpuModified() &&
|
|
!TextureCacheTestAccess::PendingDownload(texture_cache,
|
|
null_image_id),
|
|
"the shared null image did not preserve texture/storage "
|
|
"acquisition without guest readback or RenderExecutor ownership");
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), null_bindings));
|
|
Require(name, "null descriptor general layouts",
|
|
std::ranges::all_of(null_bindings.images,
|
|
[](const auto &binding) {
|
|
const auto info = MakeImageInfo(binding);
|
|
return binding.layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
info.imageView ==
|
|
binding.image_view &&
|
|
info.imageLayout == binding.layout;
|
|
}) &&
|
|
null_image.backing.state.layout == vk::ImageLayout::eGeneral,
|
|
"shared sampled/storage null descriptors did not retain "
|
|
"the general layout through descriptor generation");
|
|
|
|
constexpr uint64_t target_mip_size = 0x10000;
|
|
const auto make_target_desc = [&](uint64_t address, uint64_t size,
|
|
vk::Extent3D extent) {
|
|
ImageDesc desc{};
|
|
desc.type = BindingType::RenderTarget;
|
|
desc.info.data = {address, size};
|
|
desc.info.pixel_format = vk::Format::eR32Uint;
|
|
desc.info.guest_format = Prospero::BufferFormat::k32UInt;
|
|
desc.info.type = Prospero::ImageType::kColor2D;
|
|
desc.info.extent = extent;
|
|
desc.info.resources = {1, 1};
|
|
desc.info.pitch = extent.width;
|
|
desc.info.bytes_per_block = 4;
|
|
desc.info.samples = 1;
|
|
desc.info.tile_mode = linear;
|
|
desc.info.mip_layout[0] = {0, size, extent.width, extent.height};
|
|
desc.view_info.format = desc.info.pixel_format;
|
|
desc.view_info.type = vk::ImageViewType::e2D;
|
|
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
return desc;
|
|
};
|
|
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, true);
|
|
ShaderTextureResource storage{};
|
|
const uint64_t storage_address = base + 0x60000;
|
|
constexpr uint32_t storage_native_value = 0x13579bdfu;
|
|
constexpr uint32_t storage_stale_value = 0x2468ace0u;
|
|
std::memcpy(reinterpret_cast<uint8_t *>(mapped) + 0x60000,
|
|
&storage_native_value, sizeof(storage_native_value));
|
|
const uint64_t encoded_storage_address = storage_address >> 8u;
|
|
storage.fields[0] = static_cast<uint32_t>(encoded_storage_address);
|
|
storage.fields[1] =
|
|
static_cast<uint32_t>(encoded_storage_address >> 32u) |
|
|
(static_cast<uint32_t>(stencil_format) << 20u);
|
|
storage.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(linear) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
storage.fields[5] = 0x00700000u;
|
|
ShaderRecompiler::IR::Program storage_program{};
|
|
storage_program.stage = ShaderType::Vertex;
|
|
storage_program.resource_tracking_complete = true;
|
|
ShaderRecompiler::IR::ImageResource storage_resource{};
|
|
storage_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
storage_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
storage_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
storage_resource.written = true;
|
|
storage_program.info.images.push_back(storage_resource);
|
|
allocate_bindings(storage_program);
|
|
ShaderRecompiler::IR::DescriptorValue storage_descriptor{};
|
|
std::copy(std::begin(storage.fields), std::end(storage.fields),
|
|
storage_descriptor.dwords.begin());
|
|
storage_descriptor.dword_count = 8;
|
|
auto mipped_storage = storage;
|
|
constexpr uint64_t mipped_storage_address = base + 0x100000;
|
|
const auto encoded_mipped_storage_address = mipped_storage_address >> 8u;
|
|
mipped_storage.fields[0] =
|
|
static_cast<uint32_t>(encoded_mipped_storage_address);
|
|
mipped_storage.fields[1] =
|
|
static_cast<uint32_t>(encoded_mipped_storage_address >> 32u) |
|
|
(static_cast<uint32_t>(stencil_format) << 20u) | (3u << 30u);
|
|
mipped_storage.fields[2] = 1u | (7u << 14u);
|
|
mipped_storage.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (1u << 12u) | (3u << 16u) |
|
|
(static_cast<uint32_t>(linear) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
mipped_storage.fields[5] = 0x00700000u | (3u << 4u);
|
|
ShaderRecompiler::IR::DescriptorValue mipped_storage_descriptor{};
|
|
std::copy(std::begin(mipped_storage.fields),
|
|
std::end(mipped_storage.fields),
|
|
mipped_storage_descriptor.dwords.begin());
|
|
mipped_storage_descriptor.dword_count = 8;
|
|
auto overwide_mipped_storage = mipped_storage;
|
|
overwide_mipped_storage.fields[3] =
|
|
(overwide_mipped_storage.fields[3] & ~(0xfu << 16u)) | (4u << 16u);
|
|
ShaderRecompiler::IR::DescriptorValue
|
|
overwide_mipped_storage_descriptor{};
|
|
std::copy(std::begin(overwide_mipped_storage.fields),
|
|
std::end(overwide_mipped_storage.fields),
|
|
overwide_mipped_storage_descriptor.dwords.begin());
|
|
overwide_mipped_storage_descriptor.dword_count = 8;
|
|
auto mipped_storage_resource = storage_resource;
|
|
mipped_storage_resource.mip_mode =
|
|
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
|
mipped_storage_resource.mip_count = 3;
|
|
auto sampled_overwide_resource = storage_resource;
|
|
sampled_overwide_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
sampled_overwide_resource.numeric_class =
|
|
Prospero::TextureNumericClass::Uint;
|
|
sampled_overwide_resource.read = true;
|
|
sampled_overwide_resource.written = false;
|
|
auto plain_mipped_storage_binding =
|
|
RenderExecutorTestAccess::ResolveTexture(executor, storage_resource,
|
|
mipped_storage_descriptor);
|
|
auto mipped_storage_binding = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, mipped_storage_resource, mipped_storage_descriptor);
|
|
auto overwide_mipped_storage_binding =
|
|
RenderExecutorTestAccess::ResolveTexture(
|
|
executor, storage_resource, overwide_mipped_storage_descriptor);
|
|
auto sampled_overwide_resolved = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, sampled_overwide_resource,
|
|
overwide_mipped_storage_descriptor);
|
|
PreparedBindings mipped_prepared{};
|
|
ShaderRecompiler::IR::CompiledShaderInfo mipped_program{};
|
|
mipped_program.info.images.push_back(storage_resource);
|
|
mipped_program.info.images.push_back(mipped_storage_resource);
|
|
mipped_program.info.images.push_back(storage_resource);
|
|
mipped_program.info.images.push_back(sampled_overwide_resource);
|
|
ShaderRecompiler::IR::ResourceSnapshot mipped_snapshot{};
|
|
mipped_snapshot.images = {mipped_storage_descriptor,
|
|
mipped_storage_descriptor,
|
|
overwide_mipped_storage_descriptor,
|
|
overwide_mipped_storage_descriptor};
|
|
ShaderStageRuntime mipped_runtime{&mipped_program, std::move(mipped_snapshot)};
|
|
mipped_prepared.runtime = &mipped_runtime;
|
|
mipped_prepared.images.push_back(
|
|
std::move(plain_mipped_storage_binding));
|
|
mipped_prepared.images.push_back(
|
|
std::move(mipped_storage_binding));
|
|
mipped_prepared.images.push_back(
|
|
std::move(overwide_mipped_storage_binding));
|
|
mipped_prepared.images.push_back(
|
|
std::move(sampled_overwide_resolved));
|
|
executor.RebindImages(mipped_prepared);
|
|
auto &plain_mipped_binding = mipped_prepared.images[0];
|
|
auto &mipped_binding = mipped_prepared.images[1];
|
|
auto &overwide_mipped_binding = mipped_prepared.images[2];
|
|
auto &sampled_overwide_binding = mipped_prepared.images[3];
|
|
plain_mipped_binding.layout = vk::ImageLayout::eGeneral;
|
|
mipped_binding.layout = vk::ImageLayout::eGeneral;
|
|
overwide_mipped_binding.layout = vk::ImageLayout::eGeneral;
|
|
sampled_overwide_binding.layout = vk::ImageLayout::eGeneral;
|
|
auto plain_view_desc = plain_mipped_binding.desc;
|
|
plain_view_desc.view_info.level_count = 1;
|
|
Require(name, "dynamic storage mip views",
|
|
plain_mipped_binding.desc.view_info.base_level == 1 &&
|
|
plain_mipped_binding.desc.view_info.level_count == 3 &&
|
|
plain_mipped_binding.mip_views.empty() &&
|
|
plain_mipped_binding.image_view ==
|
|
texture_cache.FindTexture(plain_mipped_binding.image_id,
|
|
plain_view_desc) &&
|
|
mipped_binding.desc.info.resources.levels == 4 &&
|
|
mipped_binding.desc.view_info.base_level == 1 &&
|
|
mipped_binding.desc.view_info.level_count == 3 &&
|
|
mipped_binding.mip_views.size() == 3 &&
|
|
mipped_binding.mip_views[0] != nullptr &&
|
|
mipped_binding.mip_views[1] != nullptr &&
|
|
mipped_binding.mip_views[2] != nullptr &&
|
|
mipped_binding.mip_views[0] != mipped_binding.mip_views[1] &&
|
|
mipped_binding.mip_views[1] != mipped_binding.mip_views[2] &&
|
|
MakeImageInfo(mipped_binding, 0).imageView ==
|
|
mipped_binding.mip_views[0] &&
|
|
MakeImageInfo(mipped_binding, 1).imageView ==
|
|
mipped_binding.mip_views[1] &&
|
|
MakeImageInfo(mipped_binding, 2).imageView ==
|
|
mipped_binding.mip_views[2],
|
|
"base-1 through last-3 storage view did not create three "
|
|
"ordered one-level descriptors");
|
|
Require(name, "over-wide fixed storage mip view",
|
|
overwide_mipped_storage.LastLevel() == 4 &&
|
|
overwide_mipped_storage.MaxMip() == 3 &&
|
|
overwide_mipped_binding.image_id ==
|
|
plain_mipped_binding.image_id &&
|
|
overwide_mipped_binding.desc.info.resources.levels == 4 &&
|
|
overwide_mipped_binding.desc.view_info.base_level == 1 &&
|
|
overwide_mipped_binding.desc.view_info.level_count == 3 &&
|
|
overwide_mipped_binding.mip_views.empty() &&
|
|
overwide_mipped_binding.image_view ==
|
|
plain_mipped_binding.image_view,
|
|
"fixed storage view was not intersected with its physical mip "
|
|
"range before Vulkan acquisition");
|
|
Require(name, "over-wide sampled mip view",
|
|
sampled_overwide_binding.image_id ==
|
|
plain_mipped_binding.image_id &&
|
|
sampled_overwide_binding.desc.info.resources.levels == 4 &&
|
|
sampled_overwide_binding.desc.view_info.base_level == 1 &&
|
|
sampled_overwide_binding.desc.view_info.level_count == 3 &&
|
|
sampled_overwide_binding.mip_views.empty() &&
|
|
sampled_overwide_binding.image_view != nullptr,
|
|
"sampled view was not intersected with its physical mip range");
|
|
auto srgb_storage = storage;
|
|
constexpr auto srgb_format =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k8_8_8_8Srgb);
|
|
constexpr uint64_t srgb_storage_address = base + 0xc0000;
|
|
const auto encoded_srgb_storage_address = srgb_storage_address >> 8u;
|
|
srgb_storage.fields[0] =
|
|
static_cast<uint32_t>(encoded_srgb_storage_address);
|
|
srgb_storage.fields[1] =
|
|
static_cast<uint32_t>(encoded_srgb_storage_address >> 32u) |
|
|
(srgb_format << 20u);
|
|
ShaderRecompiler::IR::DescriptorValue srgb_storage_descriptor{};
|
|
std::copy(std::begin(srgb_storage.fields), std::end(srgb_storage.fields),
|
|
srgb_storage_descriptor.dwords.begin());
|
|
srgb_storage_descriptor.dword_count = 8;
|
|
auto srgb_storage_resource = storage_resource;
|
|
srgb_storage_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
srgb_storage_resource.numeric_class =
|
|
Prospero::TextureNumericClass::Float;
|
|
const auto srgb_storage_binding =
|
|
RenderExecutorTestAccess::ResolveTexture(
|
|
executor, srgb_storage_resource, srgb_storage_descriptor);
|
|
const auto srgb_storage_view = texture_cache.FindTexture(
|
|
srgb_storage_binding.image_id, srgb_storage_binding.desc);
|
|
Require(name, "sRGB storage view",
|
|
srgb_storage_view != nullptr &&
|
|
srgb_storage_binding.desc.info.pixel_format ==
|
|
vk::Format::eR8G8B8A8Srgb &&
|
|
srgb_storage_binding.desc.view_info.format ==
|
|
vk::Format::eR8G8B8A8Unorm &&
|
|
texture_cache.GetImage(srgb_storage_binding.image_id)
|
|
.backing.format == vk::Format::eR8G8B8A8Srgb,
|
|
"storage descriptor did not preserve its sRGB backing and "
|
|
"select an UNORM Vulkan view");
|
|
|
|
ShaderTextureResource sint_storage{{0x01514b00u, 0xc1500000u, 0x000bc00bu,
|
|
0x91b00204u, 0x00000000u, 0x00700000u,
|
|
0x102b0000u, 0x0001514au}};
|
|
Require(name, "PPSA06888 R32 SINT descriptor",
|
|
sint_storage.Base40() == 0x1514b0000ull &&
|
|
sint_storage.Width5() + 1u == 48 &&
|
|
sint_storage.Height5() + 1u == 48 &&
|
|
sint_storage.Depth() + 1u == 1 &&
|
|
sint_storage.Format() == Prospero::BufferFormat::k32SInt &&
|
|
sint_storage.Type() == Prospero::ImageType::kColor2D &&
|
|
sint_storage.TileMode() ==
|
|
Prospero::TileMode::kRenderTarget &&
|
|
sint_storage.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"captured write-only signed storage descriptor was decoded "
|
|
"incorrectly");
|
|
const uint64_t mapped_sint_address = base + 0xe0000;
|
|
const auto encoded_sint_address = mapped_sint_address >> 8u;
|
|
sint_storage.fields[0] = static_cast<uint32_t>(encoded_sint_address);
|
|
sint_storage.fields[1] =
|
|
(sint_storage.fields[1] & ~0xffu) |
|
|
static_cast<uint32_t>(encoded_sint_address >> 32u);
|
|
ShaderRecompiler::IR::DescriptorValue sint_storage_descriptor{};
|
|
std::copy(std::begin(sint_storage.fields), std::end(sint_storage.fields),
|
|
sint_storage_descriptor.dwords.begin());
|
|
sint_storage_descriptor.dword_count = 8;
|
|
auto sint_storage_resource = srgb_storage_resource;
|
|
sint_storage_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
sint_storage_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
const auto sint_storage_binding =
|
|
RenderExecutorTestAccess::ResolveTexture(
|
|
executor, sint_storage_resource, sint_storage_descriptor);
|
|
const auto sint_storage_view = texture_cache.FindTexture(
|
|
sint_storage_binding.image_id, sint_storage_binding.desc);
|
|
Require(name, "PPSA06888 raw R32 SINT storage view",
|
|
sint_storage_view != nullptr &&
|
|
sint_storage_binding.desc.info.data.size == 0x10000 &&
|
|
sint_storage_binding.desc.info.pixel_format ==
|
|
vk::Format::eR32Sint &&
|
|
sint_storage_binding.desc.view_info.format ==
|
|
vk::Format::eR32Uint &&
|
|
texture_cache.GetImage(sint_storage_binding.image_id)
|
|
.backing.format == vk::Format::eR32Sint,
|
|
"write-only R32 SINT storage did not select a bit-compatible "
|
|
"uint view");
|
|
|
|
auto narrowed_storage = storage;
|
|
constexpr uint64_t narrowed_storage_address = base + 0xd0000;
|
|
const auto encoded_narrowed_address = narrowed_storage_address >> 8u;
|
|
narrowed_storage.fields[0] =
|
|
static_cast<uint32_t>(encoded_narrowed_address);
|
|
narrowed_storage.fields[1] =
|
|
static_cast<uint32_t>(encoded_narrowed_address >> 32u) |
|
|
(static_cast<uint32_t>(stencil_format) << 20u);
|
|
narrowed_storage.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(linear) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor1DArray) << 28u);
|
|
narrowed_storage.fields[4] = 1u | (1u << 16u);
|
|
ShaderRecompiler::IR::DescriptorValue narrowed_storage_descriptor{};
|
|
std::copy(std::begin(narrowed_storage.fields),
|
|
std::end(narrowed_storage.fields),
|
|
narrowed_storage_descriptor.dwords.begin());
|
|
narrowed_storage_descriptor.dword_count = 8;
|
|
auto narrowed_storage_resource = storage_resource;
|
|
narrowed_storage_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim1D;
|
|
const auto narrowed_storage_binding =
|
|
RenderExecutorTestAccess::ResolveTexture(
|
|
executor, narrowed_storage_resource, narrowed_storage_descriptor);
|
|
const auto narrowed_storage_view = texture_cache.FindTexture(
|
|
narrowed_storage_binding.image_id, narrowed_storage_binding.desc);
|
|
const auto &narrowed_storage_image =
|
|
texture_cache.GetImage(narrowed_storage_binding.image_id);
|
|
Require(name, "narrowed 1D array storage view",
|
|
narrowed_storage_view != nullptr &&
|
|
narrowed_storage_binding.desc.info.type ==
|
|
Prospero::ImageType::kColor1D &&
|
|
narrowed_storage_binding.desc.info.resources.layers == 2 &&
|
|
narrowed_storage_binding.desc.view_info.type ==
|
|
vk::ImageViewType::e1D &&
|
|
narrowed_storage_binding.desc.view_info.base_layer == 1 &&
|
|
narrowed_storage_binding.desc.view_info.layer_count == 1 &&
|
|
narrowed_storage_image.backing.image_type ==
|
|
vk::ImageType::e1D,
|
|
"non-array 1D specialization did not select the descriptor "
|
|
"base layer from its 1D-array backing");
|
|
|
|
ShaderRecompiler::IR::ResourceSnapshot storage_snapshot{};
|
|
storage_snapshot.images.push_back(storage_descriptor);
|
|
ShaderRecompiler::IR::CompiledShaderInfo storage_info{};
|
|
storage_info.stage = storage_program.stage;
|
|
storage_info.info = std::move(storage_program.info);
|
|
storage_info.bindings = std::move(storage_program.bindings);
|
|
ShaderStageRuntime storage_runtime{&storage_info,
|
|
std::move(storage_snapshot)};
|
|
auto storage_discovery = executor.PrepareBindings(storage_runtime);
|
|
const auto storage_id = storage_discovery.images[0].image_id;
|
|
Require(name, "storage prefetch purity",
|
|
storage_discovery.images[0].image_view == nullptr &&
|
|
texture_cache.GetImage(storage_id).binding.is_bound &&
|
|
!texture_cache.GetImage(storage_id).usage.storage &&
|
|
!TextureCacheTestAccess::PendingDownload(texture_cache,
|
|
storage_id),
|
|
"the first descriptor pass performed final storage acquisition");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
ShaderRecompiler::IR::Program sampled_program{};
|
|
sampled_program.stage = ShaderType::Pixel;
|
|
sampled_program.resource_tracking_complete = true;
|
|
ShaderRecompiler::IR::ImageResource sampled_resource{};
|
|
sampled_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
sampled_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
sampled_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
sampled_resource.read = true;
|
|
sampled_program.info.images.push_back(sampled_resource);
|
|
allocate_bindings(sampled_program);
|
|
ShaderRecompiler::IR::ResourceSnapshot sampled_snapshot{};
|
|
sampled_snapshot.images.push_back(storage_descriptor);
|
|
ShaderRecompiler::IR::CompiledShaderInfo sampled_info{};
|
|
sampled_info.stage = sampled_program.stage;
|
|
sampled_info.info = std::move(sampled_program.info);
|
|
sampled_info.bindings = std::move(sampled_program.bindings);
|
|
ShaderStageRuntime sampled_runtime{&sampled_info,
|
|
std::move(sampled_snapshot)};
|
|
|
|
constexpr uint64_t ordered_sampled_address = base + 0x10000;
|
|
const uint32_t ordered_sampled_value = 0x89abcdefu;
|
|
std::memcpy(reinterpret_cast<uint8_t *>(mapped) + 0x10000,
|
|
&ordered_sampled_value, sizeof(ordered_sampled_value));
|
|
auto ordered_sampled = storage;
|
|
const auto encoded_ordered_address = ordered_sampled_address >> 8u;
|
|
ordered_sampled.fields[0] =
|
|
static_cast<uint32_t>(encoded_ordered_address);
|
|
ordered_sampled.fields[1] =
|
|
static_cast<uint32_t>(encoded_ordered_address >> 32u) |
|
|
(static_cast<uint32_t>(stencil_format) << 20u);
|
|
ShaderRecompiler::IR::DescriptorValue ordered_descriptor{};
|
|
std::copy(std::begin(ordered_sampled.fields),
|
|
std::end(ordered_sampled.fields),
|
|
ordered_descriptor.dwords.begin());
|
|
ordered_descriptor.dword_count = 8;
|
|
ShaderRecompiler::IR::ResourceSnapshot ordered_snapshot{};
|
|
ordered_snapshot.images.push_back(ordered_descriptor);
|
|
ShaderStageRuntime ordered_sampled_runtime{sampled_runtime.program,
|
|
std::move(ordered_snapshot)};
|
|
auto ordered_bindings = RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, storage_runtime, ordered_sampled_runtime, true);
|
|
const auto ordered_sampled_id =
|
|
ordered_bindings.pixel->images[0].image_id;
|
|
Require(
|
|
name, "VS-before-PS retained-owner order",
|
|
ordered_bindings.vertex.images[0].image_id == storage_id &&
|
|
ordered_sampled_id != storage_id &&
|
|
RenderExecutorTestAccess::BoundImagesInOrder(executor, storage_id,
|
|
ordered_sampled_id),
|
|
"production graphics binding did not retain vertex resources "
|
|
"before pixel resources");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
auto graphics_bindings =
|
|
RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, storage_runtime, sampled_runtime, true);
|
|
const auto &storage_binding =
|
|
graphics_bindings.vertex.images[0];
|
|
const auto &sampled_binding =
|
|
graphics_bindings.pixel->images[0];
|
|
Require(name, "storage final acquisition",
|
|
storage_binding.image_view != nullptr &&
|
|
texture_cache.GetImage(storage_id).usage.storage &&
|
|
texture_cache.GetImage(storage_id).IsGpuModified() &&
|
|
TextureCacheTestAccess::PendingDownload(texture_cache,
|
|
storage_id),
|
|
"the production graphics binding path did not establish GPU "
|
|
"ownership before storage readback");
|
|
Require(name, "VS-to-PS image acquisition order",
|
|
storage_binding.image_id == storage_id &&
|
|
sampled_binding.image_id == storage_id &&
|
|
texture_cache.GetImage(storage_id).binding.force_general &&
|
|
texture_cache.GetImage(storage_id).binding.shader_write &&
|
|
texture_cache.GetImage(storage_id).IsGpuModified() &&
|
|
texture_cache.GetImage(storage_id).usage.storage &&
|
|
texture_cache.GetImage(storage_id).usage.texture,
|
|
"the production graphics binding path did not complete vertex "
|
|
"storage acquisition before pixel sampling");
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), graphics_bindings.vertex,
|
|
*graphics_bindings.pixel));
|
|
Require(name, "early writable alias retention",
|
|
texture_cache.GetImage(storage_id).backing.state.access_mask ==
|
|
(vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eShaderWrite),
|
|
"a later sampled alias dropped an earlier storage write access");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
ShaderRecompiler::IR::Program vertex_sampled_program{};
|
|
vertex_sampled_program.stage = ShaderType::Vertex;
|
|
vertex_sampled_program.resource_tracking_complete = true;
|
|
vertex_sampled_program.info.images.push_back(sampled_resource);
|
|
allocate_bindings(vertex_sampled_program);
|
|
ShaderRecompiler::IR::CompiledShaderInfo vertex_sampled_info{};
|
|
vertex_sampled_info.stage = vertex_sampled_program.stage;
|
|
vertex_sampled_info.info = std::move(vertex_sampled_program.info);
|
|
vertex_sampled_info.bindings =
|
|
std::move(vertex_sampled_program.bindings);
|
|
ShaderStageRuntime vertex_sampled_runtime{
|
|
&vertex_sampled_info, sampled_runtime.resources};
|
|
ShaderRecompiler::IR::Program pixel_storage_program{};
|
|
pixel_storage_program.stage = ShaderType::Pixel;
|
|
pixel_storage_program.resource_tracking_complete = true;
|
|
pixel_storage_program.info.images.push_back(storage_resource);
|
|
allocate_bindings(pixel_storage_program);
|
|
ShaderRecompiler::IR::CompiledShaderInfo pixel_storage_info{};
|
|
pixel_storage_info.stage = pixel_storage_program.stage;
|
|
pixel_storage_info.info = std::move(pixel_storage_program.info);
|
|
pixel_storage_info.bindings = std::move(pixel_storage_program.bindings);
|
|
ShaderStageRuntime pixel_storage_runtime{&pixel_storage_info,
|
|
storage_runtime.resources};
|
|
auto writable_alias_bindings =
|
|
RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, vertex_sampled_runtime, pixel_storage_runtime, true);
|
|
Require(name, "late writable alias promotion",
|
|
texture_cache.GetImage(storage_id).binding.force_general,
|
|
"an already sampled image was not promoted when a later "
|
|
"storage alias bound the same backing");
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), writable_alias_bindings.vertex,
|
|
*writable_alias_bindings.pixel));
|
|
Require(
|
|
name, "forced-general descriptor capture",
|
|
writable_alias_bindings.vertex.images[0].layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
writable_alias_bindings.pixel->images[0].layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
MakeImageInfo(writable_alias_bindings.vertex.images[0])
|
|
.imageLayout == vk::ImageLayout::eGeneral &&
|
|
MakeImageInfo(writable_alias_bindings.pixel->images[0])
|
|
.imageLayout == vk::ImageLayout::eGeneral &&
|
|
texture_cache.GetImage(storage_id).backing.state.layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
texture_cache.GetImage(storage_id).backing.state.access_mask ==
|
|
(vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eShaderWrite),
|
|
"sampled/storage aliases did not retain the promoted general "
|
|
"layout and writable access in both generated descriptors");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
const std::array<uint32_t, 3> split_mip_data{0x01020304u, 0x05060708u,
|
|
0x090a0b0cu};
|
|
std::memcpy(reinterpret_cast<uint8_t *>(mapped) + 0x50000,
|
|
split_mip_data.data(), sizeof(split_mip_data));
|
|
ImageDesc split_desc{};
|
|
split_desc.type = BindingType::Texture;
|
|
split_desc.info.data = {base + 0x50000, sizeof(split_mip_data)};
|
|
split_desc.info.pixel_format = vk::Format::eR32Uint;
|
|
split_desc.info.guest_format = Prospero::BufferFormat::k32UInt;
|
|
split_desc.info.type = Prospero::ImageType::kColor2D;
|
|
split_desc.info.extent = {2, 1, 1};
|
|
split_desc.info.resources = {2, 1};
|
|
split_desc.info.pitch = 2;
|
|
split_desc.info.bytes_per_block = 4;
|
|
split_desc.info.samples = 1;
|
|
split_desc.info.tile_mode = linear;
|
|
split_desc.info.mip_layout[0] = {0, 8, 2, 1};
|
|
split_desc.info.mip_layout[1] = {8, 4, 1, 1};
|
|
split_desc.view_info.format = split_desc.info.pixel_format;
|
|
split_desc.view_info.type = vk::ImageViewType::e2D;
|
|
split_desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
|
|
split_desc.view_info.level_count = 2;
|
|
split_desc.view_info.layer_count = 1;
|
|
split_desc.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto split_id = texture_cache.FindImage(split_desc);
|
|
|
|
auto split_storage_desc = split_desc;
|
|
split_storage_desc.type = BindingType::Storage;
|
|
split_storage_desc.view_info.base_level = 0;
|
|
split_storage_desc.view_info.level_count = 1;
|
|
split_storage_desc.view_info.usage = vk::ImageUsageFlagBits::eStorage;
|
|
auto split_sampled_desc = split_desc;
|
|
split_sampled_desc.view_info.base_level = 1;
|
|
split_sampled_desc.view_info.level_count = 1;
|
|
split_sampled_desc.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
|
|
ShaderRecompiler::IR::Program split_ir{};
|
|
split_ir.stage = ShaderType::Vertex;
|
|
split_ir.resource_tracking_complete = true;
|
|
split_ir.info.images = {storage_resource, sampled_resource};
|
|
allocate_bindings(split_ir);
|
|
ShaderRecompiler::IR::CompiledShaderInfo split_program{};
|
|
split_program.stage = split_ir.stage;
|
|
split_program.info = std::move(split_ir.info);
|
|
split_program.bindings = std::move(split_ir.bindings);
|
|
PreparedBindings split_bindings{};
|
|
ShaderStageRuntime split_runtime{.program = &split_program};
|
|
split_bindings.runtime = &split_runtime;
|
|
split_bindings.images.push_back(
|
|
{split_id, texture_cache.FindTexture(split_id, split_storage_desc),
|
|
split_storage_desc});
|
|
split_bindings.images.push_back(
|
|
{split_id, texture_cache.FindTexture(split_id, split_sampled_desc),
|
|
split_sampled_desc});
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), split_bindings));
|
|
const auto &split_image = texture_cache.GetImage(split_id);
|
|
Require(
|
|
name, "per-binding subresource layouts",
|
|
split_bindings.images[0].layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
split_bindings.images[1].layout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal &&
|
|
MakeImageInfo(split_bindings.images[0]).imageLayout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
MakeImageInfo(split_bindings.images[1]).imageLayout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal &&
|
|
split_image.backing.subresource_states.size() == 2 &&
|
|
split_image.backing.subresource_states[0].layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
split_image.backing.subresource_states[1].layout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
"descriptor layouts were queried after a later mip transition "
|
|
"instead of being captured at each binding");
|
|
|
|
Libs::LibKernel::Memory::WriteBacking(
|
|
storage_address, &storage_stale_value, sizeof(storage_stale_value));
|
|
texture_cache.ProcessDownloadImages();
|
|
Require(
|
|
name, "storage acquisition download consumption",
|
|
!TextureCacheTestAccess::PendingDownload(texture_cache, storage_id),
|
|
"submit-time processing retained an acquired storage request");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
scheduler.Finish();
|
|
scheduler.DrainPriorityOperations();
|
|
uint32_t storage_downloaded_value = 0;
|
|
Libs::LibKernel::Memory::TryReadBacking(storage_address,
|
|
&storage_downloaded_value,
|
|
sizeof(storage_downloaded_value));
|
|
const uint32_t storage_expected_value =
|
|
(storage_stale_value & 0xffffff00u) | (storage_native_value & 0xffu);
|
|
Require(name, "storage acquisition readback window",
|
|
storage_downloaded_value == storage_expected_value &&
|
|
texture_cache.GetImage(storage_id).IsGpuModified(),
|
|
"readback enrollment was consumed before final storage "
|
|
"ownership became visible");
|
|
|
|
constexpr uint64_t phased_depth_address = base + 0x90000;
|
|
constexpr uint64_t phased_stencil_address = base + 0xa0000;
|
|
constexpr uint64_t phased_htile_address = base + 0xb0000;
|
|
HW::DepthRenderTarget phased_depth_target{};
|
|
phased_depth_target.z_info.format = Prospero::DepthFormat::kZ32F;
|
|
phased_depth_target.z_info.texture_compatibility =
|
|
Prospero::TextureCompatiblePlaneCompression::kEnable;
|
|
phased_depth_target.z_info.htile_acceleration = true;
|
|
phased_depth_target.z_info.z_compare_base = Prospero::ZCompareBase::kZMax;
|
|
phased_depth_target.stencil_info.format = Prospero::StencilFormat::k8UInt;
|
|
phased_depth_target.stencil_info.texture_compatibility =
|
|
Prospero::TextureCompatibleStencil::kEnable;
|
|
phased_depth_target.stencil_info.htile_stencil_disabled = false;
|
|
phased_depth_target.z_read_base_addr = phased_depth_address;
|
|
phased_depth_target.z_write_base_addr = phased_depth_address;
|
|
phased_depth_target.stencil_read_base_addr = phased_stencil_address;
|
|
phased_depth_target.stencil_write_base_addr = phased_stencil_address;
|
|
phased_depth_target.htile_data_base_addr = phased_htile_address;
|
|
phased_depth_target.size.valid = true;
|
|
registers.SetDepthRenderTarget(phased_depth_target);
|
|
HW::DepthControl phased_depth_control{};
|
|
phased_depth_control.z_write_enable = true;
|
|
registers.SetDepthControl(phased_depth_control);
|
|
registers.SetRenderControl({});
|
|
RenderDepthInfo disabled_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), disabled_depth);
|
|
Require(name, "disabled tests with stale depth write enable",
|
|
!disabled_depth.image_id,
|
|
"a dormant depth write bit bound a depth attachment");
|
|
phased_depth_control.z_enable = true;
|
|
registers.SetDepthControl(phased_depth_control);
|
|
HW::RenderControl phased_render_control{};
|
|
phased_render_control.resummarize_enable = true;
|
|
registers.SetRenderControl(phased_render_control);
|
|
|
|
RenderDepthInfo phased_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), phased_depth);
|
|
auto non_texture_compatible_target = phased_depth_target;
|
|
non_texture_compatible_target.z_info.texture_compatibility =
|
|
Prospero::TextureCompatiblePlaneCompression::kDisable;
|
|
non_texture_compatible_target.stencil_info.texture_compatibility =
|
|
Prospero::TextureCompatibleStencil::kDisable;
|
|
registers.SetDepthRenderTarget(non_texture_compatible_target);
|
|
RenderDepthInfo non_texture_compatible_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), non_texture_compatible_depth);
|
|
Require(
|
|
name, "depth texture compatibility identity",
|
|
phased_depth.image_id && (phased_depth.desc.info.metadata.kind == ImageMetadataKind::Htile) &&
|
|
non_texture_compatible_depth.image_id == phased_depth.image_id &&
|
|
non_texture_compatible_depth.desc.info.data ==
|
|
phased_depth.desc.info.data &&
|
|
non_texture_compatible_depth.desc.info.stencil ==
|
|
phased_depth.desc.info.stencil &&
|
|
non_texture_compatible_depth.desc.info.metadata.range ==
|
|
phased_depth.desc.info.metadata.range &&
|
|
phased_depth.desc.info.metadata.stencil_compressed &&
|
|
!phased_depth.depth_clear_enable &&
|
|
!texture_cache.IsMeta(phased_depth.desc.info.metadata.range.address) &&
|
|
!texture_cache.GetImage(phased_depth.image_id).IsGpuModified() &&
|
|
!texture_cache.GetImage(phased_depth.image_id).usage.depth_target,
|
|
"valid PS5 texture-compatibility policy changed logical depth image "
|
|
"identity or discovery side effects");
|
|
|
|
auto read_only_depth_target = phased_depth_target;
|
|
read_only_depth_target.z_write_base_addr = 0;
|
|
read_only_depth_target.stencil_write_base_addr = 0;
|
|
read_only_depth_target.depth_view.depth_write_disable = true;
|
|
read_only_depth_target.depth_view.stencil_write_disable = true;
|
|
registers.SetDepthRenderTarget(read_only_depth_target);
|
|
auto read_only_depth_control = phased_depth_control;
|
|
read_only_depth_control.z_write_enable = false;
|
|
read_only_depth_control.zfunc =
|
|
static_cast<uint8_t>(vk::CompareOp::eAlways);
|
|
read_only_depth_control.stencil_enable = true;
|
|
read_only_depth_control.stencilfunc =
|
|
static_cast<uint8_t>(vk::CompareOp::eAlways);
|
|
registers.SetDepthControl(read_only_depth_control);
|
|
HW::StencilControl read_only_stencil_control{};
|
|
read_only_stencil_control.stencil_zpass =
|
|
static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
|
|
registers.SetStencilControl(read_only_stencil_control);
|
|
HW::StencilMask read_only_stencil_mask{};
|
|
read_only_stencil_mask.stencil_testval = 0x12;
|
|
read_only_stencil_mask.stencil_mask = 0xff;
|
|
read_only_stencil_mask.stencil_writemask = 0xff;
|
|
read_only_stencil_mask.stencil_opval = 0x34;
|
|
registers.SetStencilMask(read_only_stencil_mask);
|
|
RenderDepthInfo read_only_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), read_only_depth);
|
|
Require(
|
|
name, "read-only depth/stencil target without write addresses",
|
|
read_only_depth.image_id == phased_depth.image_id &&
|
|
read_only_depth.desc.info.data.address == phased_depth_address &&
|
|
read_only_depth.desc.info.stencil.address == phased_stencil_address &&
|
|
!read_only_depth.depth_write_enable &&
|
|
!read_only_depth.stencil_clear_enable &&
|
|
read_only_depth.stencil_test_enable &&
|
|
read_only_depth.stencil_dynamic_front.writeMask == 0 &&
|
|
read_only_depth.stencil_dynamic_back.writeMask == 0 &&
|
|
read_only_depth.stencil_static_front.passOp ==
|
|
vk::StencilOp::eKeep &&
|
|
!read_only_depth.AttachmentWriteAspects() &&
|
|
depth_attachment_layout(read_only_depth) ==
|
|
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
|
"a PS5 read-only depth/stencil target required write addresses or "
|
|
"retained host writes");
|
|
|
|
auto read_only_render_control = phased_render_control;
|
|
read_only_render_control.stencil_clear_enable = true;
|
|
registers.SetRenderControl(read_only_render_control);
|
|
RenderDepthInfo read_only_clear_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), read_only_clear_depth);
|
|
Require(name, "read-only stencil clear suppression",
|
|
read_only_clear_depth.image_id == phased_depth.image_id &&
|
|
!read_only_clear_depth.stencil_clear_enable &&
|
|
!read_only_clear_depth.AttachmentWriteAspects() &&
|
|
depth_attachment_layout(read_only_clear_depth) ==
|
|
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
|
"a PS5 target-level stencil write disable retained a host clear");
|
|
|
|
registers.SetDepthRenderTarget(phased_depth_target);
|
|
registers.SetDepthControl(phased_depth_control);
|
|
registers.SetStencilControl({});
|
|
registers.SetStencilMask({});
|
|
registers.SetRenderControl(phased_render_control);
|
|
RenderColorInfo no_color{};
|
|
auto phased_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &no_color, 0, phased_depth);
|
|
Require(
|
|
name, "HTile final acquisition",
|
|
phased_rendering.depth_stencil_attachment.image_view != nullptr &&
|
|
phased_rendering.num_color_attachments == 0 &&
|
|
phased_rendering.depth_stencil_attachment.has_depth &&
|
|
phased_rendering.depth_stencil_attachment.depth_clear &&
|
|
phased_depth.depth_load_clear_enable &&
|
|
texture_cache.IsMeta(phased_depth.desc.info.metadata.range.address) &&
|
|
!texture_cache.IsMetaCleared(
|
|
phased_depth.desc.info.metadata.range.address,
|
|
phased_depth.desc.view_info.base_layer) &&
|
|
texture_cache.GetImage(phased_depth.image_id).IsGpuModified() &&
|
|
texture_cache.GetImage(phased_depth.image_id).usage.depth_target,
|
|
"final depth acquisition did not publish and consume HTile "
|
|
"state at the final acquisition boundary");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
constexpr uint64_t depth_only_address = base + 0x140000;
|
|
constexpr uint64_t depth_only_htile_address = base + 0x150000;
|
|
constexpr uint32_t captured_depth_only_z_info = 0x22900983u;
|
|
constexpr uint32_t captured_depth_only_stencil_info = 0x00100980u;
|
|
HW::DepthRenderTarget depth_only_target{};
|
|
depth_only_target.z_info =
|
|
HW::DepthZInfo::Decode(captured_depth_only_z_info);
|
|
depth_only_target.stencil_info =
|
|
HW::DepthStencilInfo::Decode(captured_depth_only_stencil_info);
|
|
depth_only_target.z_read_base_addr = depth_only_address;
|
|
depth_only_target.z_write_base_addr = depth_only_address;
|
|
depth_only_target.htile_data_base_addr = depth_only_htile_address;
|
|
depth_only_target.size = {63, 63, true};
|
|
registers.SetDepthRenderTarget(depth_only_target);
|
|
HW::DepthControl depth_only_control{};
|
|
depth_only_control.stencil_enable = true;
|
|
depth_only_control.z_enable = true;
|
|
depth_only_control.z_write_enable = true;
|
|
depth_only_control.zfunc = static_cast<uint8_t>(vk::CompareOp::eAlways);
|
|
registers.SetDepthControl(depth_only_control);
|
|
HW::RenderControl depth_only_render_control{};
|
|
depth_only_render_control.depth_clear_enable = true;
|
|
depth_only_render_control.stencil_clear_enable = true;
|
|
registers.SetRenderControl(depth_only_render_control);
|
|
RenderDepthInfo depth_only{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), depth_only);
|
|
Require(
|
|
name, "depth-only target with stale stencil state",
|
|
depth_only.image_id && depth_only.desc.view_info.format == vk::Format::eD32Sfloat &&
|
|
(depth_only.desc.info.metadata.kind == ImageMetadataKind::Htile) &&
|
|
depth_only.depth_test_enable && !depth_only.depth_write_enable &&
|
|
depth_only.depth_clear_enable &&
|
|
!depth_only.stencil_test_enable &&
|
|
!depth_only.stencil_clear_enable &&
|
|
depth_only.desc.info.stencil.Empty() &&
|
|
depth_only.desc.info.metadata.range.address ==
|
|
depth_only_htile_address &&
|
|
depth_only.desc.info.data.size != 0 &&
|
|
depth_only_address + depth_only.desc.info.data.size <=
|
|
base + allocation_size &&
|
|
depth_only.AttachmentWriteAspects() ==
|
|
vk::ImageAspectFlagBits::eDepth,
|
|
"inactive stencil state changed a depth-only HTile attachment");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
const auto sampled_width = depth_only.desc.info.extent.width - 1u;
|
|
const auto sampled_height = depth_only.desc.info.extent.height - 1u;
|
|
const auto sampled_address = depth_only_address >> 8u;
|
|
ShaderTextureResource sampled_depth_descriptor{};
|
|
sampled_depth_descriptor.fields[0] =
|
|
static_cast<uint32_t>(sampled_address);
|
|
sampled_depth_descriptor.fields[1] =
|
|
static_cast<uint32_t>(sampled_address >> 32u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32UInt) << 20u) |
|
|
((sampled_width & 3u) << 30u);
|
|
sampled_depth_descriptor.fields[2] =
|
|
(sampled_width >> 2u) | (sampled_height << 14u);
|
|
sampled_depth_descriptor.fields[3] =
|
|
DstSel(4, 4, 4, 4) |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kDepth) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
sampled_depth_descriptor.fields[5] = 0x00700000u;
|
|
ShaderRecompiler::IR::DescriptorValue sampled_depth_value{};
|
|
std::copy(std::begin(sampled_depth_descriptor.fields),
|
|
std::end(sampled_depth_descriptor.fields),
|
|
sampled_depth_value.dwords.begin());
|
|
sampled_depth_value.dword_count = 8;
|
|
ShaderRecompiler::IR::ImageResource sampled_depth_resource{};
|
|
sampled_depth_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
sampled_depth_resource.numeric_class =
|
|
Prospero::TextureNumericClass::Uint;
|
|
sampled_depth_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
sampled_depth_resource.read = true;
|
|
const auto sampled_depth_backing =
|
|
texture_cache.GetImage(depth_only.image_id).backing.image;
|
|
auto sampled_depth_binding = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, sampled_depth_resource, sampled_depth_value);
|
|
const auto sampled_depth_view = texture_cache.FindTexture(
|
|
sampled_depth_binding.image_id, sampled_depth_binding.desc);
|
|
const auto &sampled_depth_image =
|
|
texture_cache.GetImage(sampled_depth_binding.image_id);
|
|
const bool normalized_depth_view = std::ranges::any_of(
|
|
sampled_depth_image.views, [&](const auto &cached) {
|
|
return cached.view == sampled_depth_view &&
|
|
cached.info.format == vk::Format::eD32Sfloat &&
|
|
cached.info.aspect == vk::ImageAspectFlagBits::eDepth &&
|
|
cached.info.usage == vk::ImageUsageFlags{};
|
|
});
|
|
Require(
|
|
name, "R32 uint depth-backed view",
|
|
sampled_depth_binding.image_id == depth_only.image_id &&
|
|
sampled_depth_binding.desc.info.guest_format ==
|
|
Prospero::BufferFormat::k32UInt &&
|
|
sampled_depth_binding.desc.info.pixel_format ==
|
|
vk::Format::eR32Uint &&
|
|
sampled_depth_binding.desc.view_info.format ==
|
|
vk::Format::eR32Uint &&
|
|
sampled_depth_binding.desc.view_info.aspect ==
|
|
vk::ImageAspectFlagBits::eColor &&
|
|
sampled_depth_binding.desc.view_info.usage ==
|
|
vk::ImageUsageFlagBits::eSampled &&
|
|
sampled_depth_image.info.pixel_format == vk::Format::eD32Sfloat &&
|
|
sampled_depth_image.backing.format == vk::Format::eD32Sfloat &&
|
|
sampled_depth_image.backing.image == sampled_depth_backing &&
|
|
sampled_depth_view != nullptr && normalized_depth_view,
|
|
"uint T# did not retain its requested format while the concrete "
|
|
"view followed the D32 backing");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
// Attachment acquisition consumes pending HTile clears using DB_DEPTH_CLEAR.
|
|
(void)texture_cache.FindDepthTarget(depth_only.image_id, depth_only.desc);
|
|
auto bounds_target = depth_only_target;
|
|
bounds_target.z_write_base_addr = 0;
|
|
bounds_target.depth_view.depth_write_disable = true;
|
|
registers.SetDepthRenderTarget(bounds_target);
|
|
HW::DepthControl bounds_control{};
|
|
bounds_control.depth_bounds_enable = true;
|
|
registers.SetDepthControl(bounds_control);
|
|
registers.SetRenderControl({});
|
|
registers.SetDepthClearValue(0.375f);
|
|
ShaderStageRuntime bounds_vertex{&vertex_sampled_info, {}};
|
|
ShaderStageRuntime bounds_pixel{&sampled_info, {}};
|
|
bounds_vertex.resources.images.push_back(sampled_depth_value);
|
|
bounds_pixel.resources.images.push_back(sampled_depth_value);
|
|
const auto depth_texels = depth_only.desc.info.extent.width *
|
|
depth_only.desc.info.extent.height;
|
|
const auto depth_bytes = depth_texels * sizeof(uint32_t);
|
|
auto bounds_readback = CreateHostBuffer(name, depth_bytes * 2,
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
for (uint32_t pass = 0; pass < 2; ++pass) {
|
|
Require(name, "bounds prior depth contents",
|
|
texture_cache.ClearImageFromBuffer(scheduler.Current(),
|
|
depth_only.desc.info.data.address, depth_only.desc.info.data.size,
|
|
std::bit_cast<uint32_t>(0.625f)),
|
|
"failed to seed depth before the deferred HTile clear");
|
|
if (pass == 0) {
|
|
Require(name, "bounds deferred HTile clear",
|
|
texture_cache.ClearMeta(depth_only_htile_address),
|
|
"depth-only HTile was not registered");
|
|
} else {
|
|
bounds_target.z_write_base_addr = depth_only_address;
|
|
bounds_target.depth_view.depth_write_disable = false;
|
|
bounds_control.z_write_enable = true;
|
|
registers.SetDepthRenderTarget(bounds_target);
|
|
registers.SetDepthControl(bounds_control);
|
|
}
|
|
RenderDepthInfo bounds_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), bounds_depth);
|
|
auto bounds_bindings = RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, bounds_vertex, bounds_pixel, true);
|
|
const auto bounds_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &no_color, 0, bounds_depth, bounds_bindings.pixel);
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), bounds_bindings.vertex, *bounds_bindings.pixel));
|
|
const auto &vertex_depth = bounds_bindings.vertex.images[0];
|
|
const auto &pixel_depth = bounds_bindings.pixel->images[0];
|
|
constexpr auto readonly_layout = vk::ImageLayout::eDepthReadOnlyOptimal;
|
|
Require(name, "deferred clear with sampled read-only depth bounds",
|
|
bounds_depth.image_id == depth_only.image_id &&
|
|
bounds_depth.depth_bounds_test_enable &&
|
|
!bounds_depth.depth_write_enable &&
|
|
bounds_depth.depth_load_clear_enable == (pass == 0) &&
|
|
!texture_cache.IsMetaCleared(depth_only_htile_address, 0) &&
|
|
bounds_rendering.depth_stencil_attachment.image_layout == readonly_layout &&
|
|
bounds_rendering.depth_stencil_attachment.depth_clear == (pass == 0) &&
|
|
vertex_depth.image_id == depth_only.image_id &&
|
|
pixel_depth.image_id == depth_only.image_id &&
|
|
MakeImageInfo(vertex_depth).imageLayout == readonly_layout &&
|
|
MakeImageInfo(pixel_depth).imageLayout == readonly_layout,
|
|
"deferred clear changed guest depth writes, sampled layouts, or repeated");
|
|
scheduler.BeginRendering(bounds_rendering);
|
|
scheduler.EndRendering();
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
const vk::BufferImageCopy copy{pass * depth_bytes, 0, 0,
|
|
{vk::ImageAspectFlagBits::eDepth, 0, 0, 1}, {}, depth_only.desc.info.extent};
|
|
texture_cache.GetImage(depth_only.image_id).Download(
|
|
std::span{©, 1}, bounds_readback.buffer, 0, bounds_readback.size);
|
|
}
|
|
vk::MemoryBarrier2 bounds_host_barrier{};
|
|
bounds_host_barrier.sType = vk::StructureType::eMemoryBarrier2;
|
|
bounds_host_barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
|
bounds_host_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
|
bounds_host_barrier.dstStageMask = vk::PipelineStageFlagBits2::eHost;
|
|
bounds_host_barrier.dstAccessMask = vk::AccessFlagBits2::eHostRead;
|
|
vk::DependencyInfo bounds_dependency{};
|
|
bounds_dependency.sType = vk::StructureType::eDependencyInfo;
|
|
bounds_dependency.memoryBarrierCount = 1;
|
|
bounds_dependency.pMemoryBarriers = &bounds_host_barrier;
|
|
scheduler.Current().Handle().pipelineBarrier2(bounds_dependency);
|
|
scheduler.Finish();
|
|
const auto bounds_result = ReadBuffer(name, bounds_readback, depth_texels * 2);
|
|
Require(name, "deferred HTile clear occurs once",
|
|
std::ranges::all_of(std::span{bounds_result}.first(depth_texels),
|
|
[](uint32_t value) { return value == std::bit_cast<uint32_t>(0.375f); }) &&
|
|
std::ranges::all_of(std::span{bounds_result}.subspan(depth_texels),
|
|
[](uint32_t value) { return value == std::bit_cast<uint32_t>(0.625f); }),
|
|
"pending clear did not use DB_DEPTH_CLEAR or cleared the next acquisition again");
|
|
DestroyBuffer(&bounds_readback);
|
|
registers.SetDepthClearValue(0.0f);
|
|
|
|
// DB override copies run with depth and stencil tests disabled.
|
|
constexpr uint64_t copy_source_depth = base + 0x180000;
|
|
constexpr uint64_t copy_source_stencil = base + 0x1a0000;
|
|
constexpr uint64_t copy_destination_depth = base + 0x1c0000;
|
|
constexpr uint64_t copy_destination_stencil = base + 0x1e0000;
|
|
constexpr uint32_t copy_side = 8;
|
|
constexpr uint32_t copy_layer_texels = copy_side * copy_side;
|
|
constexpr uint32_t copy_depth_bytes =
|
|
copy_layer_texels * 2 * sizeof(uint32_t);
|
|
constexpr uint32_t copy_readback_stride =
|
|
copy_depth_bytes + copy_layer_texels * 2;
|
|
HW::DepthRenderTarget copy_target{};
|
|
copy_target.z_info.format = Prospero::DepthFormat::kZ32F;
|
|
copy_target.z_info.texture_compatibility =
|
|
Prospero::TextureCompatiblePlaneCompression::kEnable;
|
|
copy_target.stencil_info.format = Prospero::StencilFormat::k8UInt;
|
|
copy_target.stencil_info.texture_compatibility =
|
|
Prospero::TextureCompatibleStencil::kEnable;
|
|
copy_target.stencil_info.htile_stencil_disabled = true;
|
|
copy_target.depth_view.slice_max = 1;
|
|
copy_target.size = {copy_side - 1, copy_side - 1, true};
|
|
HW::DepthControl copy_seed_control{};
|
|
copy_seed_control.z_enable = true;
|
|
copy_seed_control.z_write_enable = true;
|
|
copy_seed_control.stencil_enable = true;
|
|
registers.SetDepthControl(copy_seed_control);
|
|
registers.SetRenderControl({});
|
|
const auto make_copy_target = [&](uint64_t depth, uint64_t stencil) {
|
|
auto target = copy_target;
|
|
target.z_read_base_addr = depth;
|
|
target.z_write_base_addr = depth;
|
|
target.stencil_read_base_addr = stencil;
|
|
target.stencil_write_base_addr = stencil;
|
|
registers.SetDepthRenderTarget(target);
|
|
RenderDepthInfo resolved{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), resolved);
|
|
(void)texture_cache.FindDepthTarget(resolved.image_id, resolved.desc);
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
return resolved;
|
|
};
|
|
const auto copy_source =
|
|
make_copy_target(copy_source_depth, copy_source_stencil);
|
|
const auto copy_destination =
|
|
make_copy_target(copy_destination_depth, copy_destination_stencil);
|
|
const auto clear_copy_target = [&](ImageId id, bool source) {
|
|
auto &image = texture_cache.GetImage(id);
|
|
image.Transit(vk::ImageLayout::eTransferDstOptimal,
|
|
vk::AccessFlagBits2::eTransferWrite, {},
|
|
scheduler.Current().Handle());
|
|
for (uint32_t layer = 0; layer < 2; ++layer) {
|
|
const vk::ClearDepthStencilValue clear{
|
|
source ? (layer == 0 ? 0.75f : 0.25f)
|
|
: (layer == 0 ? 0.875f : 0.625f),
|
|
source ? (layer == 0 ? 0xa3u : 0x5au)
|
|
: (layer == 0 ? 0x31u : 0xc4u)};
|
|
const vk::ImageSubresourceRange range{
|
|
vk::ImageAspectFlagBits::eDepth |
|
|
vk::ImageAspectFlagBits::eStencil,
|
|
0, 1, layer, 1};
|
|
scheduler.Current().Handle().clearDepthStencilImage(
|
|
image.backing.image, vk::ImageLayout::eTransferDstOptimal, &clear,
|
|
1, &range);
|
|
}
|
|
texture_cache.MarkGpuWritten(id);
|
|
};
|
|
struct CopyCase {
|
|
const char *label;
|
|
HW::DepthRenderOverride override;
|
|
uint32_t color_mode;
|
|
bool depth;
|
|
bool stencil;
|
|
};
|
|
constexpr std::array copy_cases{
|
|
CopyCase{"no override", {}, 0, false, false},
|
|
CopyCase{
|
|
"depth valid only", {true, false, false, false}, 0, false, false},
|
|
CopyCase{
|
|
"depth dirty only", {false, true, false, false}, 0, false, false},
|
|
CopyCase{"stencil valid only",
|
|
{false, false, true, false},
|
|
0,
|
|
false,
|
|
false},
|
|
CopyCase{"stencil dirty only",
|
|
{false, false, false, true},
|
|
0,
|
|
false,
|
|
false},
|
|
CopyCase{
|
|
"unpaired aspects", {true, false, false, true}, 0, false, false},
|
|
CopyCase{"color enabled", {true, true, true, true}, 1, false, false},
|
|
CopyCase{"depth only", {true, true, false, false}, 0, true, false},
|
|
CopyCase{"stencil only", {false, false, true, true}, 0, false, true},
|
|
CopyCase{
|
|
"depth and stencil", {true, true, true, true}, 0, true, true}};
|
|
auto copy_readback =
|
|
CreateHostBuffer(name, copy_cases.size() * copy_readback_stride,
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
copy_target.z_read_base_addr = copy_source_depth;
|
|
copy_target.z_write_base_addr = copy_destination_depth;
|
|
copy_target.stencil_read_base_addr = copy_source_stencil;
|
|
copy_target.stencil_write_base_addr = copy_destination_stencil;
|
|
copy_target.depth_view.slice_start = 1;
|
|
registers.SetDepthRenderTarget(copy_target);
|
|
registers.SetDepthControl({});
|
|
RenderDepthInfo copy_disabled_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), copy_disabled_depth);
|
|
Require(name, "copy without attachment tests",
|
|
!copy_disabled_depth.image_id,
|
|
"disabled depth/stencil tests acquired a regular attachment");
|
|
for (size_t index = 0; index < copy_cases.size(); ++index) {
|
|
const auto &test = copy_cases[index];
|
|
clear_copy_target(copy_source.image_id, true);
|
|
clear_copy_target(copy_destination.image_id, false);
|
|
registers.SetDepthRenderOverride(test.override);
|
|
auto color_control = registers.GetColorControl();
|
|
color_control.mode = test.color_mode;
|
|
registers.SetColorControl(color_control);
|
|
Require(name, test.label,
|
|
RenderExecutorTestAccess::DepthStencilCopy(
|
|
executor, scheduler.Current()) ==
|
|
(test.depth || test.stencil),
|
|
"DB override copy did not honor paired flags and disabled "
|
|
"color output");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
const auto offset = index * copy_readback_stride;
|
|
const std::array<vk::BufferImageCopy, 2> copies{
|
|
{{offset,
|
|
0,
|
|
0,
|
|
{vk::ImageAspectFlagBits::eDepth, 0, 0, 2},
|
|
{},
|
|
copy_destination.desc.info.extent},
|
|
{offset + copy_depth_bytes,
|
|
0,
|
|
0,
|
|
{vk::ImageAspectFlagBits::eStencil, 0, 0, 2},
|
|
{},
|
|
copy_destination.desc.info.extent}}};
|
|
texture_cache.GetImage(copy_destination.image_id)
|
|
.Download(copies, copy_readback.buffer, 0, copy_readback.size);
|
|
}
|
|
scheduler.Current().Handle().pipelineBarrier2(bounds_dependency);
|
|
scheduler.Finish();
|
|
const auto copy_result = ReadBuffer(
|
|
name, copy_readback,
|
|
copy_cases.size() * copy_readback_stride / sizeof(uint32_t));
|
|
for (size_t index = 0; index < copy_cases.size(); ++index) {
|
|
const auto &test = copy_cases[index];
|
|
const auto *depth = copy_result.data() +
|
|
index * copy_readback_stride / sizeof(uint32_t);
|
|
const auto *stencil =
|
|
reinterpret_cast<const uint8_t *>(depth) + copy_depth_bytes;
|
|
for (uint32_t layer = 0; layer < 2; ++layer) {
|
|
const auto expected_depth =
|
|
std::bit_cast<uint32_t>(layer == 0 ? 0.875f
|
|
: test.depth ? 0.25f
|
|
: 0.625f);
|
|
const auto expected_stencil = layer == 0 ? 0x31u
|
|
: test.stencil ? 0x5au
|
|
: 0xc4u;
|
|
for (uint32_t texel = 0; texel < copy_layer_texels; ++texel) {
|
|
const auto position = layer * copy_layer_texels + texel;
|
|
Require(name, test.label,
|
|
depth[position] == expected_depth &&
|
|
stencil[position] == expected_stencil,
|
|
"DB override copy changed an unselected layer/aspect or "
|
|
"missed copied texels");
|
|
}
|
|
}
|
|
}
|
|
DestroyBuffer(©_readback);
|
|
|
|
ShaderTextureResource copy_sample_descriptor{};
|
|
copy_sample_descriptor.fields[0] =
|
|
static_cast<uint32_t>(copy_destination_depth >> 8u);
|
|
copy_sample_descriptor.fields[1] =
|
|
static_cast<uint32_t>(copy_destination_depth >> 40u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32Float) << 20u) |
|
|
(((copy_side - 1u) & 3u) << 30u);
|
|
copy_sample_descriptor.fields[2] =
|
|
((copy_side - 1u) >> 2u) | ((copy_side - 1u) << 14u);
|
|
copy_sample_descriptor.fields[3] =
|
|
DstSel(4, 4, 4, 4) |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kDepth) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DArray) << 28u);
|
|
copy_sample_descriptor.fields[4] = 1;
|
|
copy_sample_descriptor.fields[5] = 0x00700000u;
|
|
TestCase copy_sample_test;
|
|
copy_sample_test.name = "DepthOverrideR32Sampling";
|
|
copy_sample_test.has_user_data = true;
|
|
copy_sample_test.image_descriptor_swizzle =
|
|
copy_sample_descriptor.DstSelXYZW();
|
|
std::copy_n(copy_sample_descriptor.fields, 8,
|
|
copy_sample_test.user_data.begin());
|
|
copy_sample_test.user_data[50] = 2 * sizeof(uint32_t);
|
|
copy_sample_test.opcodes = {
|
|
ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_SAMPLE,
|
|
ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM};
|
|
copy_sample_test.required_spirv = {"OpImageSampleExplicitLod"};
|
|
for (uint32_t layer = 0; layer < 2; ++layer) {
|
|
AppendVMovLiteral(©_sample_test.code, 20,
|
|
std::bit_cast<uint32_t>(0.5f));
|
|
AppendVMovLiteral(©_sample_test.code, 21,
|
|
std::bit_cast<uint32_t>(0.5f));
|
|
AppendVMovLiteral(©_sample_test.code, 22,
|
|
std::bit_cast<uint32_t>(static_cast<float>(layer)));
|
|
copy_sample_test.code.push_back(EncodeMimg0(0x20, 1, 0, false, 5));
|
|
copy_sample_test.code.push_back(EncodeMimg1(0, 20, 0, 2));
|
|
AppendStoreVgpr(©_sample_test.code, 0, layer);
|
|
copy_sample_test.expected.push_back(
|
|
std::bit_cast<uint32_t>(layer == 0 ? 0.875f : 0.25f));
|
|
}
|
|
AppendEnd(©_sample_test.code);
|
|
const auto copy_sample_program =
|
|
CompileCase(copy_sample_test, SubgroupSize());
|
|
ShaderRecompiler::IR::DescriptorValue copy_sample_value{};
|
|
copy_sample_value.dword_count = 8;
|
|
std::copy_n(copy_sample_descriptor.fields, 8,
|
|
copy_sample_value.dwords.begin());
|
|
const auto copy_sample_binding = RenderExecutorTestAccess::ResolveTexture(
|
|
executor, copy_sample_program.program.info.images[0],
|
|
copy_sample_value);
|
|
const auto copy_sample_view = texture_cache.FindTexture(
|
|
copy_sample_binding.image_id, copy_sample_binding.desc);
|
|
Require(
|
|
name, "copied depth sampled owner",
|
|
copy_sample_binding.image_id == copy_destination.image_id &&
|
|
copy_sample_binding.desc.info.pixel_format ==
|
|
vk::Format::eR32Sfloat,
|
|
"R32 sampling did not reuse the GPU-written depth-copy destination");
|
|
auto ©_sample_image = texture_cache.GetImage(copy_destination.image_id);
|
|
copy_sample_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, {},
|
|
scheduler.Current().Handle());
|
|
Image copied_depth_sample;
|
|
copied_depth_sample.view = copy_sample_view;
|
|
copied_depth_sample.layout = copy_sample_image.backing.state.layout;
|
|
scheduler.Finish();
|
|
auto copy_sample_output = CreateStorageBuffer(name, {}, 2);
|
|
const auto copy_sampler = CreateNearestSampler(name);
|
|
Dispatch(copy_sample_test, copy_sample_program, copy_sample_output,
|
|
nullptr, &copied_depth_sample, nullptr, nullptr, copy_sampler);
|
|
Require(
|
|
name, "copied depth R32 samples",
|
|
ReadBuffer(name, copy_sample_output, 2) == copy_sample_test.expected,
|
|
"R32 post-effect sampling saw stale depth after the override copy");
|
|
m_device.destroySampler(copy_sampler);
|
|
DestroyBuffer(©_sample_output);
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
registers.SetDepthRenderOverride({});
|
|
registers.SetColorControl({});
|
|
|
|
auto shared_depth_descriptor = sampled_depth_descriptor;
|
|
shared_depth_descriptor.fields[0] =
|
|
static_cast<uint32_t>(phased_depth_address >> 8u);
|
|
shared_depth_descriptor.fields[1] =
|
|
static_cast<uint32_t>(phased_depth_address >> 40u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32UInt) << 20u);
|
|
shared_depth_descriptor.fields[2] = 0;
|
|
ShaderRecompiler::IR::DescriptorValue shared_depth_value{};
|
|
std::copy(std::begin(shared_depth_descriptor.fields),
|
|
std::end(shared_depth_descriptor.fields),
|
|
shared_depth_value.dwords.begin());
|
|
shared_depth_value.dword_count = 8;
|
|
ShaderStageRuntime shared_depth_vertex{&vertex_sampled_info, {}};
|
|
ShaderStageRuntime shared_depth_pixel{&sampled_info, {}};
|
|
shared_depth_vertex.resources.images.push_back(shared_depth_value);
|
|
shared_depth_pixel.resources.images.push_back(shared_depth_value);
|
|
for (const bool stencil_write : {true, false}) {
|
|
registers.SetDepthRenderTarget(phased_depth_target);
|
|
registers.SetRenderControl({});
|
|
registers.SetDepthControl(read_only_depth_control);
|
|
HW::StencilControl shared_stencil_control{};
|
|
shared_stencil_control.stencil_zpass =
|
|
static_cast<uint8_t>(Prospero::StencilOp::kReplaceTest);
|
|
registers.SetStencilControl(shared_stencil_control);
|
|
HW::StencilMask shared_stencil_mask{};
|
|
shared_stencil_mask.stencil_testval = 0x20;
|
|
shared_stencil_mask.stencil_mask = 0xff;
|
|
shared_stencil_mask.stencil_writemask = stencil_write ? 0xf4 : 0;
|
|
registers.SetStencilMask(shared_stencil_mask);
|
|
RenderDepthInfo shared_depth{};
|
|
RenderExecutorTestAccess::ResolveRenderDepthTarget(
|
|
executor, scheduler.Current(), shared_depth);
|
|
auto shared_bindings =
|
|
RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, shared_depth_vertex, shared_depth_pixel, true);
|
|
const auto shared_rendering =
|
|
RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &no_color, 0, shared_depth, shared_bindings.pixel);
|
|
descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings(
|
|
executor, scheduler.Current(), shared_bindings.vertex,
|
|
*shared_bindings.pixel));
|
|
const auto expected_layout =
|
|
stencil_write
|
|
? vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal
|
|
: vk::ImageLayout::eDepthStencilReadOnlyOptimal;
|
|
const auto expected_access =
|
|
vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eDepthStencilAttachmentRead |
|
|
vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
|
|
const auto &shared_image = texture_cache.GetImage(shared_depth.image_id);
|
|
const auto &vertex_image = shared_bindings.vertex.images[0];
|
|
const auto &pixel_image = shared_bindings.pixel->images[0];
|
|
Require(name, "sampled depth and stencil attachment layout",
|
|
shared_depth.image_id == phased_depth.image_id &&
|
|
vertex_image.image_id == shared_depth.image_id &&
|
|
pixel_image.image_id == shared_depth.image_id &&
|
|
MakeImageInfo(vertex_image).imageLayout == expected_layout &&
|
|
MakeImageInfo(pixel_image).imageLayout == expected_layout &&
|
|
shared_rendering.depth_stencil_attachment.image_layout ==
|
|
expected_layout &&
|
|
shared_image.backing.state.layout == expected_layout &&
|
|
shared_image.backing.state.access_mask == expected_access,
|
|
"a shader alias replaced the depth/stencil attachment layout "
|
|
"or dropped an attachment access");
|
|
scheduler.BeginRendering(shared_rendering);
|
|
scheduler.EndRendering();
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
Require(name, "attachment binding reset",
|
|
!shared_image.binding.is_target &&
|
|
shared_image.binding.attachment_layout ==
|
|
vk::ImageLayout::eUndefined &&
|
|
!shared_image.binding.attachment_access,
|
|
"attachment usage leaked into the next draw");
|
|
}
|
|
|
|
auto video_subresource =
|
|
make_target_desc(base + 0x20000, target_mip_size, {1, 1, 1});
|
|
video_subresource.type = BindingType::VideoOut;
|
|
video_subresource.info.pixel_format = vk::Format::eR8G8B8A8Srgb;
|
|
video_subresource.info.guest_format =
|
|
Prospero::BufferFormat::k8_8_8_8Srgb;
|
|
video_subresource.view_info.format = video_subresource.info.pixel_format;
|
|
video_subresource.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto video_subresource_id =
|
|
texture_cache.FindImage(video_subresource);
|
|
const auto video_subresource_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, video_subresource_id);
|
|
auto video_parent =
|
|
make_target_desc(base + 0x20000, target_mip_size * 2, {2, 2, 1});
|
|
video_parent.type = BindingType::VideoOut;
|
|
video_parent.info.pixel_format = vk::Format::eR8G8B8A8Srgb;
|
|
video_parent.info.guest_format = Prospero::BufferFormat::k8_8_8_8Srgb;
|
|
video_parent.info.resources.levels = 2;
|
|
video_parent.info.mip_layout[0] = {0, target_mip_size, 2, 2};
|
|
video_parent.info.mip_layout[1] = {target_mip_size, target_mip_size, 1,
|
|
1};
|
|
video_parent.view_info.format = video_parent.info.pixel_format;
|
|
video_parent.view_info.usage = vk::ImageUsageFlagBits::eSampled;
|
|
const auto video_parent_id = texture_cache.FindImage(video_parent);
|
|
const auto video_parent_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, video_parent_id);
|
|
Require(name, "video-out replacement remains cache-only",
|
|
video_subresource_owner != nullptr &&
|
|
video_parent_owner != nullptr &&
|
|
video_parent_id != video_subresource_id &&
|
|
!video_subresource_owner->binding.is_bound &&
|
|
!video_subresource_owner->binding.is_target &&
|
|
!video_subresource_owner->binding.needs_rebind &&
|
|
!video_subresource_owner->usage.video_out &&
|
|
!video_parent_owner->binding.is_bound &&
|
|
!video_parent_owner->binding.is_target &&
|
|
!video_parent_owner->binding.needs_rebind &&
|
|
!video_parent_owner->usage.video_out,
|
|
"FindImage leaked caller-owned state across a video-out "
|
|
"replacement");
|
|
|
|
constexpr uint64_t array_target_address = base + 0xd0000;
|
|
auto array_target = make_target_desc(array_target_address,
|
|
target_mip_size, {128, 128, 1});
|
|
const auto array_target_id = texture_cache.FindImage(array_target);
|
|
const auto array_target_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, array_target_id);
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, array_target_id);
|
|
const auto array_target_view =
|
|
texture_cache.FindRenderTarget(array_target_id, array_target);
|
|
|
|
ShaderTextureResource array_texture{};
|
|
const auto encoded_array_address = array_target_address >> 8u;
|
|
constexpr auto array_format =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k32UInt);
|
|
array_texture.fields[0] = static_cast<uint32_t>(encoded_array_address);
|
|
array_texture.fields[1] =
|
|
static_cast<uint32_t>(encoded_array_address >> 32u) |
|
|
(array_format << 20u) | (3u << 30u);
|
|
array_texture.fields[2] = (127u >> 2u) | (127u << 14u);
|
|
array_texture.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(linear) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DArray) << 28u);
|
|
array_texture.fields[4] = 1;
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo array_program{};
|
|
array_program.stage = ShaderType::Compute;
|
|
ShaderRecompiler::IR::ImageResource array_resource{};
|
|
array_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
array_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
array_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
array_resource.read = true;
|
|
array_program.info.images.push_back(array_resource);
|
|
ShaderRecompiler::IR::DescriptorValue array_descriptor{};
|
|
std::copy(std::begin(array_texture.fields),
|
|
std::end(array_texture.fields),
|
|
array_descriptor.dwords.begin());
|
|
array_descriptor.dword_count = 8;
|
|
ShaderRecompiler::IR::ResourceSnapshot array_snapshot{};
|
|
array_snapshot.images.push_back(array_descriptor);
|
|
ShaderStageRuntime array_runtime{&array_program,
|
|
std::move(array_snapshot)};
|
|
|
|
auto array_binding = executor.PrepareBindings(array_runtime);
|
|
executor.RebindImages(array_binding);
|
|
const auto expanded_array_id = array_binding.images[0].image_id;
|
|
const auto &expanded_array = texture_cache.GetImage(expanded_array_id);
|
|
Require(
|
|
name, "2D target to array backing expansion",
|
|
array_target_view != nullptr && array_target_owner != nullptr &&
|
|
array_target_owner->binding.needs_rebind &&
|
|
expanded_array_id != array_target_id &&
|
|
!TextureCacheTestAccess::Contains(texture_cache,
|
|
array_target_id) &&
|
|
array_binding.images[0].image_view != nullptr &&
|
|
array_binding.images[0].desc.info.type ==
|
|
Prospero::ImageType::kColor2D &&
|
|
array_binding.images[0].desc.info.resources ==
|
|
ImageSubresources{1, 2} &&
|
|
array_binding.images[0].desc.view_info.type ==
|
|
vk::ImageViewType::e2DArray &&
|
|
array_binding.images[0].desc.view_info.layer_count ==
|
|
2 &&
|
|
expanded_array.backing.layers == 2 &&
|
|
expanded_array.IsGpuModified() && expanded_array.usage.texture,
|
|
"raw array view did not expand and reuse the Color2D backing");
|
|
|
|
RenderColorInfo rebound_array_target{};
|
|
rebound_array_target.desc = array_target;
|
|
rebound_array_target.image_id = array_target_id;
|
|
RenderDepthInfo no_array_depth{};
|
|
auto array_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &rebound_array_target, 1,
|
|
no_array_depth);
|
|
Require(name, "expanded array target rebind",
|
|
rebound_array_target.image_id == expanded_array_id &&
|
|
array_rendering.color_attachments[0].image_view != nullptr &&
|
|
array_rendering.num_color_attachments == 1 &&
|
|
array_rendering.width == 128 &&
|
|
array_rendering.height == 128 &&
|
|
array_rendering.num_layers == 1 &&
|
|
texture_cache.GetImage(expanded_array_id).binding.is_bound &&
|
|
texture_cache.GetImage(expanded_array_id).binding.is_target,
|
|
"the retained single-layer target did not rebind to the expanded "
|
|
"backing");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
auto colliding_msaa_texture = array_texture;
|
|
colliding_msaa_texture.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (1u << 16u) |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kRenderTarget) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DMsaa) << 28u);
|
|
colliding_msaa_texture.fields[4] = 0;
|
|
colliding_msaa_texture.fields[5] = (7u << 20u) | (1u << 4u);
|
|
ShaderRecompiler::IR::DescriptorValue colliding_msaa_descriptor{};
|
|
std::copy(std::begin(colliding_msaa_texture.fields),
|
|
std::end(colliding_msaa_texture.fields),
|
|
colliding_msaa_descriptor.dwords.begin());
|
|
colliding_msaa_descriptor.dword_count = 8;
|
|
ShaderRecompiler::IR::CompiledShaderInfo colliding_msaa_program{};
|
|
colliding_msaa_program.stage = ShaderType::Compute;
|
|
auto colliding_msaa_resource = array_resource;
|
|
colliding_msaa_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa;
|
|
colliding_msaa_program.info.images.push_back(colliding_msaa_resource);
|
|
ShaderRecompiler::IR::ResourceSnapshot colliding_msaa_snapshot{};
|
|
colliding_msaa_snapshot.images.push_back(colliding_msaa_descriptor);
|
|
ShaderStageRuntime colliding_msaa_runtime{
|
|
&colliding_msaa_program, std::move(colliding_msaa_snapshot)};
|
|
auto colliding_msaa_binding =
|
|
executor.PrepareBindings(colliding_msaa_runtime);
|
|
executor.RebindImages(colliding_msaa_binding);
|
|
const auto &resolved_colliding_msaa =
|
|
colliding_msaa_binding.images[0];
|
|
Require(
|
|
name, "equal-footprint sample-count identity",
|
|
resolved_colliding_msaa.image_id != expanded_array_id &&
|
|
resolved_colliding_msaa.image_view != nullptr &&
|
|
resolved_colliding_msaa.desc.info.type ==
|
|
Prospero::ImageType::kColor2D &&
|
|
resolved_colliding_msaa.desc.info.resources ==
|
|
ImageSubresources{1, 1} &&
|
|
resolved_colliding_msaa.desc.info.samples == 2 &&
|
|
resolved_colliding_msaa.desc.info.data.size == 0x20000 &&
|
|
texture_cache.GetImage(resolved_colliding_msaa.image_id)
|
|
.backing.samples == 2 &&
|
|
texture_cache.GetImage(expanded_array_id).backing.samples == 1 &&
|
|
texture_cache.GetImage(expanded_array_id).backing.layers == 2,
|
|
"equal-size 1x array and 2x image shared an incompatible backing");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
constexpr uint64_t msaa_target_address = base + 0x100000;
|
|
constexpr uint32_t msaa_samples = 4;
|
|
auto msaa_target =
|
|
make_target_desc(msaa_target_address, 0x40000, {128, 128, 1});
|
|
msaa_target.info.samples = msaa_samples;
|
|
msaa_target.info.tile_mode = Prospero::TileMode::kRenderTarget;
|
|
msaa_target.info.mip_layout[0].size = msaa_target.info.data.size;
|
|
const auto msaa_target_id = texture_cache.FindImage(msaa_target);
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, msaa_target_id);
|
|
const auto msaa_target_view =
|
|
texture_cache.FindRenderTarget(msaa_target_id, msaa_target);
|
|
|
|
auto msaa_texture = array_texture;
|
|
const auto encoded_msaa_address = msaa_target_address >> 8u;
|
|
msaa_texture.fields[0] = static_cast<uint32_t>(encoded_msaa_address);
|
|
msaa_texture.fields[1] =
|
|
static_cast<uint32_t>(encoded_msaa_address >> 32u) |
|
|
(array_format << 20u) | (3u << 30u);
|
|
msaa_texture.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (2u << 16u) |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kRenderTarget) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DMsaa) << 28u);
|
|
msaa_texture.fields[4] = 0;
|
|
msaa_texture.fields[5] = (7u << 20u) | (2u << 4u);
|
|
ShaderRecompiler::IR::DescriptorValue msaa_descriptor{};
|
|
std::copy(std::begin(msaa_texture.fields), std::end(msaa_texture.fields),
|
|
msaa_descriptor.dwords.begin());
|
|
msaa_descriptor.dword_count = 8;
|
|
ShaderRecompiler::IR::CompiledShaderInfo msaa_program{};
|
|
msaa_program.stage = ShaderType::Compute;
|
|
auto msaa_resource = array_resource;
|
|
msaa_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa;
|
|
msaa_program.info.images.push_back(msaa_resource);
|
|
ShaderRecompiler::IR::ResourceSnapshot msaa_snapshot{};
|
|
msaa_snapshot.images.push_back(msaa_descriptor);
|
|
ShaderStageRuntime msaa_runtime{&msaa_program, std::move(msaa_snapshot)};
|
|
auto msaa_binding = executor.PrepareBindings(msaa_runtime);
|
|
executor.RebindImages(msaa_binding);
|
|
const auto &resolved_msaa = msaa_binding.images[0];
|
|
Require(
|
|
name, "MSAA descriptor backing reuse",
|
|
msaa_target_view != nullptr &&
|
|
resolved_msaa.image_id == msaa_target_id &&
|
|
resolved_msaa.image_view != nullptr &&
|
|
resolved_msaa.desc.info.type == Prospero::ImageType::kColor2D &&
|
|
resolved_msaa.desc.info.resources == ImageSubresources{1, 1} &&
|
|
resolved_msaa.desc.info.samples == msaa_samples &&
|
|
resolved_msaa.desc.info.data.size == 0x40000 &&
|
|
resolved_msaa.desc.view_info.level_count == 1 &&
|
|
texture_cache.GetImage(msaa_target_id).backing.samples ==
|
|
msaa_samples,
|
|
"MSAA view fields did not resolve to the matching multisample "
|
|
"backing");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
auto msaa_array_texture = msaa_texture;
|
|
msaa_array_texture.fields[3] =
|
|
(msaa_array_texture.fields[3] & ~(0xfu << 28u)) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DMsaaArray)
|
|
<< 28u);
|
|
msaa_array_texture.fields[4] = 1;
|
|
ShaderRecompiler::IR::DescriptorValue msaa_array_descriptor{};
|
|
std::copy(std::begin(msaa_array_texture.fields),
|
|
std::end(msaa_array_texture.fields),
|
|
msaa_array_descriptor.dwords.begin());
|
|
msaa_array_descriptor.dword_count = 8;
|
|
ShaderRecompiler::IR::CompiledShaderInfo msaa_array_program{};
|
|
msaa_array_program.stage = ShaderType::Compute;
|
|
auto msaa_array_resource = array_resource;
|
|
msaa_array_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray;
|
|
msaa_array_program.info.images.push_back(msaa_array_resource);
|
|
ShaderRecompiler::IR::ResourceSnapshot msaa_array_snapshot{};
|
|
msaa_array_snapshot.images.push_back(msaa_array_descriptor);
|
|
ShaderStageRuntime msaa_array_runtime{&msaa_array_program,
|
|
std::move(msaa_array_snapshot)};
|
|
auto msaa_array_binding = executor.PrepareBindings(msaa_array_runtime);
|
|
executor.RebindImages(msaa_array_binding);
|
|
const auto &resolved_msaa_array = msaa_array_binding.images[0];
|
|
Require(name, "MSAA array backing expansion",
|
|
resolved_msaa_array.image_id != msaa_target_id &&
|
|
resolved_msaa_array.image_view != nullptr &&
|
|
resolved_msaa_array.desc.info.type ==
|
|
Prospero::ImageType::kColor2D &&
|
|
resolved_msaa_array.desc.info.resources ==
|
|
ImageSubresources{1, 2} &&
|
|
resolved_msaa_array.desc.info.samples == msaa_samples &&
|
|
resolved_msaa_array.desc.info.data.size == 0x80000 &&
|
|
resolved_msaa_array.desc.view_info.type ==
|
|
vk::ImageViewType::e2DArray &&
|
|
resolved_msaa_array.desc.view_info.layer_count == 2 &&
|
|
texture_cache.GetImage(resolved_msaa_array.image_id)
|
|
.backing.layers == 2 &&
|
|
texture_cache.GetImage(resolved_msaa_array.image_id)
|
|
.backing.samples == msaa_samples,
|
|
"MSAA array view did not expand the matching Color2D backing");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
ImageDesc depth{};
|
|
depth.type = BindingType::DepthTarget;
|
|
depth.info.data = {depth_address, 0x10000};
|
|
depth.info.stencil = {stencil_address, stencil_layout.size};
|
|
depth.info.pixel_format = vk::Format::eD32SfloatS8Uint;
|
|
depth.info.guest_format = Prospero::BufferFormat::k32Float;
|
|
depth.info.type = Prospero::ImageType::kColor2D;
|
|
depth.info.extent = {1, 1, 1};
|
|
depth.info.resources = {1, 1};
|
|
depth.info.pitch = 1;
|
|
depth.info.bytes_per_block = 4;
|
|
depth.info.samples = 1;
|
|
depth.info.tile_mode = linear;
|
|
depth.info.mip_layout[0] = {0, depth.info.data.size, 1, 1};
|
|
depth.view_info.format = depth.info.pixel_format;
|
|
depth.view_info.type = vk::ImageViewType::e2D;
|
|
depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
depth.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
const auto depth_id = texture_cache.FindImage(depth);
|
|
Require(name, "cache-only depth discovery",
|
|
!texture_cache.FindImageFromRange(stencil_address,
|
|
stencil_layout.size, false) &&
|
|
!texture_cache.GetImage(depth_id).binding.is_bound &&
|
|
!texture_cache.GetImage(depth_id).binding.is_target &&
|
|
!texture_cache.GetImage(depth_id).usage.depth_target,
|
|
"FindImage created depth acquisition state or a stencil proxy");
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, depth_id);
|
|
Require(name, "depth prefetch purity",
|
|
texture_cache.GetImage(depth_id).binding.is_target &&
|
|
!texture_cache.GetImage(depth_id).usage.depth_target &&
|
|
!texture_cache.FindImageFromRange(stencil_address,
|
|
stencil_layout.size, false),
|
|
"depth prefetch performed final target acquisition");
|
|
|
|
auto target_parent =
|
|
make_target_desc(base, target_mip_size * 2, {4, 4, 1});
|
|
target_parent.type = BindingType::RenderTarget;
|
|
target_parent.info.resources.levels = 2;
|
|
target_parent.info.mip_layout[0] = {0, target_mip_size, 4, 4};
|
|
target_parent.info.mip_layout[1] = {target_mip_size, target_mip_size, 2,
|
|
2};
|
|
target_parent.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
auto target_base_subresource =
|
|
make_target_desc(base, target_mip_size, {4, 4, 1});
|
|
const auto target_base_subresource_id =
|
|
texture_cache.FindImage(target_base_subresource);
|
|
auto target_subresource =
|
|
make_target_desc(base + target_mip_size, target_mip_size, {2, 2, 1});
|
|
const auto target_subresource_id =
|
|
texture_cache.FindImage(target_subresource);
|
|
const auto *target_subresource_owner =
|
|
TextureCacheTestAccess::Owner(texture_cache, target_subresource_id);
|
|
const auto &discovered_target =
|
|
texture_cache.GetImage(target_subresource_id);
|
|
Require(name, "cache-only target discovery",
|
|
!discovered_target.binding.is_bound &&
|
|
!discovered_target.binding.is_target &&
|
|
!discovered_target.binding.needs_rebind &&
|
|
!discovered_target.usage.render_target &&
|
|
!TextureCacheTestAccess::PendingDownload(
|
|
texture_cache, target_subresource_id),
|
|
"FindImage claimed RenderExecutor-owned render-target state");
|
|
RenderExecutorTestAccess::BindRenderTarget(executor,
|
|
target_subresource_id);
|
|
Require(name, "target prefetch purity",
|
|
texture_cache.GetImage(target_subresource_id).binding.is_target &&
|
|
!texture_cache.GetImage(target_subresource_id)
|
|
.usage.render_target &&
|
|
!TextureCacheTestAccess::PendingDownload(
|
|
texture_cache, target_subresource_id),
|
|
"target prefetch performed final target acquisition");
|
|
const auto target_parent_id = texture_cache.FindImage(target_parent);
|
|
Require(name, "active target overlap",
|
|
target_parent_id &&
|
|
target_parent_id != target_base_subresource_id &&
|
|
target_parent_id != target_subresource_id &&
|
|
target_subresource_owner != nullptr &&
|
|
target_subresource_owner->binding.needs_rebind &&
|
|
texture_cache.GetImage(target_parent_id).binding.is_target &&
|
|
!TextureCacheTestAccess::PendingDownload(texture_cache,
|
|
target_parent_id),
|
|
"target overlap did not transfer target state to the merged "
|
|
"owner");
|
|
RenderColorInfo rebound_color{};
|
|
rebound_color.desc = target_subresource;
|
|
rebound_color.image_id = target_subresource_id;
|
|
RenderDepthInfo rebound_depth{};
|
|
rebound_depth.desc = depth;
|
|
rebound_depth.image_id = depth_id;
|
|
scheduler.Finish();
|
|
Require(name, "deferred target slot erasure",
|
|
TextureCacheTestAccess::Owner(texture_cache,
|
|
target_subresource_id) == nullptr,
|
|
"the displaced target slot was not erased after GPU completion");
|
|
auto rebound_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &rebound_color, 1, rebound_depth);
|
|
Require(name, "guest target extent after host mip remap",
|
|
rebound_color.desc.view_info.base_level == 1 &&
|
|
rebound_color.guest_mip_level == 0 &&
|
|
rebound_color.Extent() == vk::Extent2D{2, 2},
|
|
"remapping a guest target into its parent mip shrank the guest "
|
|
"render area twice");
|
|
const auto stencil_proxy_id = texture_cache.FindImageFromRange(
|
|
stencil_address, stencil_layout.size, false);
|
|
Require(
|
|
name, "production target rebind",
|
|
rebound_color.image_id == target_parent_id &&
|
|
rebound_rendering.color_attachments[0].image_view != nullptr &&
|
|
rebound_rendering.depth_stencil_attachment.image_view != nullptr &&
|
|
rebound_rendering.num_color_attachments == 1 &&
|
|
rebound_rendering.width == 1 && rebound_rendering.height == 1 &&
|
|
texture_cache.GetImage(target_parent_id).binding.is_target &&
|
|
texture_cache.GetImage(target_parent_id).usage.render_target &&
|
|
texture_cache.GetImage(target_parent_id).IsGpuModified() &&
|
|
texture_cache.GetImage(depth_id).usage.depth_target &&
|
|
texture_cache.GetImage(depth_id).IsGpuModified() &&
|
|
TextureCacheTestAccess::PendingDownload(texture_cache,
|
|
target_parent_id) &&
|
|
stencil_proxy_id &&
|
|
texture_cache.GetImage(stencil_proxy_id).depth_id == depth_id,
|
|
"the final target pass did not acquire color before depth and "
|
|
"build the dynamic render state");
|
|
scheduler.BeginRendering(rebound_rendering);
|
|
scheduler.EndRendering();
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
Require(name, "draw-scoped target reset",
|
|
TextureCacheTestAccess::Owner(texture_cache,
|
|
target_subresource_id) == nullptr &&
|
|
!texture_cache.GetImage(target_parent_id).binding.is_target &&
|
|
!texture_cache.GetImage(depth_id).binding.is_target,
|
|
"RenderExecutor retained target binding state after reset");
|
|
|
|
constexpr uint64_t ordered_color_address = base + 0x80000;
|
|
auto ordered_color_desc =
|
|
make_target_desc(ordered_color_address, target_mip_size, {1, 1, 1});
|
|
const auto stale_ordered_color =
|
|
texture_cache.FindImage(ordered_color_desc);
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, stale_ordered_color);
|
|
resources.UnmapMemory(ordered_color_address, target_mip_size);
|
|
resources.MapMemory(ordered_color_address, target_mip_size);
|
|
|
|
auto ordered_depth_desc = depth;
|
|
ordered_depth_desc.info.stencil = {ordered_color_address,
|
|
target_mip_size};
|
|
const auto ordered_depth_id = texture_cache.FindImage(ordered_depth_desc);
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, ordered_depth_id);
|
|
RenderColorInfo ordered_color{};
|
|
ordered_color.desc = ordered_color_desc;
|
|
ordered_color.image_id = stale_ordered_color;
|
|
RenderDepthInfo ordered_depth{};
|
|
ordered_depth.desc = ordered_depth_desc;
|
|
ordered_depth.image_id = ordered_depth_id;
|
|
auto ordered_rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &ordered_color, 1, ordered_depth);
|
|
Require(name, "color-before-depth acquisition order",
|
|
ordered_color.image_id != stale_ordered_color &&
|
|
ordered_rendering.color_attachments[0].image_view != nullptr &&
|
|
ordered_rendering.depth_stencil_attachment.image_view != nullptr &&
|
|
texture_cache.GetImage(ordered_color.image_id).depth_id ==
|
|
ordered_depth.image_id,
|
|
"depth acquisition ran before the stale color target was "
|
|
"recreated and associated");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
TextureCacheTestAccess::SetLinearReadback(texture_cache, false);
|
|
|
|
ShaderTextureResource stencil{};
|
|
const uint64_t encoded_address = stencil_address >> 8u;
|
|
stencil.fields[0] = static_cast<uint32_t>(encoded_address);
|
|
stencil.fields[1] = static_cast<uint32_t>(encoded_address >> 32u) |
|
|
(static_cast<uint32_t>(stencil_format) << 20u);
|
|
stencil.fields[3] =
|
|
DstSel(4, 5, 6, 7) | (static_cast<uint32_t>(linear) << 20u) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
|
|
ShaderRecompiler::IR::CompiledShaderInfo program{};
|
|
program.stage = ShaderType::Compute;
|
|
ShaderRecompiler::IR::ImageResource stencil_resource{};
|
|
stencil_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
stencil_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
stencil_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
stencil_resource.read = true;
|
|
program.info.images.push_back(stencil_resource);
|
|
|
|
ShaderRecompiler::IR::ResourceSnapshot snapshot{};
|
|
ShaderRecompiler::IR::DescriptorValue descriptor{};
|
|
std::copy(std::begin(stencil.fields), std::end(stencil.fields),
|
|
descriptor.dwords.begin());
|
|
descriptor.dword_count = 8;
|
|
snapshot.images.push_back(descriptor);
|
|
ShaderStageRuntime runtime{&program, std::move(snapshot)};
|
|
|
|
auto prepared = context.GetRenderExecutor().PrepareBindings(runtime);
|
|
const auto sampled_stencil_id = prepared.images[0].image_id;
|
|
Require(name, "first stencil discovery",
|
|
prepared.images.size() == 1 &&
|
|
sampled_stencil_id != depth_id &&
|
|
prepared.images[0].image_view == nullptr &&
|
|
texture_cache.GetImage(sampled_stencil_id).binding.is_bound &&
|
|
!texture_cache.GetImage(stencil_proxy_id).binding.is_bound &&
|
|
!texture_cache.GetImage(stencil_proxy_id).binding.is_target,
|
|
"the first sampled-stencil lookup did not remain an ordinary "
|
|
"discovery before final depth acquisition");
|
|
|
|
context.GetRenderExecutor().RebindImages(prepared);
|
|
Require(name, "first stencil acquisition",
|
|
prepared.images[0].image_id == sampled_stencil_id &&
|
|
prepared.images[0].image_view != nullptr &&
|
|
texture_cache.GetImage(sampled_stencil_id).usage.texture,
|
|
"the first sampled-stencil image was not finally acquired");
|
|
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
RenderDepthInfo reassociated_depth = rebound_depth;
|
|
RenderColorInfo no_reassociated_color{};
|
|
(void)RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, scheduler.Current(), &no_reassociated_color, 0,
|
|
reassociated_depth);
|
|
Require(name, "existing stencil association selection",
|
|
texture_cache.GetImage(sampled_stencil_id).depth_id ==
|
|
reassociated_depth.image_id,
|
|
"final depth acquisition did not associate the existing image "
|
|
"at the stencil guest address");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
auto redirected = context.GetRenderExecutor().PrepareBindings(runtime);
|
|
Require(name, "redirected owner discovery",
|
|
redirected.images.size() == 1 &&
|
|
redirected.images[0].image_id == depth_id &&
|
|
redirected.images[0].image_view == nullptr &&
|
|
texture_cache.GetImage(depth_id).binding.is_bound &&
|
|
!texture_cache.GetImage(sampled_stencil_id).binding.is_bound,
|
|
"the established stencil association did not redirect the next "
|
|
"discovery to the depth owner");
|
|
context.GetRenderExecutor().RebindImages(redirected);
|
|
Require(name, "second-pass stencil acquisition",
|
|
redirected.images[0].image_id == depth_id &&
|
|
redirected.images[0].image_view != nullptr &&
|
|
texture_cache.GetImage(depth_id).usage.texture,
|
|
"RebindImages did not acquire the associated depth owner");
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
|
|
// Execute the captured IMAGE_STORE clear through production fill recognition.
|
|
const std::array<uint32_t, 12> stencil_shader{
|
|
0xd7460000u, 0x0401060cu, 0xf4201a84u, 0xfa000000u,
|
|
0xbf8cc07fu, 0x7e06026au, 0xd7460001u, 0x0405060du,
|
|
0x7e04020eu, 0xf0200128u, 0x00000300u, 0xbf810000u};
|
|
uint32_t stencil_byte = 0x35;
|
|
const auto value_address = reinterpret_cast<uint64_t>(&stencil_byte);
|
|
auto storage_stencil = stencil;
|
|
storage_stencil.fields[3] = (storage_stencil.fields[3] & 0x0fffffffu) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2DArray) << 28u);
|
|
storage_stencil.fields[5] = 0x00700000u;
|
|
std::array<uint32_t, 12> stencil_userdata{};
|
|
std::copy_n(storage_stencil.fields, 8, stencil_userdata.begin());
|
|
stencil_userdata[8] = static_cast<uint32_t>(value_address);
|
|
stencil_userdata[9] = static_cast<uint32_t>(value_address >> 32) | (4u << 16);
|
|
stencil_userdata[10] = 1;
|
|
stencil_userdata[11] = 0x14204u;
|
|
ShaderComputeInputInfo stencil_compute{};
|
|
stencil_compute.threads_num[0] = stencil_compute.threads_num[1] = 8;
|
|
stencil_compute.threads_num[2] = 1;
|
|
stencil_compute.workgroup_register = 12;
|
|
stencil_compute.thread_ids_num = 2;
|
|
std::fill_n(stencil_compute.group_id, 3, true);
|
|
ShaderRecompiler::CompileOptions stencil_options;
|
|
stencil_options.stage = ShaderType::Compute;
|
|
stencil_options.user_data = stencil_userdata;
|
|
stencil_options.input_info.compute = &stencil_compute;
|
|
auto stencil_translated = ShaderRecompiler::TranslateProgram(stencil_shader, stencil_options);
|
|
auto stencil_plan = ShaderRecompiler::IR::ExtractResourcePlan(stencil_translated.program);
|
|
ShaderRecompiler::IR::ResourceSpecialization stencil_specialization;
|
|
Require(name, "stencil fill materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(stencil_plan,
|
|
{.user_data = stencil_userdata, .userdata = &stencil_byte,
|
|
.read_specialization_memory = +[](void *data, uint64_t address, uint32_t *word) {
|
|
if (address != reinterpret_cast<uint64_t>(data)) return false;
|
|
*word = *static_cast<uint32_t *>(data);
|
|
return true;
|
|
}}, stencil_compute.stage.resources, stencil_specialization),
|
|
"captured stencil shader could not resolve its clear byte");
|
|
ShaderRecompiler::IR::ApplyResourceSpecialization(stencil_translated.program,
|
|
stencil_specialization);
|
|
ShaderRecompiler::IR::CompiledShaderInfo stencil_program{};
|
|
stencil_program.stage = ShaderType::Compute;
|
|
stencil_program.info = std::move(stencil_translated.program.info);
|
|
stencil_compute.stage.program = &stencil_program;
|
|
vk::ClearValue stencil_initial{};
|
|
stencil_initial.depthStencil = vk::ClearDepthStencilValue{0.625f, 0x91};
|
|
TextureCacheTestAccess::ClearImage(texture_cache, scheduler.Current(), depth_id,
|
|
{vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1},
|
|
stencil_initial);
|
|
Require(name, "stencil fill coverage",
|
|
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
|
scheduler.Current(), 0, 1, 1, 0x41u) &&
|
|
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
|
scheduler.Current(), 2, 1, 1, 0x41u),
|
|
"incomplete or excessive stencil dispatch was consumed");
|
|
auto &stencil_source = stencil_compute.stage.resources.buffers[0];
|
|
const auto clean_source = stencil_source;
|
|
stencil_source.dwords[0] = static_cast<uint32_t>(stencil_address);
|
|
stencil_source.dwords[1] = static_cast<uint32_t>(stencil_address >> 32) | (4u << 16);
|
|
Require(name, "stencil fill source alias",
|
|
!RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
|
scheduler.Current(), 1, 1, 1, 0x41u),
|
|
"an aliased scalar load was consumed as a uniform image clear");
|
|
stencil_source = clean_source;
|
|
Require(name, "stencil IMAGE_STORE clear",
|
|
RenderExecutorTestAccess::TryConsumeComputeImageClear(executor, stencil_compute,
|
|
scheduler.Current(), 1, 1, 1, 0x41u),
|
|
"captured clear attempted an unsupported depth/stencil storage binding");
|
|
auto stencil_readback = CreateHostBuffer(name, 8,
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
const std::array<vk::BufferImageCopy, 2> stencil_copies{{
|
|
{0, 0, 0, {vk::ImageAspectFlagBits::eDepth, 0, 0, 1}, {}, {1, 1, 1}},
|
|
{4, 0, 0, {vk::ImageAspectFlagBits::eStencil, 0, 0, 1}, {}, {1, 1, 1}}}};
|
|
texture_cache.GetImage(depth_id).Download(stencil_copies, stencil_readback.buffer, 0, 8);
|
|
vk::MemoryBarrier2 stencil_host_barrier{};
|
|
stencil_host_barrier.sType = vk::StructureType::eMemoryBarrier2;
|
|
stencil_host_barrier.srcStageMask = vk::PipelineStageFlagBits2::eTransfer;
|
|
stencil_host_barrier.srcAccessMask = vk::AccessFlagBits2::eTransferWrite;
|
|
stencil_host_barrier.dstStageMask = vk::PipelineStageFlagBits2::eHost;
|
|
stencil_host_barrier.dstAccessMask = vk::AccessFlagBits2::eHostRead;
|
|
vk::DependencyInfo stencil_dependency{};
|
|
stencil_dependency.sType = vk::StructureType::eDependencyInfo;
|
|
stencil_dependency.memoryBarrierCount = 1;
|
|
stencil_dependency.pMemoryBarriers = &stencil_host_barrier;
|
|
scheduler.Current().Handle().pipelineBarrier2(stencil_dependency);
|
|
scheduler.Finish();
|
|
const auto stencil_result = ReadBuffer(name, stencil_readback, 2);
|
|
Require(name, "stencil clear retains depth",
|
|
stencil_result[0] == std::bit_cast<uint32_t>(0.625f) &&
|
|
(stencil_result[1] & 0xffu) == 0x35 &&
|
|
!texture_cache.GetImage(depth_id).usage.storage,
|
|
"stencil clear altered depth, lost its scalar value, or used a storage view");
|
|
DestroyBuffer(&stencil_readback);
|
|
resources.UnmapMemory(base, allocation_size);
|
|
scheduler.Finish();
|
|
RenderExecutorTestAccess::DestroyDescriptorPipelines(
|
|
executor, descriptor_pipelines);
|
|
}
|
|
|
|
Require(name, "unmap direct backing",
|
|
Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0,
|
|
"descriptor discovery direct mapping release failed");
|
|
Require(name, "release direct backing",
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"descriptor discovery direct-memory allocation release failed");
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
Buffer CreateStorageBuffer(const char *shader_name,
|
|
const std::vector<u32> &initial,
|
|
size_t dword_count, bool device_address = false) {
|
|
Buffer ret;
|
|
ret.size = static_cast<vk::DeviceSize>(std::max<size_t>(dword_count, 1u) *
|
|
sizeof(u32));
|
|
|
|
vk::BufferCreateInfo buffer_info{};
|
|
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
|
|
buffer_info.size = ret.size;
|
|
buffer_info.usage = vk::BufferUsageFlagBits::eStorageBuffer;
|
|
if (device_address) {
|
|
buffer_info.usage |= vk::BufferUsageFlagBits::eShaderDeviceAddress;
|
|
}
|
|
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createBuffer(&buffer_info, nullptr, &ret.buffer),
|
|
"vkCreateBuffer");
|
|
|
|
vk::MemoryRequirements req{};
|
|
m_device.getBufferMemoryRequirements(ret.buffer, &req);
|
|
u32 memory_type = 0;
|
|
ret.coherent = FindMemoryType(req.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
|
vk::MemoryPropertyFlagBits::eHostCoherent,
|
|
&memory_type);
|
|
if (!ret.coherent) {
|
|
Require(shader_name, "dispatch",
|
|
FindMemoryType(req.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eHostVisible,
|
|
&memory_type),
|
|
"no host-visible memory type for storage buffer");
|
|
}
|
|
|
|
vk::MemoryAllocateInfo alloc{};
|
|
alloc.sType = vk::StructureType::eMemoryAllocateInfo;
|
|
alloc.allocationSize = req.size;
|
|
alloc.memoryTypeIndex = memory_type;
|
|
vk::MemoryAllocateFlagsInfo flags{};
|
|
if (device_address) {
|
|
flags.sType = vk::StructureType::eMemoryAllocateFlagsInfo;
|
|
flags.flags = vk::MemoryAllocateFlagBits::eDeviceAddress;
|
|
alloc.pNext = &flags;
|
|
}
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.allocateMemory(&alloc, nullptr, &ret.memory),
|
|
"vkAllocateMemory");
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.bindBufferMemory(ret.buffer, ret.memory, 0),
|
|
"vkBindBufferMemory");
|
|
if (device_address) {
|
|
vk::BufferDeviceAddressInfo address_info{};
|
|
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
|
|
address_info.buffer = ret.buffer;
|
|
ret.device_address = m_device.getBufferAddress(address_info);
|
|
Require(shader_name, "dispatch", ret.device_address != 0,
|
|
"storage buffer has no device address");
|
|
}
|
|
|
|
std::vector<u32> contents(dword_count, 0);
|
|
for (size_t i = 0; i < initial.size() && i < contents.size(); i++) {
|
|
contents[i] = initial[i];
|
|
}
|
|
WriteBuffer(shader_name, ret, contents);
|
|
return ret;
|
|
}
|
|
|
|
void DestroyBuffer(Buffer *buffer) {
|
|
if (buffer == nullptr) {
|
|
return;
|
|
}
|
|
if (buffer->buffer != nullptr) {
|
|
m_device.destroyBuffer(buffer->buffer, nullptr);
|
|
buffer->buffer = nullptr;
|
|
}
|
|
if (buffer->memory != nullptr) {
|
|
m_device.freeMemory(buffer->memory, nullptr);
|
|
buffer->memory = nullptr;
|
|
}
|
|
}
|
|
|
|
Image CreateImage2D(const char *shader_name, u32 width, u32 height,
|
|
vk::Format format, vk::ImageUsageFlags usage,
|
|
const std::vector<u32> &initial, u32 dwords_per_pixel,
|
|
vk::ImageLayout final_layout) {
|
|
std::vector<std::vector<u32>> mips;
|
|
if (!initial.empty()) {
|
|
mips.push_back(initial);
|
|
}
|
|
return CreateImageMips(shader_name, width, height, format, usage, mips,
|
|
dwords_per_pixel, final_layout, vk::ImageType::e2D,
|
|
vk::ImageViewType::e2D, 1);
|
|
}
|
|
|
|
static u32 MipExtent(u32 value, u32 level) {
|
|
for (u32 i = 0; i < level && value > 1u; i++) {
|
|
value >>= 1u;
|
|
}
|
|
return std::max(value, 1u);
|
|
}
|
|
|
|
static size_t ImageMipDwordCount(u32 width, u32 height, u32 dwords_per_pixel,
|
|
u32 level, u32 layers = 1) {
|
|
return static_cast<size_t>(MipExtent(width, level)) *
|
|
static_cast<size_t>(MipExtent(height, level)) * layers *
|
|
dwords_per_pixel;
|
|
}
|
|
|
|
Image CreateImageMips(const char *shader_name, u32 width, u32 height,
|
|
vk::Format format, vk::ImageUsageFlags usage,
|
|
const std::vector<std::vector<u32>> &initial_mips,
|
|
u32 dwords_per_pixel, vk::ImageLayout final_layout,
|
|
vk::ImageType image_type, vk::ImageViewType view_type,
|
|
u32 layers, u32 view_base_layer = 0,
|
|
u32 view_layers = 0,
|
|
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1) {
|
|
Image ret;
|
|
ret.format = format;
|
|
ret.width = width;
|
|
ret.height = height;
|
|
ret.layers = layers;
|
|
ret.mip_levels = std::max<u32>(static_cast<u32>(initial_mips.size()), 1u);
|
|
ret.dwords_per_pixel = dwords_per_pixel;
|
|
|
|
vk::ImageCreateInfo image_info{};
|
|
image_info.sType = vk::StructureType::eImageCreateInfo;
|
|
image_info.imageType = image_type;
|
|
image_info.format = format;
|
|
image_info.extent.width = width;
|
|
image_info.extent.height = height;
|
|
image_info.extent.depth = 1;
|
|
image_info.mipLevels = ret.mip_levels;
|
|
image_info.arrayLayers = layers;
|
|
image_info.samples = samples;
|
|
image_info.tiling = vk::ImageTiling::eOptimal;
|
|
image_info.usage = usage | vk::ImageUsageFlagBits::eTransferDst |
|
|
vk::ImageUsageFlagBits::eTransferSrc;
|
|
image_info.sharingMode = vk::SharingMode::eExclusive;
|
|
image_info.initialLayout = vk::ImageLayout::eUndefined;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createImage(&image_info, nullptr, &ret.image),
|
|
"vkCreateImage");
|
|
|
|
vk::MemoryRequirements req{};
|
|
m_device.getImageMemoryRequirements(ret.image, &req);
|
|
u32 memory_type = 0;
|
|
if (!FindMemoryType(req.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal,
|
|
&memory_type)) {
|
|
Require(shader_name, "dispatch",
|
|
FindMemoryType(req.memoryTypeBits, {}, &memory_type),
|
|
"no memory type for image");
|
|
}
|
|
|
|
vk::MemoryAllocateInfo alloc{};
|
|
alloc.sType = vk::StructureType::eMemoryAllocateInfo;
|
|
alloc.allocationSize = req.size;
|
|
alloc.memoryTypeIndex = memory_type;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.allocateMemory(&alloc, nullptr, &ret.memory),
|
|
"vk::Device::allocateMemory(image)");
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.bindImageMemory(ret.image, ret.memory, 0),
|
|
"vkBindImageMemory");
|
|
|
|
vk::ImageViewCreateInfo view_info{};
|
|
view_info.sType = vk::StructureType::eImageViewCreateInfo;
|
|
view_info.image = ret.image;
|
|
view_info.viewType = view_type;
|
|
view_info.format = format;
|
|
view_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
view_info.subresourceRange.baseMipLevel = 0;
|
|
view_info.subresourceRange.levelCount = ret.mip_levels;
|
|
Require(shader_name, "dispatch", view_base_layer < layers,
|
|
"image view base layer is out of bounds");
|
|
if (view_layers == 0) {
|
|
view_layers = layers - view_base_layer;
|
|
}
|
|
Require(shader_name, "dispatch", view_layers <= layers - view_base_layer,
|
|
"image view layer count is out of bounds");
|
|
view_info.subresourceRange.baseArrayLayer = view_base_layer;
|
|
view_info.subresourceRange.layerCount = view_layers;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createImageView(&view_info, nullptr, &ret.view),
|
|
"vkCreateImageView");
|
|
|
|
if (!initial_mips.empty()) {
|
|
size_t total_dwords = 0;
|
|
for (u32 level = 0; level < ret.mip_levels; level++) {
|
|
total_dwords +=
|
|
ImageMipDwordCount(width, height, dwords_per_pixel, level, layers);
|
|
}
|
|
std::vector<u32> contents(total_dwords, 0);
|
|
size_t offset = 0;
|
|
for (u32 level = 0; level < ret.mip_levels; level++) {
|
|
const auto level_dwords =
|
|
ImageMipDwordCount(width, height, dwords_per_pixel, level, layers);
|
|
const auto &src = initial_mips[level];
|
|
for (size_t i = 0; i < src.size() && i < level_dwords; i++) {
|
|
contents[offset + i] = src[i];
|
|
}
|
|
offset += level_dwords;
|
|
}
|
|
auto staging =
|
|
CreateHostBuffer(shader_name, contents.size() * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eTransferSrc, contents);
|
|
UploadImageMips(shader_name, &ret, staging.buffer, final_layout);
|
|
DestroyBuffer(&staging);
|
|
} else {
|
|
TransitionImage(shader_name, &ret, final_layout,
|
|
vk::PipelineStageFlagBits::eTopOfPipe,
|
|
vk::PipelineStageFlagBits::eComputeShader, {},
|
|
AccessForLayout(final_layout));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void DestroyImage(Image *image) {
|
|
if (image == nullptr) {
|
|
return;
|
|
}
|
|
if (image->view != nullptr) {
|
|
m_device.destroyImageView(image->view, nullptr);
|
|
image->view = nullptr;
|
|
}
|
|
if (image->image != nullptr) {
|
|
m_device.destroyImage(image->image, nullptr);
|
|
image->image = nullptr;
|
|
}
|
|
if (image->memory != nullptr) {
|
|
m_device.freeMemory(image->memory, nullptr);
|
|
image->memory = nullptr;
|
|
}
|
|
}
|
|
|
|
std::vector<u32> ReadImage(const char *shader_name, Image *image) {
|
|
const auto dword_count = static_cast<size_t>(image->width) *
|
|
static_cast<size_t>(image->height) *
|
|
image->layers * image->dwords_per_pixel;
|
|
auto staging = CreateHostBuffer(shader_name, dword_count * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
|
|
|
vk::CommandBuffer cmd = BeginCommands(shader_name, "readback");
|
|
AddImageBarrier(
|
|
cmd, image->image, image->layout, vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::PipelineStageFlagBits::eAllCommands,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
|
vk::AccessFlagBits::eTransferRead, image->mip_levels, image->layers);
|
|
vk::BufferImageCopy copy{};
|
|
copy.bufferOffset = 0;
|
|
copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
copy.imageSubresource.mipLevel = 0;
|
|
copy.imageSubresource.baseArrayLayer = 0;
|
|
copy.imageSubresource.layerCount = image->layers;
|
|
copy.imageExtent.width = image->width;
|
|
copy.imageExtent.height = image->height;
|
|
copy.imageExtent.depth = 1;
|
|
cmd.copyImageToBuffer(image->image, vk::ImageLayout::eTransferSrcOptimal,
|
|
staging.buffer, 1, ©);
|
|
EndSubmitAndFree(shader_name, "readback", cmd);
|
|
image->layout = vk::ImageLayout::eTransferSrcOptimal;
|
|
|
|
auto ret = ReadBuffer(shader_name, staging, dword_count);
|
|
DestroyBuffer(&staging);
|
|
return ret;
|
|
}
|
|
|
|
vk::Sampler CreateNearestSampler(const char *shader_name) {
|
|
vk::SamplerCreateInfo sampler_info{};
|
|
sampler_info.sType = vk::StructureType::eSamplerCreateInfo;
|
|
sampler_info.magFilter = vk::Filter::eNearest;
|
|
sampler_info.minFilter = vk::Filter::eNearest;
|
|
sampler_info.mipmapMode = vk::SamplerMipmapMode::eNearest;
|
|
sampler_info.addressModeU = vk::SamplerAddressMode::eClampToEdge;
|
|
sampler_info.addressModeV = vk::SamplerAddressMode::eClampToEdge;
|
|
sampler_info.addressModeW = vk::SamplerAddressMode::eClampToEdge;
|
|
sampler_info.minLod = 0.0f;
|
|
sampler_info.maxLod = 0.0f;
|
|
vk::Sampler sampler = nullptr;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createSampler(&sampler_info, nullptr, &sampler),
|
|
"vkCreateSampler");
|
|
return sampler;
|
|
}
|
|
|
|
std::vector<u32> ReadBuffer(const char *shader_name, const Buffer &buffer,
|
|
size_t dword_count) {
|
|
if (!buffer.coherent) {
|
|
vk::MappedMemoryRange range{};
|
|
range.sType = vk::StructureType::eMappedMemoryRange;
|
|
range.memory = buffer.memory;
|
|
range.offset = 0;
|
|
range.size = VK_WHOLE_SIZE;
|
|
RequireVk(shader_name, "readback",
|
|
m_device.invalidateMappedMemoryRanges(1, &range),
|
|
"vkInvalidateMappedMemoryRanges");
|
|
}
|
|
|
|
void *data = nullptr;
|
|
RequireVk(shader_name, "readback",
|
|
m_device.mapMemory(buffer.memory, 0, buffer.size, {}, &data),
|
|
"vkMapMemory");
|
|
std::vector<u32> ret(dword_count, 0);
|
|
std::memcpy(ret.data(), data, dword_count * sizeof(u32));
|
|
m_device.unmapMemory(buffer.memory);
|
|
return ret;
|
|
}
|
|
|
|
void Dispatch(const TestCase &test, const CompiledShader &compiled,
|
|
const Buffer &buffer, const Buffer *gds_buffer = nullptr,
|
|
const Image *sampled_image = nullptr,
|
|
const Image *storage_image = nullptr,
|
|
const Image *storage_image_uint = nullptr,
|
|
vk::Sampler sampler = nullptr) {
|
|
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
|
|
const auto &layout = compiled.program.bindings;
|
|
auto Binding = [&](Kind kind) {
|
|
return ShaderRecompiler::IR::FindBinding(layout, kind);
|
|
};
|
|
auto Native = [](Kind kind) {
|
|
return ShaderRecompiler::IR::NativeBinding(ShaderType::Compute, kind);
|
|
};
|
|
for (const auto &binding : layout.descriptors) {
|
|
const auto resource_class =
|
|
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
|
|
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) {
|
|
continue;
|
|
}
|
|
const auto &image =
|
|
compiled.program.info.images.at(binding.resources.front());
|
|
bool supported =
|
|
image.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
|
supported = supported ||
|
|
image.dimension ==
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
|
image.dimension ==
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
|
image.dimension ==
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
}
|
|
Require(test.name, "dispatch", supported,
|
|
"unsupported image dimension needs a matching Vulkan test view");
|
|
}
|
|
|
|
vk::ShaderModuleCreateInfo module_info{};
|
|
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
|
module_info.codeSize = compiled.spirv.size() * sizeof(u32);
|
|
module_info.pCode = compiled.spirv.data();
|
|
vk::ShaderModule module = nullptr;
|
|
RequireVk(test.name, "SPIR-V module",
|
|
m_device.createShaderModule(&module_info, nullptr, &module),
|
|
"vkCreateShaderModule");
|
|
|
|
std::vector<vk::DescriptorSetLayoutBinding> layout_bindings;
|
|
auto add_layout_binding =
|
|
[&layout_bindings](u32 binding, vk::DescriptorType type, u32 count) {
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
vk::DescriptorSetLayoutBinding item{};
|
|
item.binding = binding;
|
|
item.descriptorType = type;
|
|
item.descriptorCount = count;
|
|
item.stageFlags = vk::ShaderStageFlagBits::eCompute;
|
|
layout_bindings.push_back(item);
|
|
};
|
|
for (const auto &binding : layout.descriptors) {
|
|
add_layout_binding(ShaderRecompiler::IR::NativeBinding(
|
|
ShaderType::Compute, binding.kind),
|
|
NativeDescriptorType(binding.kind),
|
|
NativeDescriptorCount(binding));
|
|
}
|
|
|
|
vk::DescriptorSetLayoutCreateInfo layout_info{};
|
|
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
|
layout_info.bindingCount = static_cast<u32>(layout_bindings.size());
|
|
layout_info.pBindings =
|
|
layout_bindings.empty() ? nullptr : layout_bindings.data();
|
|
vk::DescriptorSetLayout descriptor_layout = nullptr;
|
|
RequireVk(test.name, "dispatch",
|
|
m_device.createDescriptorSetLayout(&layout_info, nullptr,
|
|
&descriptor_layout),
|
|
"vkCreateDescriptorSetLayout");
|
|
|
|
vk::PipelineLayoutCreateInfo pipeline_layout_info{};
|
|
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
|
pipeline_layout_info.setLayoutCount = 1;
|
|
pipeline_layout_info.pSetLayouts = &descriptor_layout;
|
|
vk::PushConstantRange push_range{};
|
|
if (layout.UsesPushData()) {
|
|
push_range.stageFlags = vk::ShaderStageFlagBits::eCompute;
|
|
push_range.offset = 0;
|
|
push_range.size = ShaderRecompiler::IR::NativePushConstantSize;
|
|
pipeline_layout_info.pushConstantRangeCount = 1;
|
|
pipeline_layout_info.pPushConstantRanges = &push_range;
|
|
}
|
|
vk::PipelineLayout pipeline_layout = nullptr;
|
|
RequireVk(test.name, "dispatch",
|
|
m_device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
|
&pipeline_layout),
|
|
"vkCreatePipelineLayout");
|
|
|
|
vk::PipelineShaderStageCreateInfo stage{};
|
|
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
|
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
|
stage.module = module;
|
|
stage.pName = "main";
|
|
|
|
vk::ComputePipelineCreateInfo pipeline_info{};
|
|
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
|
|
pipeline_info.stage = stage;
|
|
pipeline_info.layout = pipeline_layout;
|
|
vk::Pipeline pipeline = nullptr;
|
|
RequireVk(test.name, "dispatch",
|
|
m_device.createComputePipelines(nullptr, 1, &pipeline_info,
|
|
nullptr, &pipeline),
|
|
"vkCreateComputePipelines");
|
|
|
|
std::vector<vk::DescriptorPoolSize> pool_sizes;
|
|
auto add_pool_size = [&pool_sizes](vk::DescriptorType type, u32 count) {
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
for (auto &size : pool_sizes) {
|
|
if (size.type == type) {
|
|
size.descriptorCount += count;
|
|
return;
|
|
}
|
|
}
|
|
pool_sizes.push_back({type, count});
|
|
};
|
|
for (const auto &binding : layout.descriptors) {
|
|
add_pool_size(NativeDescriptorType(binding.kind),
|
|
NativeDescriptorCount(binding));
|
|
}
|
|
vk::DescriptorPoolCreateInfo pool_info{};
|
|
pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
|
pool_info.maxSets = 1;
|
|
pool_info.poolSizeCount = static_cast<u32>(pool_sizes.size());
|
|
pool_info.pPoolSizes = pool_sizes.empty() ? nullptr : pool_sizes.data();
|
|
vk::DescriptorPool descriptor_pool = nullptr;
|
|
RequireVk(
|
|
test.name, "dispatch",
|
|
m_device.createDescriptorPool(&pool_info, nullptr, &descriptor_pool),
|
|
"vkCreateDescriptorPool");
|
|
|
|
vk::DescriptorSetAllocateInfo set_info{};
|
|
set_info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
|
set_info.descriptorPool = descriptor_pool;
|
|
set_info.descriptorSetCount = 1;
|
|
set_info.pSetLayouts = &descriptor_layout;
|
|
vk::DescriptorSet descriptor_set = nullptr;
|
|
RequireVk(test.name, "dispatch",
|
|
m_device.allocateDescriptorSets(&set_info, &descriptor_set),
|
|
"vkAllocateDescriptorSets");
|
|
|
|
std::vector<vk::WriteDescriptorSet> writes;
|
|
std::vector<vk::DescriptorBufferInfo> buffer_infos;
|
|
std::vector<vk::DescriptorImageInfo> sampled_infos;
|
|
std::vector<vk::DescriptorImageInfo> storage_infos;
|
|
std::vector<vk::DescriptorImageInfo> storage_uint_infos;
|
|
std::vector<vk::DescriptorImageInfo> storage_atomic_infos;
|
|
std::vector<vk::DescriptorImageInfo> sampler_infos;
|
|
Buffer flattened_buffer;
|
|
Buffer user_data_buffer;
|
|
vk::DescriptorBufferInfo flattened_info{};
|
|
vk::DescriptorBufferInfo user_data_info{};
|
|
vk::DescriptorBufferInfo gds_info{};
|
|
vk::DescriptorBufferInfo bda_pagetable_info{};
|
|
vk::DescriptorBufferInfo fault_buffer_info{};
|
|
|
|
const bool uses_bda = Binding(Kind::BdaPagetable) != nullptr;
|
|
Require(test.name, "dispatch",
|
|
uses_bda == (Binding(Kind::FaultBuffer) != nullptr),
|
|
"BDA page table and fault buffer must be bound together");
|
|
if (uses_bda) {
|
|
EnsureBdaBuffers(test.name);
|
|
bda_pagetable_info = {m_bda_pagetable_buffer.buffer, 0,
|
|
m_bda_pagetable_buffer.size};
|
|
fault_buffer_info = {m_fault_buffer.buffer, 0, m_fault_buffer.size};
|
|
const std::array special_buffers{
|
|
std::pair{Kind::BdaPagetable, &bda_pagetable_info},
|
|
std::pair{Kind::FaultBuffer, &fault_buffer_info},
|
|
};
|
|
for (const auto &[kind, info] : special_buffers) {
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(kind);
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
write.pBufferInfo = info;
|
|
writes.push_back(write);
|
|
}
|
|
}
|
|
|
|
const auto *buffers = Binding(Kind::Buffers);
|
|
if (buffers != nullptr) {
|
|
buffer_infos.resize(buffers->resources.size());
|
|
for (u32 i = 0; i < buffer_infos.size(); i++) {
|
|
auto &info = buffer_infos[i];
|
|
info.buffer = buffer.buffer;
|
|
info.offset = 0;
|
|
info.range = buffer.size;
|
|
if (test.storage_buffer_range_dwords != 0) {
|
|
const auto offset = i < test.storage_buffer_offsets.size()
|
|
? test.storage_buffer_offsets[i]
|
|
: 0u;
|
|
info.range = static_cast<vk::DeviceSize>(
|
|
test.storage_buffer_range_dwords * sizeof(u32) + offset);
|
|
Require(test.name, "dispatch", info.range <= buffer.size,
|
|
"storage buffer descriptor range exceeds backing buffer");
|
|
}
|
|
}
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(Kind::Buffers);
|
|
write.descriptorCount = static_cast<u32>(buffer_infos.size());
|
|
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
write.pBufferInfo = buffer_infos.data();
|
|
writes.push_back(write);
|
|
}
|
|
if (const auto *flattened = Binding(Kind::FlattenedSrt);
|
|
flattened != nullptr) {
|
|
flattened_buffer =
|
|
CreateStorageBuffer(test.name, compiled.resources.flattened_srt,
|
|
compiled.resources.flattened_srt.size());
|
|
flattened_info = {flattened_buffer.buffer, 0, flattened_buffer.size};
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(Kind::FlattenedSrt);
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
write.pBufferInfo = &flattened_info;
|
|
writes.push_back(write);
|
|
}
|
|
if (const auto *user = Binding(Kind::ShaderData); user != nullptr) {
|
|
user_data_buffer =
|
|
CreateStorageBuffer(test.name, compiled.packed_user_data,
|
|
compiled.packed_user_data.size());
|
|
user_data_info = {user_data_buffer.buffer, 0, user_data_buffer.size};
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(Kind::ShaderData);
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
write.pBufferInfo = &user_data_info;
|
|
writes.push_back(write);
|
|
}
|
|
if (const auto *gds = Binding(Kind::Gds); gds != nullptr) {
|
|
Require(test.name, "dispatch", gds_buffer != nullptr,
|
|
"GDS descriptor requested but no GDS buffer was provided");
|
|
gds_info = {gds_buffer->buffer, 0, gds_buffer->size};
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(Kind::Gds);
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
|
write.pBufferInfo = &gds_info;
|
|
writes.push_back(write);
|
|
}
|
|
const ShaderRecompiler::IR::DescriptorBinding *sampled = nullptr;
|
|
const ShaderRecompiler::IR::DescriptorBinding *storage = nullptr;
|
|
const ShaderRecompiler::IR::DescriptorBinding *storage_uint = nullptr;
|
|
const ShaderRecompiler::IR::DescriptorBinding *storage_atomic = nullptr;
|
|
for (const auto &binding : layout.descriptors) {
|
|
const auto resource_class =
|
|
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
|
|
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::None) {
|
|
continue;
|
|
}
|
|
const auto &image =
|
|
compiled.program.info.images.at(binding.resources.front());
|
|
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
|
Require(test.name, "dispatch", sampled == nullptr,
|
|
"Vulkan test harness needs separate sampled images for mixed "
|
|
"descriptor classes");
|
|
sampled = &binding;
|
|
} else if (image.atomic) {
|
|
storage_atomic = &binding;
|
|
} else if (image.numeric_class == Prospero::TextureNumericClass::Float) {
|
|
storage = &binding;
|
|
} else {
|
|
storage_uint = &binding;
|
|
}
|
|
}
|
|
if (sampled != nullptr) {
|
|
Require(test.name, "dispatch", sampled_image != nullptr,
|
|
"sampled image descriptor requested but no sampled image was "
|
|
"provided");
|
|
sampled_infos.resize(sampled->resources.size());
|
|
for (auto &info : sampled_infos) {
|
|
info.imageView = sampled_image->view;
|
|
info.imageLayout = sampled_image->layout;
|
|
}
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(sampled->kind);
|
|
write.descriptorCount = static_cast<u32>(sampled_infos.size());
|
|
write.descriptorType = vk::DescriptorType::eSampledImage;
|
|
write.pImageInfo = sampled_infos.data();
|
|
writes.push_back(write);
|
|
}
|
|
const auto BindStorage =
|
|
[&](const ShaderRecompiler::IR::DescriptorBinding *binding,
|
|
const Image *image, std::vector<vk::DescriptorImageInfo> *infos) {
|
|
if (binding == nullptr) {
|
|
return;
|
|
}
|
|
Require(
|
|
test.name, "dispatch", image != nullptr,
|
|
"storage image descriptor requested but no matching image was "
|
|
"provided");
|
|
infos->resize(binding->resources.size());
|
|
for (auto &info : *infos) {
|
|
info.imageView = image->view;
|
|
info.imageLayout = image->layout;
|
|
}
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(binding->kind);
|
|
write.descriptorCount = static_cast<u32>(infos->size());
|
|
write.descriptorType = vk::DescriptorType::eStorageImage;
|
|
write.pImageInfo = infos->data();
|
|
writes.push_back(write);
|
|
};
|
|
BindStorage(storage, storage_image, &storage_infos);
|
|
BindStorage(storage_uint, storage_image_uint, &storage_uint_infos);
|
|
BindStorage(storage_atomic, storage_image_uint, &storage_atomic_infos);
|
|
const auto *samplers = Binding(Kind::Samplers);
|
|
if (samplers != nullptr) {
|
|
Require(test.name, "dispatch", sampler != nullptr,
|
|
"sampler descriptor requested but no sampler was provided");
|
|
sampler_infos.resize(samplers->resources.size());
|
|
for (auto &info : sampler_infos) {
|
|
info.sampler = sampler;
|
|
}
|
|
vk::WriteDescriptorSet write{};
|
|
write.sType = vk::StructureType::eWriteDescriptorSet;
|
|
write.dstSet = descriptor_set;
|
|
write.dstBinding = Native(Kind::Samplers);
|
|
write.descriptorCount = static_cast<u32>(sampler_infos.size());
|
|
write.descriptorType = vk::DescriptorType::eSampler;
|
|
write.pImageInfo = sampler_infos.data();
|
|
writes.push_back(write);
|
|
}
|
|
if (!writes.empty()) {
|
|
m_device.updateDescriptorSets(static_cast<u32>(writes.size()),
|
|
writes.data(), 0, nullptr);
|
|
}
|
|
|
|
vk::CommandBuffer cmd = BeginCommands(test.name, "dispatch");
|
|
if (uses_bda) {
|
|
Require(test.name, "dispatch", buffer.device_address != 0,
|
|
"BDA test backing has no device address");
|
|
Require(test.name, "dispatch", !test.bda_mappings.empty(),
|
|
"BDA test has no explicit guest mapping");
|
|
cmd.fillBuffer(m_bda_pagetable_buffer.buffer, 0,
|
|
m_bda_pagetable_buffer.size, 0);
|
|
cmd.fillBuffer(m_fault_buffer.buffer, 0, m_fault_buffer.size, 0);
|
|
std::array<vk::BufferMemoryBarrier, 2> barriers{};
|
|
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barriers[0].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barriers[0].dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barriers[0].buffer = m_bda_pagetable_buffer.buffer;
|
|
barriers[0].offset = 0;
|
|
barriers[0].size = m_bda_pagetable_buffer.size;
|
|
barriers[1] = barriers[0];
|
|
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead |
|
|
vk::AccessFlagBits::eShaderWrite;
|
|
barriers[1].buffer = m_fault_buffer.buffer;
|
|
barriers[1].size = m_fault_buffer.size;
|
|
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr,
|
|
1, barriers.data(), 0, nullptr);
|
|
for (const auto &[guest_base, backing] : test.bda_mappings) {
|
|
const auto page_offset = guest_base &
|
|
(BufferCache::CACHING_PAGESIZE - 1);
|
|
Require(test.name, "dispatch",
|
|
backing >= page_offset && backing < buffer.size,
|
|
"BDA test mapping begins outside its backing buffer");
|
|
const auto bytes = page_offset + buffer.size - backing;
|
|
const auto pages =
|
|
(bytes + BufferCache::CACHING_PAGESIZE - 1) /
|
|
BufferCache::CACHING_PAGESIZE;
|
|
auto address = buffer.device_address + backing - page_offset;
|
|
auto page = guest_base >> BufferCache::CACHING_PAGEBITS;
|
|
for (uint64_t mapped = 0; mapped < pages; mapped++) {
|
|
cmd.updateBuffer(m_bda_pagetable_buffer.buffer,
|
|
(page + mapped) * sizeof(vk::DeviceAddress),
|
|
sizeof(address), &address);
|
|
address += BufferCache::CACHING_PAGESIZE;
|
|
}
|
|
}
|
|
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eComputeShader, {}, 0,
|
|
nullptr, static_cast<u32>(barriers.size()),
|
|
barriers.data(), 0, nullptr);
|
|
}
|
|
cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline);
|
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipeline_layout, 0,
|
|
1, &descriptor_set, 0, nullptr);
|
|
if (layout.UsesPushData()) {
|
|
ShaderRecompiler::IR::PushData push_data;
|
|
std::copy(compiled.packed_user_data.begin(),
|
|
compiled.packed_user_data.end(),
|
|
push_data.dwords.begin() + layout.push_data_start_dword);
|
|
cmd.pushConstants(pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
|
|
sizeof(push_data), push_data.dwords.data());
|
|
}
|
|
cmd.dispatch(test.dispatch_x, test.dispatch_y, test.dispatch_z);
|
|
|
|
if (buffers != nullptr) {
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask =
|
|
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = buffer.buffer;
|
|
barrier.offset = 0;
|
|
barrier.size = buffer.size;
|
|
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1,
|
|
&barrier, 0, nullptr);
|
|
}
|
|
if (gds_buffer != nullptr) {
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask =
|
|
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = gds_buffer->buffer;
|
|
barrier.offset = 0;
|
|
barrier.size = gds_buffer->size;
|
|
cmd.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1,
|
|
&barrier, 0, nullptr);
|
|
}
|
|
EndSubmitAndFree(test.name, "dispatch", cmd);
|
|
if (flattened_buffer.buffer != nullptr) {
|
|
DestroyBuffer(&flattened_buffer);
|
|
}
|
|
if (user_data_buffer.buffer != nullptr) {
|
|
DestroyBuffer(&user_data_buffer);
|
|
}
|
|
m_device.destroyDescriptorPool(descriptor_pool, nullptr);
|
|
m_device.destroyPipeline(pipeline, nullptr);
|
|
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
|
|
m_device.destroyDescriptorSetLayout(descriptor_layout, nullptr);
|
|
m_device.destroyShaderModule(module, nullptr);
|
|
}
|
|
|
|
void CheckRasterization(bool depth_feedback, bool packed_vertex_color = false) {
|
|
const char *name = packed_vertex_color ? "PackedFloatVertexColor"
|
|
: depth_feedback ? "DepthAttachmentFeedback"
|
|
: "PolygonModeRasterization";
|
|
const uint32_t extent = depth_feedback ? 8 : 32;
|
|
constexpr uintptr_t depth_address = 0x0000000204400000ull;
|
|
constexpr uint64_t allocation_size = 0x20000;
|
|
EnsureRuntimeContext();
|
|
RenderContext context(m_runtime_context);
|
|
auto &scheduler = context.GetCommandScheduler();
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
registers.SetRenderTargetMask(0xf);
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
auto &resources = context.GetGpuResources();
|
|
auto &cache = context.GetTextureCache();
|
|
auto &executor = context.GetRenderExecutor();
|
|
int64_t direct_offset = -1;
|
|
RenderDepthInfo depth{};
|
|
Require(name, "direct allocation",
|
|
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
|
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(),
|
|
allocation_size, allocation_size, 0, &direct_offset) == 0,
|
|
"depth allocation failed");
|
|
void *mapped = reinterpret_cast<void *>(depth_address);
|
|
Require(name, "direct mapping",
|
|
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
|
&mapped, allocation_size, 0x3, 0x10, direct_offset,
|
|
allocation_size) == 0 &&
|
|
mapped == reinterpret_cast<void *>(depth_address),
|
|
"depth mapping failed");
|
|
std::memset(mapped, 0, allocation_size);
|
|
const auto initial_depth = [](uint32_t x, uint32_t y) {
|
|
return 0.125f + static_cast<float>(x + 2 * y) / 128.0f;
|
|
};
|
|
for (uint32_t y = 0; y < extent; y++) {
|
|
for (uint32_t x = 0; x < extent; x++) {
|
|
static_cast<float *>(mapped)[y * 64 + x] = initial_depth(x, y);
|
|
}
|
|
}
|
|
resources.MapMemory(depth_address, allocation_size);
|
|
if (depth_feedback) {
|
|
depth.desc.type = BindingType::DepthTarget;
|
|
depth.desc.info.data = {depth_address, 256 * extent};
|
|
depth.desc.info.pixel_format = vk::Format::eD32Sfloat;
|
|
depth.desc.info.guest_format = Prospero::BufferFormat::k32Float;
|
|
depth.desc.info.extent = {extent, extent, 1};
|
|
depth.desc.info.pitch = 64;
|
|
depth.desc.info.bytes_per_block = 4;
|
|
depth.desc.info.tile_mode = Prospero::TileMode::kLinear;
|
|
depth.desc.info.mip_layout[0] = {0, 256 * extent, 64, extent};
|
|
depth.desc.view_info.format = depth.desc.info.pixel_format;
|
|
depth.desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
|
|
depth.desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
|
depth.depth_test_enable = depth.depth_write_enable = true;
|
|
depth.depth_compare_op = vk::CompareOp::eAlways;
|
|
depth.image_id = cache.FindImage(depth.desc);
|
|
}
|
|
|
|
GraphicsCase test;
|
|
test.name = name;
|
|
test.pixel_depth_export = depth_feedback;
|
|
if (depth_feedback) {
|
|
const ShaderTextureResource descriptor{{
|
|
static_cast<uint32_t>(depth_address >> 8u),
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32Float) << 20u) |
|
|
(((extent - 1u) & 3u) << 30u),
|
|
((extent - 1u) >> 2u) | ((extent - 1u) << 14u),
|
|
DstSel(4, 4, 4, 4) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u),
|
|
0, 0, 0, 0}};
|
|
test.has_user_data = true;
|
|
std::copy_n(descriptor.fields, 8, test.user_data.begin());
|
|
// Each fragment samples its own texel through interpolated screen UVs.
|
|
for (uint32_t component = 0; component < 2; component++) {
|
|
test.fragment_code.push_back(EncodeVintrp(0, 20 + component, 0, component, 0));
|
|
test.fragment_code.push_back(EncodeVintrp(1, 20 + component, 0, component, 1));
|
|
}
|
|
test.fragment_code.push_back(EncodeMimg0(0x27, 1));
|
|
test.fragment_code.push_back(EncodeMimg1(0, 20, 0, 2));
|
|
AppendVMovLiteral(&test.fragment_code, 1, 0x3e000000u);
|
|
test.fragment_code.push_back(EncodeVop2(0x03, 1, Vgpr(0), 1));
|
|
test.fragment_code.push_back(EncodeExp0(0x08, 0x1, false));
|
|
test.fragment_code.push_back(EncodeExp1(1, 1, 1, 1));
|
|
} else {
|
|
test.pixel_interpolator_settings = {0x400u};
|
|
for (uint32_t component = 0; component < (packed_vertex_color ? 4u : 1u); component++) {
|
|
test.fragment_code.push_back(EncodeVintrp(0x02, component, 0, component, 2));
|
|
}
|
|
}
|
|
test.fragment_code.push_back(EncodeExp0(0x00, 0xf));
|
|
test.fragment_code.push_back(packed_vertex_color ? EncodeExp1(0, 1, 2, 3)
|
|
: EncodeExp1(0, 0, 0, 0));
|
|
AppendEnd(&test.fragment_code);
|
|
auto fragment = CompileFragmentCase(test);
|
|
const auto vertex_spirv = TestSpv::MakePassthroughVertexSpirv(false);
|
|
const ShaderProgram vertex_shader{1, CreateShaderModule(name, vertex_spirv)};
|
|
const ShaderProgram pixel_shader{2, CreateShaderModule(name, fragment.spirv)};
|
|
ShaderRecompiler::IR::CompiledShaderInfo vertex_program{};
|
|
vertex_program.stage = ShaderType::Vertex;
|
|
vertex_program.info.vertex_fetch_components[0] = 2;
|
|
vertex_program.info.vertex_fetch_components[1] = packed_vertex_color ? 3 : 4;
|
|
ShaderRecompiler::IR::CompiledShaderInfo pixel_program{};
|
|
pixel_program.stage = ShaderType::Pixel;
|
|
pixel_program.info = fragment.program.info;
|
|
pixel_program.bindings = fragment.program.bindings;
|
|
ShaderVertexInputInfo vertex{};
|
|
vertex.stage.program = &vertex_program;
|
|
vertex.resources_num = 2;
|
|
vertex.buffers_num = 1;
|
|
vertex.buffers[0].stride = 6 * sizeof(float);
|
|
vertex.buffers[0].attr_num = 2;
|
|
vertex.buffers[0].attr_indices[1] = 1;
|
|
vertex.buffers[0].attr_offsets[1] = 2 * sizeof(float);
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
const auto format = i == 0 ? Prospero::BufferFormat::k32_32Float
|
|
: packed_vertex_color ? Prospero::BufferFormat::k11_11_10Float
|
|
: Prospero::BufferFormat::k32_32_32_32Float;
|
|
vertex.resources[i].fields[3] = DstSel(4, 5, 6, 7) |
|
|
(static_cast<uint32_t>(format) << 12u);
|
|
vertex.resources_dst[i].registers_num =
|
|
i == 0 ? 2 : vertex_program.info.vertex_fetch_components[1];
|
|
}
|
|
ShaderPixelInputInfo pixel{};
|
|
pixel.stage.program = &pixel_program;
|
|
pixel.stage.resources = std::move(fragment.resources);
|
|
|
|
RenderColorInfo color{};
|
|
color.desc.type = BindingType::RenderTarget;
|
|
color.desc.info.data = {depth_address + 0x10000, extent * extent * 16};
|
|
color.desc.info.mip_layout[0] = {0, color.desc.info.data.size, extent, extent};
|
|
color.desc.info.pixel_format = vk::Format::eR32G32B32A32Sfloat;
|
|
color.desc.info.guest_format = Prospero::BufferFormat::k32_32_32_32Float;
|
|
color.desc.info.extent = {extent, extent, 1};
|
|
color.desc.info.pitch = extent;
|
|
color.desc.info.bytes_per_block = 16;
|
|
color.desc.view_info.format = color.desc.info.pixel_format;
|
|
color.desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
color.image_id = cache.FindImage(color.desc);
|
|
std::array<float, 18> vertices{
|
|
-0.75f, -0.75f, 1, 1, 1, 1, 0.75f, -0.75f, 0.5f, 1, 1, 1,
|
|
0.0f, 0.75f, 0.25f, 1, 1, 1};
|
|
if (depth_feedback) {
|
|
vertices = {-1, -1, 0, 0, 0, 1, 3, -1, 2, 0, 0, 1, -1, 3, 0, 2, 0, 1};
|
|
}
|
|
std::vector<u32> vertex_words(vertices.size());
|
|
std::memcpy(vertex_words.data(), vertices.data(), sizeof(vertices));
|
|
if (packed_vertex_color) {
|
|
// R11/G11/B10 unsigned floats: 0.5, 1.0, 2.0; Vulkan supplies the missing alpha as 1.
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
vertex_words[i * 6 + 2] = 0x801e0380u;
|
|
}
|
|
}
|
|
auto buffer = CreateHostBuffer(name, sizeof(vertices), vk::BufferUsageFlagBits::eVertexBuffer,
|
|
vertex_words);
|
|
const auto pipeline = [&](bool enabled, uint8_t front, uint8_t back,
|
|
bool provoking_last = false) -> PipelineCache::Pipeline & {
|
|
HW::ModeControl mode{};
|
|
mode.poly_mode = enabled;
|
|
mode.polymode_front_ptype = front;
|
|
mode.polymode_back_ptype = back;
|
|
mode.provoking_vtx_last = provoking_last;
|
|
registers.SetModeControl(mode);
|
|
return context.GetPipelineCache().CreateGraphicsPipeline(
|
|
std::span{&color, 1u}, depth, vertex, scheduler.Current(), &pixel,
|
|
vk::PrimitiveTopology::eTriangleList, false, vertex_shader, pixel_shader);
|
|
};
|
|
auto &filled = pipeline(true, 2, 2);
|
|
const auto draw = [&](const PipelineCache::Pipeline &selected) {
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, color.image_id);
|
|
if (depth.image_id) {
|
|
RenderExecutorTestAccess::BindRenderTarget(executor, depth.image_id);
|
|
}
|
|
auto bindings = RenderExecutorTestAccess::PrepareGraphicsBindings(
|
|
executor, vertex.stage, pixel.stage, true);
|
|
Require(name, "sampled attachment identity",
|
|
!depth_feedback || bindings.pixel->images[0].image_id == depth.image_id,
|
|
"the fragment must sample the depth attachment's native image");
|
|
auto &command = scheduler.Current();
|
|
auto rendering = RenderExecutorTestAccess::AcquireRenderTargets(
|
|
executor, command, &color, 1, depth, bindings.pixel);
|
|
const bool feedback_enabled = rendering.depth_stencil_attachment.image_layout ==
|
|
vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT;
|
|
Require(name, "per-draw depth feedback",
|
|
feedback_enabled == (depth_feedback && depth.depth_write_enable),
|
|
"feedback was not selected only for an overlapping fragment depth read/write");
|
|
RenderExecutorTestAccess::CommitBindings(
|
|
executor, command, selected, bindings.vertex, *bindings.pixel);
|
|
rendering.color_attachments[0].is_clear = true;
|
|
command.BeginRendering(rendering);
|
|
auto cmd = command.Handle();
|
|
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, selected.pipeline);
|
|
const vk::Viewport viewport{0, 0, static_cast<float>(extent),
|
|
static_cast<float>(extent), 0, 1};
|
|
const vk::Rect2D scissor{{0, 0}, {extent, extent}};
|
|
cmd.setViewportWithCount(1, &viewport);
|
|
cmd.setScissorWithCount(1, &scissor);
|
|
cmd.setLineWidth(1);
|
|
cmd.setDepthTestEnable(depth.depth_test_enable);
|
|
cmd.setDepthWriteEnable(depth.depth_write_enable);
|
|
cmd.setDepthCompareOp(vk::CompareOp::eAlways);
|
|
cmd.setDepthBiasEnable(false);
|
|
const vk::Bool32 write = true;
|
|
cmd.setColorWriteEnableEXT(1, &write);
|
|
cmd.setAttachmentFeedbackLoopEnableEXT(
|
|
feedback_enabled ? vk::ImageAspectFlags{vk::ImageAspectFlagBits::eDepth}
|
|
: vk::ImageAspectFlags{});
|
|
const vk::DeviceSize offset = 0;
|
|
cmd.bindVertexBuffers(0, 1, &buffer.buffer, &offset);
|
|
cmd.draw(3, 1, 0, 0);
|
|
command.EndRendering();
|
|
RenderExecutorTestAccess::ResetBindings(executor);
|
|
};
|
|
const auto read_color = [&] {
|
|
return ReadCachedTexel(name, context, color.image_id, {}, {extent, extent, 1});
|
|
};
|
|
draw(filled);
|
|
const auto solid_pixels = read_color();
|
|
if (packed_vertex_color) {
|
|
const std::array<float, 4> expected{0.5f, 1.0f, 2.0f, 1.0f};
|
|
const auto center = 4 * ((extent / 2) * extent + extent / 2);
|
|
for (uint32_t component = 0; component < expected.size(); component++) {
|
|
Require(name, "packed vertex fetch and coverage",
|
|
std::abs(std::bit_cast<float>(solid_pixels[center + component]) -
|
|
expected[component]) < 0.0001f && solid_pixels[component] == 0,
|
|
"packed vertex color changed channels, clamped HDR, or filled outside the triangle");
|
|
}
|
|
} else if (depth_feedback) {
|
|
// Consecutive draws in the same submission must see each earlier depth write.
|
|
draw(filled);
|
|
draw(filled);
|
|
depth.depth_write_enable = false;
|
|
draw(filled); // Reset feedback in the same command buffer and preserve the depth writes.
|
|
const auto repeated_pixels = read_color();
|
|
const auto stored_depth = ReadCachedTexel(
|
|
name, context, depth.image_id, {}, {extent, extent, 1});
|
|
for (uint32_t y = 0; y < extent; y++) {
|
|
for (uint32_t x = 0; x < extent; x++) {
|
|
const auto index = y * extent + x;
|
|
Require(name, "sampled and stored depth",
|
|
solid_pixels[index * 4] == std::bit_cast<uint32_t>(initial_depth(x, y)) &&
|
|
repeated_pixels[index * 4] ==
|
|
std::bit_cast<uint32_t>(initial_depth(x, y) + 0.375f) &&
|
|
stored_depth[index] == repeated_pixels[index * 4],
|
|
"depth feedback lost a prior write or the read-only draw changed depth");
|
|
}
|
|
}
|
|
} else {
|
|
auto &wireframe = pipeline(true, 1, 1);
|
|
Require(name, "pipeline cache", filled.pipeline != wireframe.pipeline &&
|
|
pipeline(true, 2, 2).pipeline == filled.pipeline &&
|
|
pipeline(false, 1, 0).pipeline == filled.pipeline,
|
|
"polygon mode was lost from the cache key or dormant face modes were applied");
|
|
draw(wireframe);
|
|
const auto line_pixels = read_color();
|
|
const auto interior = 4 * (16 * extent + 16);
|
|
Require(name, "GPU coverage", solid_pixels[interior] == 0x3f800000u &&
|
|
line_pixels[interior] == 0 &&
|
|
std::ranges::any_of(line_pixels, [](u32 value) { return value == 0x3f800000u; }),
|
|
"wireframe did not preserve triangle edges while leaving its interior empty");
|
|
auto &last_vertex = pipeline(true, 2, 2, true);
|
|
Require(name, "provoking vertex pipeline cache",
|
|
last_vertex.pipeline != filled.pipeline &&
|
|
pipeline(true, 2, 2, true).pipeline == last_vertex.pipeline &&
|
|
pipeline(true, 2, 2).pipeline == filled.pipeline,
|
|
"first and last provoking vertices did not keep distinct cached pipelines");
|
|
draw(last_vertex);
|
|
const auto last_pixels = read_color();
|
|
for (size_t i = 0; i < solid_pixels.size(); i++) {
|
|
Require(name, "flat provoking vertex output",
|
|
last_pixels[i] == (solid_pixels[i] == 0 ? 0 : 0x3e800000u),
|
|
"last-vertex flat shading changed coverage or did not use vertex two");
|
|
}
|
|
}
|
|
resources.UnmapMemory(depth_address, allocation_size);
|
|
scheduler.Finish();
|
|
DestroyBuffer(&buffer);
|
|
m_device.destroyShaderModule(pixel_shader.module);
|
|
m_device.destroyShaderModule(vertex_shader.module);
|
|
Require(name, "release depth memory",
|
|
Libs::LibKernel::Memory::KernelMunmap(depth_address, allocation_size) == 0 &&
|
|
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
|
|
direct_offset, allocation_size) == 0,
|
|
"depth mapping or allocation release failed");
|
|
std::printf("[gpu] %-32s ok\n", name);
|
|
}
|
|
|
|
std::vector<u32> RenderFragment(const GraphicsCase &test,
|
|
const CompiledShader &fragment) {
|
|
const auto vertex_spirv =
|
|
TestSpv::MakePassthroughVertexSpirv(test.layers > 1, test.vertex_clip_w);
|
|
ValidateSpirv(test.name, vertex_spirv);
|
|
|
|
Image target =
|
|
CreateImageMips(test.name, 1, 1, vk::Format::eR32G32B32A32Sfloat,
|
|
vk::ImageUsageFlagBits::eColorAttachment, {}, 4,
|
|
vk::ImageLayout::eGeneral, vk::ImageType::e2D,
|
|
test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D,
|
|
test.layers, 0, 0, test.samples);
|
|
Image resolved;
|
|
if (test.samples != vk::SampleCountFlagBits::e1) {
|
|
resolved = CreateImageMips(
|
|
test.name, 1, 1, target.format, vk::ImageUsageFlagBits::eColorAttachment,
|
|
{}, 4, vk::ImageLayout::eGeneral, vk::ImageType::e2D,
|
|
test.layers > 1 ? vk::ImageViewType::e2DArray : vk::ImageViewType::e2D,
|
|
test.layers);
|
|
}
|
|
const std::vector<u32> default_vertices = {
|
|
0xbf800000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u,
|
|
0x3f800000u, 0x40400000u, 0xbf800000u, 0x3e800000u, 0x3f000000u,
|
|
0x3f400000u, 0x3f800000u, 0xbf800000u, 0x40400000u, 0x3e800000u,
|
|
0x3f000000u, 0x3f400000u, 0x3f800000u,
|
|
};
|
|
const auto &vertices =
|
|
test.vertices.empty() ? default_vertices : test.vertices;
|
|
Require(test.name, "graphics", vertices.size() == 18u,
|
|
"graphics vertex buffer must contain three pos2/color4 vertices");
|
|
auto vertex_buffer =
|
|
CreateHostBuffer(test.name, vertices.size() * sizeof(u32),
|
|
vk::BufferUsageFlagBits::eVertexBuffer, vertices);
|
|
|
|
vk::ShaderModule vertex_module = CreateShaderModule(test.name, vertex_spirv);
|
|
vk::ShaderModule fragment_module = CreateShaderModule(test.name, fragment.spirv);
|
|
|
|
const auto &fragment_bind = fragment.program.bindings;
|
|
vk::PushConstantRange push_constant_range{};
|
|
if (fragment_bind.UsesPushData()) {
|
|
Require(test.name, "graphics",
|
|
test.push_constants.size() * sizeof(u32) ==
|
|
fragment_bind.ShaderDataDwords() * sizeof(u32),
|
|
"fragment push constant data size does not match reflection");
|
|
push_constant_range.stageFlags = vk::ShaderStageFlagBits::eFragment;
|
|
push_constant_range.offset = 0;
|
|
push_constant_range.size = ShaderRecompiler::IR::NativePushConstantSize;
|
|
}
|
|
|
|
vk::PipelineLayoutCreateInfo pipeline_layout_info{};
|
|
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
|
pipeline_layout_info.pushConstantRangeCount =
|
|
push_constant_range.size != 0 ? 1u : 0u;
|
|
pipeline_layout_info.pPushConstantRanges =
|
|
push_constant_range.size != 0 ? &push_constant_range : nullptr;
|
|
vk::PipelineLayout pipeline_layout = nullptr;
|
|
RequireVk(test.name, "graphics",
|
|
m_device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
|
&pipeline_layout),
|
|
"vkCreatePipelineLayout");
|
|
|
|
vk::PipelineShaderStageCreateInfo stages[2] = {};
|
|
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
|
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
|
|
stages[0].module = vertex_module;
|
|
stages[0].pName = "main";
|
|
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
|
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
|
|
stages[1].module = fragment_module;
|
|
stages[1].pName = "main";
|
|
|
|
vk::VertexInputBindingDescription vertex_binding{};
|
|
vertex_binding.binding = 0;
|
|
vertex_binding.stride = 6u * sizeof(float);
|
|
vertex_binding.inputRate = vk::VertexInputRate::eVertex;
|
|
vk::VertexInputAttributeDescription attributes[2] = {};
|
|
attributes[0].location = 0;
|
|
attributes[0].binding = 0;
|
|
attributes[0].format = vk::Format::eR32G32Sfloat;
|
|
attributes[0].offset = 0;
|
|
attributes[1].location = 1;
|
|
attributes[1].binding = 0;
|
|
attributes[1].format = vk::Format::eR32G32B32A32Sfloat;
|
|
attributes[1].offset = 2u * sizeof(float);
|
|
vk::PipelineVertexInputStateCreateInfo vertex_input{};
|
|
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
|
vertex_input.vertexBindingDescriptionCount = 1;
|
|
vertex_input.pVertexBindingDescriptions = &vertex_binding;
|
|
vertex_input.vertexAttributeDescriptionCount = 2;
|
|
vertex_input.pVertexAttributeDescriptions = attributes;
|
|
|
|
vk::PipelineInputAssemblyStateCreateInfo input_assembly{};
|
|
input_assembly.sType =
|
|
vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
|
|
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
|
|
|
|
vk::Viewport viewport{};
|
|
viewport.x = 0.0f;
|
|
viewport.y = 0.0f;
|
|
viewport.width = 1.0f;
|
|
viewport.height = 1.0f;
|
|
viewport.minDepth = 0.0f;
|
|
viewport.maxDepth = 1.0f;
|
|
vk::Rect2D scissor{};
|
|
scissor.extent.width = 1;
|
|
scissor.extent.height = 1;
|
|
vk::PipelineViewportStateCreateInfo viewport_state{};
|
|
viewport_state.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
|
|
viewport_state.viewportCount = 1;
|
|
viewport_state.pViewports = &viewport;
|
|
viewport_state.scissorCount = 1;
|
|
viewport_state.pScissors = &scissor;
|
|
|
|
vk::PipelineRasterizationStateCreateInfo raster{};
|
|
raster.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
|
raster.polygonMode = vk::PolygonMode::eFill;
|
|
raster.cullMode = vk::CullModeFlagBits::eNone;
|
|
raster.frontFace = vk::FrontFace::eCounterClockwise;
|
|
raster.lineWidth = 1.0f;
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisample{};
|
|
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
|
|
multisample.rasterizationSamples = test.samples;
|
|
|
|
vk::PipelineColorBlendAttachmentState color_attachment{};
|
|
color_attachment.colorWriteMask =
|
|
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
|
vk::PipelineColorBlendStateCreateInfo color_blend{};
|
|
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
|
color_blend.attachmentCount = 1;
|
|
color_blend.pAttachments = &color_attachment;
|
|
|
|
vk::GraphicsPipelineCreateInfo pipeline_info{};
|
|
const vk::Format color_format = vk::Format::eR32G32B32A32Sfloat;
|
|
vk::PipelineRenderingCreateInfo rendering_pipeline{};
|
|
rendering_pipeline.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
|
rendering_pipeline.colorAttachmentCount = 1;
|
|
rendering_pipeline.pColorAttachmentFormats = &color_format;
|
|
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
|
pipeline_info.pNext = &rendering_pipeline;
|
|
pipeline_info.stageCount = 2;
|
|
pipeline_info.pStages = stages;
|
|
pipeline_info.pVertexInputState = &vertex_input;
|
|
pipeline_info.pInputAssemblyState = &input_assembly;
|
|
pipeline_info.pViewportState = &viewport_state;
|
|
pipeline_info.pRasterizationState = &raster;
|
|
pipeline_info.pMultisampleState = &multisample;
|
|
pipeline_info.pColorBlendState = &color_blend;
|
|
pipeline_info.layout = pipeline_layout;
|
|
vk::Pipeline pipeline = nullptr;
|
|
RequireVk(test.name, "graphics",
|
|
m_device.createGraphicsPipelines(nullptr, 1, &pipeline_info,
|
|
nullptr, &pipeline),
|
|
"vkCreateGraphicsPipelines");
|
|
|
|
vk::CommandBuffer cmd = BeginCommands(test.name, "graphics");
|
|
vk::RenderingAttachmentInfo color{};
|
|
color.sType = vk::StructureType::eRenderingAttachmentInfo;
|
|
color.imageView = target.view;
|
|
color.imageLayout = vk::ImageLayout::eGeneral;
|
|
color.loadOp = vk::AttachmentLoadOp::eClear;
|
|
color.storeOp = vk::AttachmentStoreOp::eStore;
|
|
if (resolved.image != nullptr) {
|
|
color.resolveMode = vk::ResolveModeFlagBits::eAverage;
|
|
color.resolveImageView = resolved.view;
|
|
color.resolveImageLayout = vk::ImageLayout::eGeneral;
|
|
}
|
|
vk::RenderingInfo rendering{};
|
|
rendering.sType = vk::StructureType::eRenderingInfo;
|
|
rendering.renderArea.extent = {1, 1};
|
|
rendering.layerCount = test.layers;
|
|
rendering.colorAttachmentCount = 1;
|
|
rendering.pColorAttachments = &color;
|
|
cmd.beginRendering(rendering);
|
|
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
|
if (push_constant_range.size != 0) {
|
|
ShaderRecompiler::IR::PushData push_data;
|
|
std::copy(test.push_constants.begin(), test.push_constants.end(),
|
|
push_data.dwords.begin() + fragment_bind.push_data_start_dword);
|
|
cmd.pushConstants(pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0,
|
|
sizeof(push_data), push_data.dwords.data());
|
|
}
|
|
vk::DeviceSize offset = 0;
|
|
cmd.bindVertexBuffers(0, 1, &vertex_buffer.buffer, &offset);
|
|
cmd.draw(3, test.layers, 0, 0);
|
|
cmd.endRendering();
|
|
EndSubmitAndFree(test.name, "graphics", cmd);
|
|
target.layout = vk::ImageLayout::eGeneral;
|
|
|
|
auto pixel = ReadImage(test.name, resolved.image != nullptr ? &resolved : &target);
|
|
pixel.resize(4 * test.layers);
|
|
|
|
m_device.destroyPipeline(pipeline, nullptr);
|
|
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
|
|
m_device.destroyShaderModule(fragment_module, nullptr);
|
|
m_device.destroyShaderModule(vertex_module, nullptr);
|
|
DestroyBuffer(&vertex_buffer);
|
|
DestroyImage(&resolved);
|
|
DestroyImage(&target);
|
|
return pixel;
|
|
}
|
|
|
|
void CheckGpuTilerCpuParity() {
|
|
constexpr const char *name = "GpuTilerCpuParity";
|
|
EnsureRuntimeContext();
|
|
|
|
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
StreamBuffer parameters(m_runtime_context, scheduler, MemoryUsage::Stream,
|
|
1u << 20);
|
|
TileManager tile_manager(m_runtime_context, scheduler, parameters);
|
|
constexpr uint64_t conversion_limit = 128ull << 20;
|
|
Require(name, "D16 conversion chunk boundaries",
|
|
TileManagerTestAccess::ConversionRows(0, 32ull << 10, 32ull << 10,
|
|
4096, 256, conversion_limit,
|
|
65535) == 4096 &&
|
|
TileManagerTestAccess::ConversionRows(
|
|
0, 32ull << 10, 32ull << 10, 4097, 256, conversion_limit,
|
|
65535) == 4096 &&
|
|
TileManagerTestAccess::ConversionRows(
|
|
257, 32ull << 10, 32ull << 10, 4097, 256, conversion_limit,
|
|
65535) == 4095,
|
|
"D16 conversion planner exceeds descriptor or dispatch limits");
|
|
|
|
const auto align_dword = [](uint64_t size) {
|
|
return (size + sizeof(u32) - 1u) & ~(uint64_t{sizeof(u32) - 1u});
|
|
};
|
|
const auto to_dwords = [&](const std::vector<uint8_t> &bytes,
|
|
uint64_t padded_size) {
|
|
std::vector<u32> words(static_cast<size_t>(padded_size / sizeof(u32)), 0);
|
|
std::memcpy(words.data(), bytes.data(), bytes.size());
|
|
return words;
|
|
};
|
|
const auto read_bytes = [&](const Buffer &buffer, uint64_t size) {
|
|
const auto words = ReadBuffer(
|
|
name, buffer, static_cast<size_t>(align_dword(size) / sizeof(u32)));
|
|
std::vector<uint8_t> bytes(static_cast<size_t>(size));
|
|
std::memcpy(bytes.data(), words.data(), bytes.size());
|
|
return bytes;
|
|
};
|
|
const auto host_barrier = [&](vk::Buffer buffer, uint64_t size,
|
|
vk::PipelineStageFlags source_stage,
|
|
vk::AccessFlags source_access) {
|
|
vk::BufferMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = source_access;
|
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = buffer;
|
|
barrier.offset = 0;
|
|
barrier.size = size;
|
|
scheduler.Current().Handle().pipelineBarrier(
|
|
source_stage, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1,
|
|
&barrier, 0, nullptr);
|
|
};
|
|
const auto gpu_detile =
|
|
[&](const std::vector<uint8_t> &tiled, std::vector<uint8_t> *linear,
|
|
uint64_t tiled_capacity, uint64_t linear_capacity,
|
|
std::span<const GpuTileInfo> infos) {
|
|
const uint64_t padded_tiled = align_dword(tiled_capacity);
|
|
const uint64_t padded_linear = align_dword(linear_capacity);
|
|
auto input = CreateHostBuffer(name, padded_tiled, AllFlags,
|
|
to_dwords(tiled, padded_tiled));
|
|
auto output = CreateHostBuffer(
|
|
name, padded_linear, AllFlags,
|
|
std::vector<u32>(static_cast<size_t>(padded_linear / sizeof(u32)),
|
|
0xabababab));
|
|
const auto result = tile_manager.Detile(input.buffer, 0, padded_tiled,
|
|
padded_linear, infos);
|
|
const vk::BufferCopy copy{result.offset, 0, padded_linear};
|
|
scheduler.Current().Handle().copyBuffer(result.buffer, output.buffer,
|
|
1, ©);
|
|
host_barrier(output.buffer, padded_linear,
|
|
vk::PipelineStageFlagBits::eTransfer,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
scheduler.Finish();
|
|
*linear = read_bytes(output, linear_capacity);
|
|
DestroyBuffer(&output);
|
|
DestroyBuffer(&input);
|
|
};
|
|
const auto gpu_tile = [&](const std::vector<uint8_t> &linear,
|
|
std::vector<uint8_t> *tiled,
|
|
uint64_t tiled_capacity, uint64_t linear_capacity,
|
|
std::span<const GpuTileInfo> infos) {
|
|
const uint64_t padded_tiled = align_dword(tiled_capacity);
|
|
const uint64_t padded_linear = align_dword(linear_capacity);
|
|
auto input = CreateHostBuffer(name, padded_linear, AllFlags,
|
|
to_dwords(linear, padded_linear));
|
|
auto output = CreateHostBuffer(
|
|
name, padded_tiled, AllFlags,
|
|
std::vector<u32>(static_cast<size_t>(padded_tiled / sizeof(u32)),
|
|
0xabababab));
|
|
tile_manager.Tile(input.buffer, 0, padded_linear, output.buffer, 0,
|
|
padded_tiled, infos);
|
|
host_barrier(output.buffer, padded_tiled,
|
|
vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::AccessFlagBits::eShaderWrite);
|
|
scheduler.Finish();
|
|
*tiled = read_bytes(output, tiled_capacity);
|
|
DestroyBuffer(&output);
|
|
DestroyBuffer(&input);
|
|
};
|
|
|
|
for (const bool d32 : std::array{true, false}) {
|
|
constexpr uint32_t width = 5;
|
|
constexpr uint32_t height = 3;
|
|
constexpr uint32_t pitch = 8;
|
|
constexpr uint32_t layers = 2;
|
|
constexpr uint64_t d16_row = pitch * sizeof(uint16_t);
|
|
constexpr uint64_t d32_row = pitch * sizeof(uint32_t);
|
|
constexpr uint64_t d16_slice = d16_row * height;
|
|
constexpr uint64_t d32_slice = d32_row * height;
|
|
constexpr uint64_t d16_size = d16_slice * layers;
|
|
constexpr uint64_t d32_size = d32_slice * layers;
|
|
StreamBuffer conversion_upload(m_runtime_context, scheduler,
|
|
MemoryUsage::Upload, 1u << 20);
|
|
auto [prefix, prefix_offset] = conversion_upload.Map(4096, 256);
|
|
Require(name, "D16 conversion prefix",
|
|
prefix != nullptr && prefix_offset == 0,
|
|
"failed to advance the D16 conversion upload ring");
|
|
std::memset(prefix, 0x7b, 4096);
|
|
conversion_upload.Commit();
|
|
auto [source_bytes, source_offset] = conversion_upload.Map(d16_size, 4);
|
|
Require(name, "D16 conversion source",
|
|
source_bytes != nullptr && source_offset != 0,
|
|
"failed to reserve a nonzero D16 conversion source");
|
|
std::memset(source_bytes, 0x3c, d16_size);
|
|
for (uint32_t layer = 0; layer < layers; layer++) {
|
|
for (uint32_t y = 0; y < height; y++) {
|
|
auto *row = reinterpret_cast<uint16_t *>(
|
|
source_bytes + layer * d16_slice + y * d16_row);
|
|
for (uint32_t x = 0; x < width; x++) {
|
|
row[x] = static_cast<uint16_t>(
|
|
0x1111u * (1u + x + y * width + layer * width * height));
|
|
}
|
|
}
|
|
}
|
|
conversion_upload.Commit();
|
|
auto promoted = CreateHostBuffer(
|
|
name, d32_size, AllFlags,
|
|
std::vector<u32>(static_cast<size_t>(d32_size / sizeof(u32)), 0));
|
|
auto round_trip = CreateHostBuffer(
|
|
name, d16_size, AllFlags,
|
|
std::vector<u32>(static_cast<size_t>(d16_size / sizeof(u32)),
|
|
0xa5a5a5a5u));
|
|
const TileManager::D16Layout promote_layout{
|
|
.width = width,
|
|
.height = height,
|
|
.layers = layers,
|
|
.source_row_stride = d16_row,
|
|
.target_row_stride = d32_row,
|
|
.source_slice_stride = d16_slice,
|
|
.target_slice_stride = d32_slice,
|
|
};
|
|
tile_manager.ConvertD16({conversion_upload.Handle(), source_offset,
|
|
conversion_upload.Size() - source_offset},
|
|
{promoted.buffer, 0, d32_size},
|
|
TileManager::D16Direction::Promote, d32,
|
|
promote_layout);
|
|
tile_manager.ConvertD16({promoted.buffer, 0, d32_size},
|
|
{round_trip.buffer, 0, d16_size},
|
|
TileManager::D16Direction::Demote, d32,
|
|
{.width = width,
|
|
.height = height,
|
|
.layers = layers,
|
|
.source_row_stride = d32_row,
|
|
.target_row_stride = d16_row,
|
|
.source_slice_stride = d32_slice,
|
|
.target_slice_stride = d16_slice});
|
|
host_barrier(round_trip.buffer, d16_size,
|
|
vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::AccessFlagBits::eShaderWrite);
|
|
scheduler.Finish();
|
|
const auto result = read_bytes(round_trip, d16_size);
|
|
bool conversion_matches = true;
|
|
for (uint32_t layer = 0; layer < layers; layer++) {
|
|
for (uint32_t y = 0; y < height; y++) {
|
|
for (uint32_t x = 0; x < pitch; x++) {
|
|
const auto offset =
|
|
layer * d16_slice + y * d16_row + x * sizeof(uint16_t);
|
|
conversion_matches &=
|
|
x < width
|
|
? std::memcmp(result.data() + offset, source_bytes + offset,
|
|
sizeof(uint16_t)) == 0
|
|
: result[offset] == 0xa5 && result[offset + 1] == 0xa5;
|
|
}
|
|
}
|
|
}
|
|
Require(name,
|
|
d32 ? "D32 conversion round trip" : "D24 conversion round trip",
|
|
conversion_matches,
|
|
"D16 conversion lost rows, layers, active values, or padding");
|
|
DestroyBuffer(&round_trip);
|
|
DestroyBuffer(&promoted);
|
|
}
|
|
|
|
auto fill = [](std::vector<uint8_t> *bytes, u32 salt) {
|
|
for (size_t i = 0; i < bytes->size(); i++) {
|
|
uint64_t value =
|
|
i + static_cast<uint64_t>(salt) * 0x9e3779b97f4a7c15ull;
|
|
value = (value ^ (value >> 30u)) * 0xbf58476d1ce4e5b9ull;
|
|
value = (value ^ (value >> 27u)) * 0x94d049bb133111ebull;
|
|
(*bytes)[i] = static_cast<uint8_t>((value ^ (value >> 31u)) >> 56u);
|
|
}
|
|
};
|
|
auto compare = [&](const char *stage, const std::vector<uint8_t> &expected,
|
|
const std::vector<uint8_t> &actual) {
|
|
if (expected != actual) {
|
|
const auto mismatch = static_cast<size_t>(
|
|
std::mismatch(expected.begin(), expected.end(), actual.begin())
|
|
.first -
|
|
expected.begin());
|
|
std::ostringstream out;
|
|
out << "first mismatch at " << mismatch << " of " << expected.size();
|
|
Fail(name, stage, out.str());
|
|
}
|
|
};
|
|
auto convert_reference = [&](bool to_tiled, std::vector<uint8_t> *dst,
|
|
const std::vector<uint8_t> &src,
|
|
const GpuTileInfo &info) {
|
|
TileBlockLayout block{};
|
|
Require(name, "reference layout",
|
|
TileGetBlockLayout(info.family, info.bytes_per_element, block),
|
|
"CPU reference rejected GPU tile info");
|
|
const u32 tiled_width =
|
|
info.tiled_width != 0 ? info.tiled_width : info.pitch;
|
|
const u32 tiled_height =
|
|
info.tiled_height != 0 ? info.tiled_height : info.height;
|
|
const uint64_t columns =
|
|
(tiled_width + block.block_width - 1u) / block.block_width;
|
|
const uint64_t rows =
|
|
(tiled_height + block.block_height - 1u) / block.block_height;
|
|
const uint64_t slice = info.linear_slice_stride != 0
|
|
? info.linear_slice_stride
|
|
: static_cast<uint64_t>(info.pitch) *
|
|
info.height * info.bytes_per_element;
|
|
for (u32 z = 0; z < info.depth; ++z) {
|
|
for (u32 y = 0; y < info.height; ++y) {
|
|
for (u32 x = 0; x < info.width; ++x) {
|
|
const u32 bx = info.tail ? 0 : x / block.block_width;
|
|
const u32 by = info.tail ? 0 : y / block.block_height;
|
|
const u32 bz = info.tail ? 0 : z / block.block_depth;
|
|
const u32 lx = info.tail ? x + info.tail_x : x % block.block_width;
|
|
const u32 ly = info.tail ? y + info.tail_y : y % block.block_height;
|
|
const u32 lz = z % block.block_depth;
|
|
u32 local = 0, block_xor = 0;
|
|
Require(name, "reference offset",
|
|
TileGetBlockOffset(block, lx, ly, lz, local) &&
|
|
TileGetBlockXor(block, bx, by, info.surface_z + bz,
|
|
block_xor),
|
|
"CPU reference address lookup failed");
|
|
const uint64_t block_index =
|
|
static_cast<uint64_t>(bz) * columns * rows + by * columns + bx;
|
|
const uint64_t tiled = info.tiled_offset +
|
|
block_index * block.block_size +
|
|
(local ^ block_xor);
|
|
const uint64_t linear =
|
|
info.linear_offset + static_cast<uint64_t>(z) * slice +
|
|
static_cast<uint64_t>(y) * info.pitch * info.bytes_per_element +
|
|
static_cast<uint64_t>(x) * info.bytes_per_element;
|
|
const uint64_t dst_offset = to_tiled ? tiled : linear;
|
|
const uint64_t src_offset = to_tiled ? linear : tiled;
|
|
Require(name, "reference range",
|
|
dst_offset + info.bytes_per_element <= dst->size() &&
|
|
src_offset + info.bytes_per_element <= src.size(),
|
|
"CPU reference address escaped storage");
|
|
std::memcpy(dst->data() + dst_offset, src.data() + src_offset,
|
|
info.bytes_per_element);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
struct FamilyCase {
|
|
TileBlockFamily family;
|
|
u32 max_bpe;
|
|
};
|
|
constexpr FamilyCase families[] = {
|
|
{TileBlockFamily::Standard256B, 16},
|
|
{TileBlockFamily::Standard4KB, 16},
|
|
{TileBlockFamily::Standard4KB3D, 16},
|
|
{TileBlockFamily::Standard64KB, 16},
|
|
{TileBlockFamily::Standard64KB3D, 16},
|
|
{TileBlockFamily::Prt64KB, 16},
|
|
{TileBlockFamily::Prt64KB3D, 16},
|
|
{TileBlockFamily::RenderTarget64KB, 16},
|
|
{TileBlockFamily::Depth64KB, 8},
|
|
};
|
|
|
|
{
|
|
TileBlockLayout standard{}, prt{}, standard_3d{}, prt_3d{}, color{},
|
|
depth{};
|
|
u32 standard_offset = 0, prt_offset = 0, standard_3d_offset = 0,
|
|
prt_3d_offset = 0, color_z = 0, depth_z = 0;
|
|
bool fixed_addresses =
|
|
TileGetBlockLayout(TileBlockFamily::Standard64KB, 4, standard) &&
|
|
TileGetBlockLayout(TileBlockFamily::Prt64KB, 4, prt) &&
|
|
TileGetBlockLayout(TileBlockFamily::Standard64KB3D, 4, standard_3d) &&
|
|
TileGetBlockLayout(TileBlockFamily::Prt64KB3D, 4, prt_3d) &&
|
|
TileGetBlockLayout(TileBlockFamily::RenderTarget64KB, 4, color) &&
|
|
TileGetBlockLayout(TileBlockFamily::Depth64KB, 4, depth) &&
|
|
TileGetBlockOffset(standard, 64, 0, 0, standard_offset) &&
|
|
TileGetBlockOffset(prt, 64, 0, 0, prt_offset) &&
|
|
TileGetBlockOffset(standard_3d, 16, 0, 0, standard_3d_offset) &&
|
|
TileGetBlockOffset(prt_3d, 16, 0, 0, prt_3d_offset) &&
|
|
TileGetBlockXor(color, 0, 0, 1, color_z) &&
|
|
TileGetBlockXor(depth, 0, 0, 15, depth_z);
|
|
Require(name, "fixed address vectors",
|
|
fixed_addresses && standard_offset == 0x8000 &&
|
|
prt_offset == 0x8100 && standard_3d_offset == 0x8000 &&
|
|
prt_3d_offset == 0x8400 && standard_3d.block_width == 32 &&
|
|
standard_3d.block_height == 32 &&
|
|
standard_3d.block_depth == 16 && color_z == 0x800 &&
|
|
depth_z == 0xf00,
|
|
"a fixed block address changed");
|
|
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr u32 levels = 7;
|
|
TileSizeAlign total{};
|
|
TileSizeOffset mip[levels]{};
|
|
TilePaddedSize padded[levels]{};
|
|
TileGetTextureSize(format, 65, 33, levels,
|
|
Prospero::TileMode::kStandard256B, &total, mip,
|
|
padded);
|
|
constexpr u32 offsets[levels] = {0x1a00, 0xb00, 0x500, 0x300,
|
|
0x200, 0x100, 0};
|
|
constexpr u32 sizes[levels] = {0x2d00, 0xf00, 0x600, 0x200,
|
|
0x100, 0x100, 0x100};
|
|
bool layout_matches = total.size == 0x4700 && total.align == 0x100 &&
|
|
padded[0].width == 72 && padded[0].height == 40;
|
|
for (u32 level = 0; level < levels; ++level) {
|
|
layout_matches &= mip[level].offset == offsets[level] &&
|
|
mip[level].size == sizes[level];
|
|
}
|
|
Require(name, "fixed mip vector", layout_matches,
|
|
"the reverse-packed small-block mip layout changed");
|
|
}
|
|
|
|
u32 case_index = 0;
|
|
auto check_round_trip = [&](const char *stage, uint64_t tiled_size,
|
|
std::span<const GpuTileInfo> infos) {
|
|
uint64_t linear_size = 0;
|
|
for (const auto &info : infos) {
|
|
linear_size =
|
|
std::max(linear_size, info.linear_offset + info.linear_size);
|
|
}
|
|
std::vector<uint8_t> tiled(tiled_size);
|
|
std::vector<uint8_t> cpu(linear_size, 0);
|
|
std::vector<uint8_t> gpu(linear_size, 0xab);
|
|
fill(&tiled, ++case_index);
|
|
for (const auto &info : infos) {
|
|
convert_reference(false, &cpu, tiled, info);
|
|
}
|
|
gpu_detile(tiled, &gpu, tiled_size, linear_size, infos);
|
|
compare((std::string(stage) + " detile bytes").c_str(), cpu, gpu);
|
|
|
|
std::vector<uint8_t> linear(linear_size);
|
|
std::vector<uint8_t> cpu_tiled(tiled_size, 0xab);
|
|
std::vector<uint8_t> gpu_tiled(tiled_size, 0xab);
|
|
fill(&linear, 0x280u + case_index);
|
|
for (const auto &info : infos) {
|
|
convert_reference(true, &cpu_tiled, linear, info);
|
|
}
|
|
gpu_tile(linear, &gpu_tiled, tiled_size, linear_size, infos);
|
|
compare((std::string(stage) + " tile bytes").c_str(), cpu_tiled,
|
|
gpu_tiled);
|
|
};
|
|
for (const auto family : families) {
|
|
for (u32 bpe = 1; bpe <= family.max_bpe; bpe <<= 1u) {
|
|
TileBlockLayout block{};
|
|
Require(name, "block layout",
|
|
TileGetBlockLayout(family.family, bpe, block),
|
|
"admitted family/BPE has no block layout");
|
|
const bool volume = block.block_depth > 1;
|
|
const u32 width =
|
|
block.block_width * 3u + std::min(block.block_width, 3u);
|
|
const u32 height =
|
|
block.block_height * 3u + std::min(block.block_height, 3u);
|
|
const u32 depth = volume ? block.block_depth + 1u : 1u;
|
|
const u32 pitch = block.block_width * 4u;
|
|
const uint64_t block_columns =
|
|
(pitch + block.block_width - 1u) / block.block_width;
|
|
const uint64_t block_rows =
|
|
(height + block.block_height - 1u) / block.block_height;
|
|
const uint64_t block_slices =
|
|
(depth + block.block_depth - 1u) / block.block_depth;
|
|
const uint64_t storage_size =
|
|
block_columns * block_rows * block_slices * block.block_size;
|
|
const uint64_t slice_stride =
|
|
static_cast<uint64_t>(pitch) * height * bpe;
|
|
|
|
std::vector<uint8_t> tiled(storage_size);
|
|
std::vector<uint8_t> cpu(storage_size, 0);
|
|
std::vector<uint8_t> gpu(storage_size, 0xab);
|
|
fill(&tiled, ++case_index);
|
|
|
|
GpuTileInfo info{};
|
|
info.family = block.family;
|
|
info.bytes_per_element = block.bytes_per_element;
|
|
info.linear_size = storage_size;
|
|
info.tiled_size = storage_size;
|
|
info.linear_slice_stride = volume ? slice_stride : 0;
|
|
info.width = width;
|
|
info.height = height;
|
|
info.depth = depth;
|
|
info.pitch = pitch;
|
|
info.surface_z = family.family == TileBlockFamily::RenderTarget64KB ||
|
|
family.family == TileBlockFamily::Depth64KB
|
|
? 3
|
|
: 0;
|
|
convert_reference(false, &cpu, tiled, info);
|
|
gpu_detile(tiled, &gpu, storage_size, storage_size,
|
|
std::span<const GpuTileInfo>(&info, 1));
|
|
const auto family_label = [&](const char *operation) {
|
|
std::ostringstream out;
|
|
out << operation << " family=" << static_cast<u32>(family.family)
|
|
<< " bpe=" << bpe;
|
|
return out.str();
|
|
};
|
|
compare(family_label("detile bytes").c_str(), cpu, gpu);
|
|
|
|
{
|
|
std::vector<uint8_t> linear(storage_size);
|
|
std::vector<uint8_t> cpu_tiled(storage_size, 0xab);
|
|
std::vector<uint8_t> gpu_tiled(storage_size, 0xab);
|
|
fill(&linear, 0x80u + case_index);
|
|
convert_reference(true, &cpu_tiled, linear, info);
|
|
gpu_tile(linear, &gpu_tiled, storage_size, storage_size,
|
|
std::span<const GpuTileInfo>(&info, 1));
|
|
compare(family_label("tile bytes").c_str(), cpu_tiled, gpu_tiled);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exercise the real texture-layout/info-building seam for every format
|
|
// admitted by each standard tile mode. Formats sharing a byte/block width
|
|
// intentionally share a shader, but this loop still validates their
|
|
// texel-to-element conversion (notably every BCn format).
|
|
struct StandardMode {
|
|
Prospero::TileMode tile;
|
|
TileBlockFamily family;
|
|
};
|
|
constexpr StandardMode standard_modes[] = {
|
|
{Prospero::TileMode::kStandard256B, TileBlockFamily::Standard256B},
|
|
{Prospero::TileMode::kStandard4KB, TileBlockFamily::Standard4KB},
|
|
{Prospero::TileMode::kStandard64KB, TileBlockFamily::Standard64KB},
|
|
{Prospero::TileMode::kPrt, TileBlockFamily::Prt64KB},
|
|
};
|
|
for (const auto format :
|
|
{Prospero::BufferFormat::k8Srgb, Prospero::BufferFormat::k8_8Srgb,
|
|
Prospero::BufferFormat::k9_9_9_5Float}) {
|
|
for (const auto tile :
|
|
{Prospero::TileMode::kDepth, Prospero::TileMode::kRenderTarget}) {
|
|
TileTextureBlockLayout texture{};
|
|
Require(name, "RT format policy",
|
|
!TileGetTextureBlockLayout(format, tile, false, texture),
|
|
"non-render-target format admitted by an RT/depth tile family");
|
|
}
|
|
}
|
|
{
|
|
TileSurfaceLayout invalid{};
|
|
const TileSurfaceDescription description{
|
|
Prospero::BufferFormat::k32Float,
|
|
Prospero::TileMode::kStandard256B,
|
|
TileSurfaceDimension::Dim3D,
|
|
16,
|
|
16,
|
|
16,
|
|
1,
|
|
1,
|
|
};
|
|
Require(name, "3D tile policy",
|
|
!TileGetTiledTextureLayout(description, invalid),
|
|
"Standard256B volume silently fell back to a 2D array layout");
|
|
}
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::kFmask8_S4_F4;
|
|
constexpr auto tile = Prospero::TileMode::kStandard64KB;
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, 128, 128, 1, tile, &total, nullptr, nullptr);
|
|
const auto layout = TextureCalcUploadLayout(
|
|
format, 128, 128, 1, 1, tile, total.size, false, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
Require(name, "FMASK policy",
|
|
!TextureBuildGpuTileInfos(total.size, regions, layout, 1, infos),
|
|
"FMASK entered the texel tiler through a non-depth family");
|
|
}
|
|
u32 format_cases = 0;
|
|
for (u32 raw_format = 1;
|
|
raw_format <= static_cast<uint32_t>(Prospero::BufferFormat::kBc7Srgb);
|
|
++raw_format) {
|
|
const auto format = static_cast<Prospero::BufferFormat>(raw_format);
|
|
// FMASK contains sample-to-fragment metadata, not texels for the GPU tiler.
|
|
if (Prospero::IsFmaskTextureFormat(format)) {
|
|
continue;
|
|
}
|
|
for (const auto &mode : standard_modes) {
|
|
TileTextureBlockLayout texture{};
|
|
if (!TileGetTextureBlockLayout(format, mode.tile, false, texture)) {
|
|
continue;
|
|
}
|
|
const u32 bpe = texture.block.bytes_per_element;
|
|
Require(name, "format block", texture.block.family == mode.family,
|
|
"CPU-supported format selected the wrong block family");
|
|
|
|
constexpr u32 width = 67;
|
|
constexpr u32 height = 51;
|
|
const u32 pitch = TileGetTexturePitch(format, width, mode.tile);
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, width, height, 1, mode.tile, &total, nullptr,
|
|
nullptr);
|
|
Require(name, "format size", total.size != 0,
|
|
"supported format has an empty layout");
|
|
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, 1, 1, mode.tile,
|
|
total.size, false, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
if (!TextureBuildGpuTileInfos(total.size, regions, layout, 1, infos)) {
|
|
std::ostringstream out;
|
|
out << "format=" << raw_format
|
|
<< " tile=" << static_cast<uint32_t>(mode.tile)
|
|
<< " size=" << total.size << " pitch=" << pitch;
|
|
Fail(name, "format infos", out.str());
|
|
}
|
|
Require(name, "format family",
|
|
infos.size() == 1 && infos[0].family == mode.family &&
|
|
infos[0].bytes_per_element == bpe,
|
|
"texture info selected the wrong shader family");
|
|
|
|
std::vector<uint8_t> tiled(total.size);
|
|
std::vector<uint8_t> cpu(total.size, 0);
|
|
std::vector<uint8_t> gpu(total.size, 0xab);
|
|
fill(&tiled, ++case_index);
|
|
convert_reference(false, &cpu, tiled, infos[0]);
|
|
gpu_detile(tiled, &gpu, total.size, total.size, infos);
|
|
compare("format bytes", cpu, gpu);
|
|
++format_cases;
|
|
}
|
|
}
|
|
Require(name, "format coverage", format_cases != 0,
|
|
"no CPU-supported standard formats were tested");
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::k11_11_10UInt;
|
|
constexpr auto tile = Prospero::TileMode::kStandard64KB;
|
|
constexpr u32 width = 128, height = 128, levels = 8;
|
|
TileSurfaceLayout surface{};
|
|
const TileSurfaceDescription description{
|
|
format, tile, TileSurfaceDimension::Dim2D, width, height, 1,
|
|
levels, 1};
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, 1, levels, tile, false,
|
|
total);
|
|
const bool built = TileGetTiledTextureLayout(description, surface);
|
|
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
|
constexpr u32 tail_xy[7][2] = {{64, 0}, {0, 64}, {32, 0}, {0, 32},
|
|
{16, 0}, {8, 16}, {0, 24}};
|
|
bool valid = built && Prospero::NumBytesPerElement(format) == 4 &&
|
|
Prospero::BlockCompressedBytesPerBlock(format) == 0 &&
|
|
Prospero::SampledTextureNumericClass(format) ==
|
|
Prospero::TextureNumericClass::Uint &&
|
|
Prospero::RemapTextureFormat(format) ==
|
|
Prospero::BufferFormat::k32UInt &&
|
|
surface_format.vk_format == vk::Format::eR32Uint &&
|
|
surface_format.conversion_format == format &&
|
|
VulkanFormat(format) == vk::Format::eUndefined &&
|
|
surface.texture.block.block_width == 128 &&
|
|
surface.texture.block.block_height == 128 &&
|
|
surface.texture.block.block_depth == 1 &&
|
|
surface.first_tail_level == 1 &&
|
|
surface.mips[0].offset == 0x10000 && total.size == 0x20000 &&
|
|
total.align == 0x10000;
|
|
for (u32 level = 1; level < levels && valid; level++) {
|
|
valid &= surface.mips[level].offset == 0 &&
|
|
surface.mips[level].tail_x == tail_xy[level - 1][0] &&
|
|
surface.mips[level].tail_y == tail_xy[level - 1][1];
|
|
}
|
|
Require(name, "11_11_10 uint layout", valid,
|
|
"PS5 packed integer texture metadata or Standard64KB mip tail "
|
|
"changed");
|
|
|
|
const auto signed_format = Prospero::BufferFormat::k32SInt;
|
|
const auto signed_surface = TextureGetSurfaceFormatInfo(signed_format);
|
|
Require(name, "signed sampled surface mapping",
|
|
Prospero::SampledTextureNumericClass(signed_format) ==
|
|
Prospero::TextureNumericClass::Sint &&
|
|
Prospero::RemapTextureFormat(signed_format) ==
|
|
signed_format &&
|
|
signed_surface.vk_format == vk::Format::eR32Sint &&
|
|
signed_surface.conversion_format ==
|
|
Prospero::BufferFormat::kInvalid,
|
|
"signed sampled texture class or native backing changed");
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::k32_32_32Float;
|
|
TileTextureElementLayout element{};
|
|
Require(name, "RGB32 texture policy",
|
|
!TileGetTextureElementLayout(format, element),
|
|
"12-byte texel layouts must be rejected");
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::kBc1UNorm;
|
|
constexpr auto tile = Prospero::TileMode::kStandard64KB;
|
|
constexpr u32 width = 256, height = 256, levels = 9;
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, width, height, levels, tile, &total, nullptr,
|
|
nullptr);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, 1, tile,
|
|
total.size, false, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
const bool built =
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels, infos);
|
|
uint64_t linear_size = 0;
|
|
for (const auto &info : infos) {
|
|
linear_size =
|
|
std::max(linear_size, info.linear_offset + info.linear_size);
|
|
}
|
|
Require(name, "BC1 mip-tail capacities",
|
|
built && total.size == 0x10000 &&
|
|
layout.surface.first_tail_level == 0 &&
|
|
linear_size == 0x15560 && linear_size > total.size,
|
|
"BC1 mip tail conflated tiled and linear capacities");
|
|
check_round_trip("BC1 mip tail", total.size, infos);
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr auto tile = Prospero::TileMode::kRenderTarget;
|
|
constexpr u32 width = 129, height = 65, layers = 3;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, layers, 1, tile, false,
|
|
total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, 1, layers, tile,
|
|
total.size, true, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
const bool built =
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, 1, infos);
|
|
Require(name, "array infos",
|
|
built && infos.size() == layers && infos[0].surface_z == 0 &&
|
|
infos[1].surface_z == 1 && infos[2].surface_z == 2,
|
|
"array slices lost their absolute surface Z");
|
|
check_round_trip("array", total.size, infos);
|
|
}
|
|
|
|
for (const auto &mode : standard_modes) {
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr u32 levels = 2;
|
|
TileBlockLayout block{};
|
|
Require(name, "odd mip block", TileGetBlockLayout(mode.family, 4, block),
|
|
"odd multi-mip format has no block layout");
|
|
const u32 width = block.block_width * 2u + 1u;
|
|
const u32 height = block.block_height * 2u + 1u;
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, width, height, levels, mode.tile, &total,
|
|
nullptr, nullptr);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, 1, mode.tile,
|
|
total.size, false, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
Require(name, "odd mip infos",
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels,
|
|
infos) &&
|
|
infos.size() == 2 && infos[1].tiled_width >= infos[1].pitch &&
|
|
infos[1].tiled_height >= infos[1].height &&
|
|
(mode.family == TileBlockFamily::Standard256B ||
|
|
infos[1].tiled_height > infos[1].height),
|
|
"odd multi-mip physical stride collapsed to the active linear "
|
|
"extent");
|
|
check_round_trip("odd multi-mip", total.size, infos);
|
|
}
|
|
|
|
struct TailMode {
|
|
Prospero::TileMode tile;
|
|
TileBlockFamily family;
|
|
};
|
|
constexpr TailMode tail_modes[] = {
|
|
{Prospero::TileMode::kStandard4KB, TileBlockFamily::Standard4KB},
|
|
{Prospero::TileMode::kStandard64KB, TileBlockFamily::Standard64KB},
|
|
{Prospero::TileMode::kPrt, TileBlockFamily::Prt64KB},
|
|
{Prospero::TileMode::kRenderTarget, TileBlockFamily::RenderTarget64KB},
|
|
{Prospero::TileMode::kDepth, TileBlockFamily::Depth64KB},
|
|
};
|
|
for (const auto &mode : tail_modes) {
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr u32 levels = 7;
|
|
TileBlockLayout block{};
|
|
Require(name, "2D tail block", TileGetBlockLayout(mode.family, 4, block),
|
|
"2D tail mode has no block layout");
|
|
const u32 width = block.block_width * 2u;
|
|
const u32 height = block.block_height * 2u;
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, width, height, levels, mode.tile, &total,
|
|
nullptr, nullptr);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, 1, mode.tile,
|
|
total.size, true, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
Require(name, "2D mip tail seam",
|
|
layout.surface.first_tail_level == 2 &&
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels,
|
|
infos) &&
|
|
infos.size() == levels && !infos[0].tail && !infos[1].tail &&
|
|
std::all_of(infos.begin() + 2, infos.end(),
|
|
[](const auto &info) { return info.tail; }),
|
|
"2D mip chain lost its linear/tiled tail boundary");
|
|
check_round_trip("2D mip tail seam", total.size, infos);
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr auto tile = Prospero::TileMode::kStandard4KB;
|
|
constexpr u32 width = 65;
|
|
constexpr u32 height = 129;
|
|
constexpr u32 depth = 17;
|
|
constexpr u32 levels = 2;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, depth, levels, tile, true,
|
|
total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, depth, tile,
|
|
total.size, false, true, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
const bool built =
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels, infos);
|
|
Require(name, "3D mip infos",
|
|
regions.size() == depth + (depth >> 1u) && built &&
|
|
infos.size() == 4 && infos[0].depth == 8 &&
|
|
infos[1].depth == 8 && infos[2].depth == 1 &&
|
|
infos[3].depth == 8 && infos[0].tiled_offset == 0x19000 &&
|
|
infos[1].tiled_offset == 0x83000 &&
|
|
infos[2].tiled_offset == 0xed000 &&
|
|
infos[3].tiled_offset == 0 && infos[3].pitch == 32 &&
|
|
infos[3].height == 64 && infos[3].tiled_width == 40 &&
|
|
infos[3].tiled_height == 80,
|
|
"Standard4KB3D mip depth, packing, or physical stride changed");
|
|
check_round_trip("3D mip", total.size, infos);
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::kBc1UNorm;
|
|
constexpr auto tile = Prospero::TileMode::kStandard4KB;
|
|
constexpr u32 width = 65;
|
|
constexpr u32 height = 129;
|
|
constexpr u32 depth = 17;
|
|
constexpr u32 levels = 2;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, depth, levels, tile, true,
|
|
total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, depth, tile,
|
|
total.size, false, true, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
Require(name, "3D BC mip infos",
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels,
|
|
infos) &&
|
|
infos.size() == 4 && infos[3].pitch == 8 &&
|
|
infos[3].height == 16 && infos[3].tiled_width == 16 &&
|
|
infos[3].tiled_height == 24,
|
|
"block-compressed 3D mip lost its physical row or slice stride");
|
|
check_round_trip("3D BC mip", total.size, infos);
|
|
}
|
|
|
|
{
|
|
constexpr auto format = Prospero::BufferFormat::kBc3UNorm;
|
|
constexpr auto tile = Prospero::TileMode::kLinear;
|
|
constexpr u32 width = 8, height = 8, levels = 4;
|
|
TileSizeAlign total{};
|
|
TileGetTextureSize(format, width, height, levels, tile, &total, nullptr,
|
|
nullptr);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, levels, 1, tile,
|
|
total.size, false, false, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
Require(name, "linear BC native regions",
|
|
regions.size() == levels &&
|
|
std::all_of(regions.begin(), regions.end(),
|
|
[](const auto ®ion) {
|
|
return region.bufferRowLength >=
|
|
region.imageExtent.width &&
|
|
region.bufferImageHeight >=
|
|
region.imageExtent.height &&
|
|
region.bufferRowLength % 4 == 0 &&
|
|
region.bufferImageHeight % 4 == 0;
|
|
}),
|
|
"linear BC mip tails emitted invalid Vulkan copy strides");
|
|
}
|
|
|
|
struct VolumeModeCase {
|
|
Prospero::TileMode tile;
|
|
TileBlockFamily family;
|
|
Prospero::BufferFormat format;
|
|
};
|
|
constexpr VolumeModeCase volume_modes[] = {
|
|
{Prospero::TileMode::kStandard64KB, TileBlockFamily::Standard64KB3D,
|
|
Prospero::BufferFormat::k32Float},
|
|
{Prospero::TileMode::kPrt, TileBlockFamily::Prt64KB3D,
|
|
Prospero::BufferFormat::k32Float},
|
|
{Prospero::TileMode::kRenderTarget, TileBlockFamily::RenderTarget64KB,
|
|
Prospero::BufferFormat::k32Float},
|
|
{Prospero::TileMode::kDepth, TileBlockFamily::Depth64KB,
|
|
Prospero::BufferFormat::k32Float},
|
|
{Prospero::TileMode::kStandard64KB, TileBlockFamily::Standard64KB3D,
|
|
Prospero::BufferFormat::kBc1UNorm},
|
|
{Prospero::TileMode::kPrt, TileBlockFamily::Prt64KB3D,
|
|
Prospero::BufferFormat::kBc3UNorm},
|
|
};
|
|
for (const auto test : volume_modes) {
|
|
constexpr u32 width = 65, height = 33, depth = 37, levels = 6;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(test.format, width, height, depth, levels,
|
|
test.tile, true, total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(test.format, width, height, levels, depth,
|
|
test.tile, total.size, true, true, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
const bool built =
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels, infos);
|
|
const bool uses_z = test.family == TileBlockFamily::RenderTarget64KB ||
|
|
test.family == TileBlockFamily::Depth64KB;
|
|
Require(name, "volume family infos",
|
|
built && !infos.empty() &&
|
|
std::all_of(infos.begin(), infos.end(),
|
|
[&](const auto &info) {
|
|
return info.family == test.family;
|
|
}) &&
|
|
(!uses_z || std::any_of(infos.begin(), infos.end(),
|
|
[](const auto &info) {
|
|
return info.surface_z != 0;
|
|
})),
|
|
"volume mode selected the wrong family or lost its surface Z");
|
|
check_round_trip("volume family", total.size, infos);
|
|
}
|
|
|
|
struct VolumeTailCase {
|
|
Prospero::BufferFormat format;
|
|
u32 bytes;
|
|
};
|
|
constexpr VolumeTailCase volume_tails[] = {
|
|
{Prospero::BufferFormat::k8UNorm, 1},
|
|
{Prospero::BufferFormat::k16UNorm, 2},
|
|
{Prospero::BufferFormat::k32Float, 4},
|
|
{Prospero::BufferFormat::k16_16_16_16Float, 8},
|
|
{Prospero::BufferFormat::k32_32_32_32Float, 16},
|
|
{Prospero::BufferFormat::kBc1UNorm, 8},
|
|
{Prospero::BufferFormat::kBc3UNorm, 16},
|
|
};
|
|
constexpr u32 volume_tail_xy[5][5][2] = {
|
|
{{0, 8}, {8, 4}, {8, 0}, {0, 4}, {0, 0}},
|
|
{{0, 8}, {4, 4}, {4, 0}, {0, 4}, {0, 0}},
|
|
{{0, 8}, {4, 4}, {4, 0}, {0, 4}, {0, 0}},
|
|
{{0, 4}, {4, 2}, {4, 0}, {0, 2}, {0, 0}},
|
|
{{0, 4}, {2, 2}, {2, 0}, {0, 2}, {0, 0}},
|
|
};
|
|
for (const auto &tail : volume_tails) {
|
|
constexpr auto tile = Prospero::TileMode::kStandard4KB;
|
|
constexpr u32 levels = 5;
|
|
TileBlockLayout block{};
|
|
Require(
|
|
name, "3D tail block",
|
|
TileGetBlockLayout(TileBlockFamily::Standard4KB3D, tail.bytes, block),
|
|
"Standard4KB3D tail format has no block layout");
|
|
const bool compressed =
|
|
Prospero::BlockCompressedBytesPerBlock(tail.format) != 0;
|
|
const u32 width = block.block_width * (compressed ? 4u : 1u);
|
|
const u32 height = block.block_height / 2u * (compressed ? 4u : 1u);
|
|
const u32 depth = block.block_depth + 1u;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(tail.format, width, height, depth, levels, tile,
|
|
true, total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(tail.format, width, height, levels, depth,
|
|
tile, total.size, false, true, name);
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
std::vector<GpuTileInfo> infos;
|
|
bool valid = total.size == 8192 &&
|
|
regions.size() == depth + std::max(depth >> 1u, 1u) +
|
|
std::max(depth >> 2u, 1u) +
|
|
std::max(depth >> 3u, 1u) +
|
|
std::max(depth >> 4u, 1u) &&
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, levels,
|
|
infos) &&
|
|
infos.size() == 6;
|
|
const u32 table = std::countr_zero(tail.bytes);
|
|
for (u32 level = 0; level < levels && valid; level++) {
|
|
const auto &info = infos[level == 0 ? 0 : level + 1];
|
|
valid &= info.tail && info.tail_x == volume_tail_xy[table][level][0] &&
|
|
info.tail_y == volume_tail_xy[table][level][1] &&
|
|
info.tiled_offset == 0;
|
|
}
|
|
if (valid) {
|
|
valid = infos[1].tail &&
|
|
infos[1].tail_x == volume_tail_xy[table][0][0] &&
|
|
infos[1].tail_y == volume_tail_xy[table][0][1] &&
|
|
infos[1].tiled_offset == 4096;
|
|
}
|
|
Require(
|
|
name, "3D mip tail infos", valid,
|
|
"Standard4KB3D mip tail coordinates or block-slice packing changed");
|
|
check_round_trip("3D mip tail", total.size, infos);
|
|
}
|
|
|
|
for (const auto family :
|
|
{TileBlockFamily::Standard4KB, TileBlockFamily::Standard64KB,
|
|
TileBlockFamily::Prt64KB, TileBlockFamily::RenderTarget64KB,
|
|
TileBlockFamily::Depth64KB}) {
|
|
for (u32 bpe = 1; bpe <= 16; bpe <<= 1u) {
|
|
if (family == TileBlockFamily::Depth64KB && bpe == 16)
|
|
continue;
|
|
TileBlockLayout block{};
|
|
Require(name, "tail layout", TileGetBlockLayout(family, bpe, block),
|
|
"tail family/BPE has no block layout");
|
|
const u32 width = std::max(block.block_width / 4u, 1u);
|
|
const u32 height = std::max(block.block_height / 4u, 1u);
|
|
const u32 x = block.block_width / 2u;
|
|
const u32 y = block.block_height / 2u;
|
|
const u32 pitch = width;
|
|
const uint64_t linear_size =
|
|
static_cast<uint64_t>(pitch) * height * bpe;
|
|
std::vector<uint8_t> tiled(block.block_size);
|
|
std::vector<uint8_t> cpu(linear_size, 0xcd);
|
|
std::vector<uint8_t> gpu(linear_size, 0xab);
|
|
fill(&tiled, ++case_index);
|
|
GpuTileInfo info{};
|
|
info.family = block.family;
|
|
info.bytes_per_element = block.bytes_per_element;
|
|
info.linear_size = linear_size;
|
|
info.tiled_size = block.block_size;
|
|
info.width = width;
|
|
info.height = height;
|
|
info.pitch = pitch;
|
|
info.tail = true;
|
|
info.tail_x = x;
|
|
info.tail_y = y;
|
|
info.surface_z = family == TileBlockFamily::RenderTarget64KB ||
|
|
family == TileBlockFamily::Depth64KB
|
|
? 2
|
|
: 0;
|
|
convert_reference(false, &cpu, tiled, info);
|
|
gpu_detile(tiled, &gpu, block.block_size, linear_size,
|
|
std::span<const GpuTileInfo>(&info, 1));
|
|
compare("tail bytes", cpu, gpu);
|
|
|
|
std::vector<uint8_t> linear(linear_size);
|
|
std::vector<uint8_t> cpu_tiled(block.block_size, 0xab);
|
|
std::vector<uint8_t> gpu_tiled(block.block_size, 0xab);
|
|
fill(&linear, 0x400u + case_index);
|
|
convert_reference(true, &cpu_tiled, linear, info);
|
|
gpu_tile(linear, &gpu_tiled, block.block_size, linear_size,
|
|
std::span<const GpuTileInfo>(&info, 1));
|
|
compare("tail tile bytes", cpu_tiled, gpu_tiled);
|
|
}
|
|
}
|
|
|
|
TileBlockLayout small_block{};
|
|
Require(name, "small layout",
|
|
TileGetBlockLayout(TileBlockFamily::Standard256B, 4, small_block),
|
|
"small fixture layout is unavailable");
|
|
std::vector<uint8_t> small_input(small_block.block_size);
|
|
std::vector<uint8_t> small_expected(small_block.block_size, 0);
|
|
std::vector<uint8_t> small_output(small_block.block_size, 0xab);
|
|
fill(&small_input, 0xee);
|
|
GpuTileInfo small_info{};
|
|
small_info.family = small_block.family;
|
|
small_info.bytes_per_element = small_block.bytes_per_element;
|
|
small_info.linear_size = small_output.size();
|
|
small_info.tiled_size = small_input.size();
|
|
small_info.width = 1;
|
|
small_info.height = 1;
|
|
small_info.pitch = small_block.block_width;
|
|
convert_reference(false, &small_expected, small_input, small_info);
|
|
gpu_detile(small_input, &small_output, small_input.size(),
|
|
small_output.size(),
|
|
std::span<const GpuTileInfo>(&small_info, 1));
|
|
compare("small detile", small_expected, small_output);
|
|
|
|
std::fill(small_output.begin(), small_output.end(), 0xab);
|
|
gpu_detile(small_input, &small_output, small_input.size(),
|
|
small_output.size(),
|
|
std::span<const GpuTileInfo>(&small_info, 1));
|
|
compare("scheduler-owned reuse", small_expected, small_output);
|
|
|
|
constexpr auto volume_format = Prospero::BufferFormat::k32UInt;
|
|
constexpr auto volume_tile = Prospero::TileMode::kLinear;
|
|
constexpr u32 volume_width = 8, volume_height = 4, volume_depth = 5;
|
|
constexpr u32 volume_levels = 3;
|
|
const u32 volume_pitch =
|
|
TileGetTexturePitch(volume_format, volume_width, volume_tile);
|
|
TileSizeAlign volume_size{};
|
|
TileGetTextureTotalSize(volume_format, volume_width, volume_height,
|
|
volume_depth, volume_levels, volume_tile, true,
|
|
volume_size);
|
|
const auto volume_layout = TextureCalcUploadLayout(
|
|
volume_format, volume_width, volume_height, volume_levels, volume_depth,
|
|
volume_tile, volume_size.size, true, true, name);
|
|
const auto volume_copies = TextureBuildImageCopies(volume_layout);
|
|
std::vector<u32> volume_source(volume_size.size / sizeof(u32), 0);
|
|
std::vector<std::pair<size_t, u32>> volume_probes;
|
|
for (const auto © : volume_copies) {
|
|
for (u32 y = 0; y < copy.imageExtent.height; y++) {
|
|
for (u32 x = 0; x < copy.imageExtent.width; x++) {
|
|
const auto index =
|
|
(copy.bufferOffset +
|
|
(static_cast<uint64_t>(y) * copy.bufferRowLength + x) *
|
|
sizeof(u32)) /
|
|
sizeof(u32);
|
|
const u32 value =
|
|
0xa0000000u | (copy.imageSubresource.mipLevel << 24u) |
|
|
(static_cast<u32>(copy.imageOffset.z) << 16u) | (y << 8u) | x;
|
|
Require(name, "linear volume native bounds",
|
|
index < volume_source.size(),
|
|
"linear volume copy escaped its padded guest layout");
|
|
volume_source[index] = value;
|
|
volume_probes.emplace_back(static_cast<size_t>(index), value);
|
|
}
|
|
}
|
|
}
|
|
auto volume_upload =
|
|
CreateHostBuffer(name, volume_size.size,
|
|
vk::BufferUsageFlagBits::eTransferSrc, volume_source);
|
|
auto volume_download = CreateHostBuffer(
|
|
name, volume_size.size, vk::BufferUsageFlagBits::eTransferDst,
|
|
std::vector<u32>(volume_source.size(), 0));
|
|
ImageInfo volume_info{};
|
|
volume_info.data = {0x10000, volume_size.size};
|
|
volume_info.pixel_format = vk::Format::eR32Uint;
|
|
volume_info.guest_format = volume_format;
|
|
volume_info.type = Prospero::ImageType::kColor3D;
|
|
volume_info.extent = {volume_width, volume_height, volume_depth};
|
|
volume_info.resources = {volume_levels, 1};
|
|
volume_info.pitch = volume_pitch;
|
|
volume_info.bytes_per_block = sizeof(u32);
|
|
volume_info.tile_mode = volume_tile;
|
|
for (u32 level = 0; level < volume_levels; level++) {
|
|
volume_info.mip_layout[level] = {volume_layout.mips[level].offset,
|
|
volume_layout.mips[level].size,
|
|
volume_layout.mips[level].row_length,
|
|
volume_layout.mips[level].image_height};
|
|
}
|
|
Libs::Graphics::Image volume_image(m_runtime_context, scheduler,
|
|
volume_info);
|
|
volume_image.Upload(volume_copies, volume_upload.buffer, 0,
|
|
volume_size.size);
|
|
volume_image.Download(volume_copies, volume_download.buffer, 0,
|
|
volume_size.size);
|
|
scheduler.Finish();
|
|
const auto volume_observed =
|
|
ReadBuffer(name, volume_download, volume_source.size());
|
|
Require(name, "linear volume native content",
|
|
std::ranges::all_of(volume_probes,
|
|
[&](const auto &probe) {
|
|
return volume_observed[probe.first] ==
|
|
probe.second;
|
|
}),
|
|
"native upload/download did not preserve padded 3D mip rows");
|
|
DestroyBuffer(&volume_download);
|
|
DestroyBuffer(&volume_upload);
|
|
std::printf("[gpu] %-32s ok (%u cases, %u format/mode pairs)\n", name,
|
|
case_index, format_cases);
|
|
}
|
|
|
|
private:
|
|
RenderContext &Renderer() {
|
|
EXIT_IF(m_renderer == nullptr);
|
|
return *m_renderer;
|
|
}
|
|
|
|
void EnsureRuntimeContext() {
|
|
if (m_runtime_context.allocator != nullptr) {
|
|
return;
|
|
}
|
|
m_runtime_context.instance = m_instance;
|
|
m_runtime_context.physical_device = m_physical_device;
|
|
m_runtime_context.device = m_device;
|
|
m_physical_device.getProperties(
|
|
&m_runtime_context.physical_device_properties);
|
|
m_runtime_context.physical_device_memory_properties = m_memory_properties;
|
|
m_runtime_context.queue_family = m_queue_family;
|
|
m_runtime_context.queue = m_queue;
|
|
m_runtime_context.attachment_feedback_loop_enabled = true;
|
|
m_runtime_context.provoking_vertex_last_enabled = true;
|
|
const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info{
|
|
.format = vk::Format::eBc1RgbaUnormBlock,
|
|
.type = vk::ImageType::e2D,
|
|
.tiling = vk::ImageTiling::eOptimal,
|
|
.usage = vk::ImageUsageFlagBits::eSampled,
|
|
.flags = vk::ImageCreateFlagBits::eBlockTexelViewCompatible,
|
|
};
|
|
const auto block_texel_view_props =
|
|
m_physical_device.getImageFormatProperties2(block_texel_view_info);
|
|
m_runtime_context.supports_block_texel_view =
|
|
block_texel_view_props.result == vk::Result::eSuccess;
|
|
|
|
VmaVulkanFunctions functions{};
|
|
functions.vkGetInstanceProcAddr =
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr;
|
|
functions.vkGetDeviceProcAddr =
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr;
|
|
|
|
VmaAllocatorCreateInfo allocator_info{};
|
|
allocator_info.instance = m_instance;
|
|
allocator_info.physicalDevice = m_physical_device;
|
|
allocator_info.device = m_device;
|
|
allocator_info.pVulkanFunctions = &functions;
|
|
allocator_info.vulkanApiVersion = VK_API_VERSION_1_3;
|
|
allocator_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
|
|
RequireVk("VulkanHarness", "runtime context",
|
|
static_cast<vk::Result>(vmaCreateAllocator(
|
|
&allocator_info, &m_runtime_context.allocator)),
|
|
"vmaCreateAllocator");
|
|
m_renderer = std::make_unique<RenderContext>(m_runtime_context);
|
|
}
|
|
|
|
void Init() {
|
|
static vk::detail::DynamicLoader loader;
|
|
const auto get_instance_proc_addr =
|
|
loader.getProcAddress<PFN_vkGetInstanceProcAddr>(
|
|
"vkGetInstanceProcAddr");
|
|
Require("VulkanHarness", "dispatch", get_instance_proc_addr != nullptr,
|
|
"could not load the Vulkan loader");
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
|
|
|
|
vk::ApplicationInfo app{};
|
|
app.sType = vk::StructureType::eApplicationInfo;
|
|
app.pApplicationName = "ShaderRecompilerComputeTests";
|
|
app.apiVersion = VK_API_VERSION_1_3;
|
|
|
|
vk::InstanceCreateInfo instance_info{};
|
|
instance_info.sType = vk::StructureType::eInstanceCreateInfo;
|
|
instance_info.pApplicationInfo = &app;
|
|
RequireVk("VulkanHarness", "dispatch",
|
|
vk::createInstance(&instance_info, nullptr, &m_instance),
|
|
"vkCreateInstance");
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_instance);
|
|
|
|
u32 physical_count = 0;
|
|
RequireVk("VulkanHarness", "dispatch",
|
|
m_instance.enumeratePhysicalDevices(&physical_count, nullptr),
|
|
"vkEnumeratePhysicalDevices");
|
|
Require("VulkanHarness", "dispatch", physical_count != 0,
|
|
"no Vulkan physical devices");
|
|
std::vector<vk::PhysicalDevice> physical_devices(physical_count);
|
|
RequireVk("VulkanHarness", "dispatch",
|
|
m_instance.enumeratePhysicalDevices(&physical_count,
|
|
physical_devices.data()),
|
|
"vkEnumeratePhysicalDevices");
|
|
|
|
for (auto physical : physical_devices) {
|
|
u32 queue_count = 0;
|
|
physical.getQueueFamilyProperties(&queue_count, nullptr);
|
|
std::vector<vk::QueueFamilyProperties> queues(queue_count);
|
|
physical.getQueueFamilyProperties(&queue_count, queues.data());
|
|
for (u32 i = 0; i < queue_count; i++) {
|
|
if ((queues[i].queueFlags &
|
|
(vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) ==
|
|
(vk::QueueFlagBits::eCompute | vk::QueueFlagBits::eGraphics)) {
|
|
vk::PhysicalDeviceVulkan12Features features12{};
|
|
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
|
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
|
|
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
|
barycentric.pNext = &features12;
|
|
vk::PhysicalDeviceFeatures2 features{};
|
|
features.sType = vk::StructureType::ePhysicalDeviceFeatures2;
|
|
features.pNext = &barycentric;
|
|
physical.getFeatures2(&features);
|
|
if (barycentric.fragmentShaderBarycentric != true ||
|
|
features.features.shaderInt64 != true ||
|
|
features12.bufferDeviceAddress != true) {
|
|
continue;
|
|
}
|
|
m_physical_device = physical;
|
|
m_queue_family = i;
|
|
break;
|
|
}
|
|
}
|
|
if (m_physical_device != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
Require("VulkanHarness", "dispatch", m_physical_device != nullptr,
|
|
"no Vulkan graphics+compute device with fragment barycentrics");
|
|
m_physical_device.getMemoryProperties(&m_memory_properties);
|
|
|
|
vk::PhysicalDeviceFeatures available_features{};
|
|
m_physical_device.getFeatures(&available_features);
|
|
vk::PhysicalDeviceVulkan12Features available_features12{};
|
|
available_features12.sType =
|
|
vk::StructureType::ePhysicalDeviceVulkan12Features;
|
|
vk::PhysicalDeviceVulkan13Features available_features13{};
|
|
available_features13.sType =
|
|
vk::StructureType::ePhysicalDeviceVulkan13Features;
|
|
available_features13.pNext = &available_features12;
|
|
vk::PhysicalDeviceComputeShaderDerivativesFeaturesKHR available_derivatives{};
|
|
available_derivatives.pNext = &available_features13;
|
|
vk::PhysicalDeviceDepthClipEnableFeaturesEXT available_depth_clip{};
|
|
available_depth_clip.pNext = &available_derivatives;
|
|
vk::PhysicalDeviceDepthClipControlFeaturesEXT available_clip_control{};
|
|
available_clip_control.pNext = &available_depth_clip;
|
|
vk::PhysicalDeviceColorWriteEnableFeaturesEXT available_color_write{};
|
|
available_color_write.pNext = &available_clip_control;
|
|
vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT available_feedback_layout{};
|
|
available_feedback_layout.pNext = &available_color_write;
|
|
vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT available_feedback_dynamic{};
|
|
available_feedback_dynamic.pNext = &available_feedback_layout;
|
|
vk::PhysicalDeviceProvokingVertexFeaturesEXT available_provoking_vertex{};
|
|
available_provoking_vertex.pNext = &available_feedback_dynamic;
|
|
vk::PhysicalDeviceFeatures2 available_features2{};
|
|
available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
|
|
available_features2.pNext = &available_provoking_vertex;
|
|
m_physical_device.getFeatures2(&available_features2);
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features.shaderStorageImageWriteWithoutFormat == true,
|
|
"shaderStorageImageWriteWithoutFormat is not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features.shaderImageGatherExtended == true &&
|
|
available_derivatives.computeDerivativeGroupQuads == true,
|
|
"image gather or compute derivative quads are not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features12.timelineSemaphore == true,
|
|
"timeline semaphores are not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features13.dynamicRendering == true,
|
|
"dynamic rendering is not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features13.synchronization2 == true,
|
|
"synchronization2 is not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features.sampleRateShading == true,
|
|
"sample-rate shading is not supported");
|
|
Require("VulkanHarness", "dispatch", available_features.shaderInt64 == true,
|
|
"shaderInt64 is not supported");
|
|
Require("VulkanHarness", "dispatch",
|
|
available_features12.bufferDeviceAddress == true,
|
|
"bufferDeviceAddress is not supported");
|
|
Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true,
|
|
"vertex layer output is not supported");
|
|
Require("VulkanHarness", "graphics", available_features.fillModeNonSolid &&
|
|
available_depth_clip.depthClipEnable && available_clip_control.depthClipControl &&
|
|
available_color_write.colorWriteEnable &&
|
|
available_feedback_layout.attachmentFeedbackLoopLayout &&
|
|
available_feedback_dynamic.attachmentFeedbackLoopDynamicState &&
|
|
available_provoking_vertex.provokingVertexLast,
|
|
"production rasterization features are not supported");
|
|
|
|
float priority = 1.0f;
|
|
vk::DeviceQueueCreateInfo queue_info{};
|
|
queue_info.sType = vk::StructureType::eDeviceQueueCreateInfo;
|
|
queue_info.queueFamilyIndex = m_queue_family;
|
|
queue_info.queueCount = 1;
|
|
queue_info.pQueuePriorities = &priority;
|
|
|
|
vk::DeviceCreateInfo device_info{};
|
|
device_info.sType = vk::StructureType::eDeviceCreateInfo;
|
|
device_info.queueCreateInfoCount = 1;
|
|
device_info.pQueueCreateInfos = &queue_info;
|
|
vk::PhysicalDeviceVulkan12Features device_features12{};
|
|
device_features12.sType =
|
|
vk::StructureType::ePhysicalDeviceVulkan12Features;
|
|
device_features12.timelineSemaphore = true;
|
|
device_features12.bufferDeviceAddress = true;
|
|
device_features12.shaderOutputLayer = true;
|
|
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
|
|
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
|
barycentric.pNext = &device_features12;
|
|
barycentric.fragmentShaderBarycentric = true;
|
|
vk::PhysicalDeviceVulkan13Features device_features13{};
|
|
device_features13.sType =
|
|
vk::StructureType::ePhysicalDeviceVulkan13Features;
|
|
device_features13.pNext = &barycentric;
|
|
device_features13.dynamicRendering = true;
|
|
device_features13.synchronization2 = true;
|
|
vk::PhysicalDeviceComputeShaderDerivativesFeaturesKHR derivatives{};
|
|
derivatives.pNext = &device_features13;
|
|
derivatives.computeDerivativeGroupQuads = true;
|
|
vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip{};
|
|
depth_clip.pNext = &derivatives;
|
|
depth_clip.depthClipEnable = true;
|
|
vk::PhysicalDeviceDepthClipControlFeaturesEXT clip_control{};
|
|
clip_control.pNext = &depth_clip;
|
|
clip_control.depthClipControl = true;
|
|
vk::PhysicalDeviceColorWriteEnableFeaturesEXT color_write{};
|
|
color_write.pNext = &clip_control;
|
|
color_write.colorWriteEnable = true;
|
|
vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT feedback_layout{};
|
|
feedback_layout.pNext = &color_write;
|
|
feedback_layout.attachmentFeedbackLoopLayout = true;
|
|
vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT feedback_dynamic{};
|
|
feedback_dynamic.pNext = &feedback_layout;
|
|
feedback_dynamic.attachmentFeedbackLoopDynamicState = true;
|
|
vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex{};
|
|
provoking_vertex.pNext = &feedback_dynamic;
|
|
provoking_vertex.provokingVertexLast = available_provoking_vertex.provokingVertexLast;
|
|
device_info.pNext = &provoking_vertex;
|
|
vk::PhysicalDeviceFeatures device_features{};
|
|
device_features.shaderStorageImageWriteWithoutFormat = true;
|
|
device_features.shaderImageGatherExtended = true;
|
|
device_features.sampleRateShading = true;
|
|
device_features.shaderInt64 = true;
|
|
device_features.fillModeNonSolid = true;
|
|
device_info.pEnabledFeatures = &device_features;
|
|
constexpr const char *device_extensions[] = {
|
|
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
|
VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME,
|
|
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
|
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
|
|
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
|
VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME,
|
|
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME,
|
|
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME,
|
|
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME};
|
|
device_info.enabledExtensionCount = std::size(device_extensions);
|
|
device_info.ppEnabledExtensionNames = device_extensions;
|
|
RequireVk("VulkanHarness", "dispatch",
|
|
m_physical_device.createDevice(&device_info, nullptr, &m_device),
|
|
"vkCreateDevice");
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_device);
|
|
m_device.getQueue(m_queue_family, 0, &m_queue);
|
|
|
|
vk::CommandPoolCreateInfo pool_info{};
|
|
pool_info.sType = vk::StructureType::eCommandPoolCreateInfo;
|
|
pool_info.queueFamilyIndex = m_queue_family;
|
|
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
|
RequireVk("VulkanHarness", "dispatch",
|
|
m_device.createCommandPool(&pool_info, nullptr, &m_command_pool),
|
|
"vkCreateCommandPool");
|
|
}
|
|
|
|
void Destroy() {
|
|
if (m_device != nullptr) {
|
|
RequireVulkanSuccess(m_device.waitIdle(), "vkDeviceWaitIdle");
|
|
DestroyBuffer(&m_fault_buffer);
|
|
DestroyBuffer(&m_bda_pagetable_buffer);
|
|
if (m_runtime_context.allocator != nullptr) {
|
|
m_renderer.reset();
|
|
vmaDestroyAllocator(m_runtime_context.allocator);
|
|
m_runtime_context.allocator = nullptr;
|
|
}
|
|
if (m_command_pool != nullptr) {
|
|
m_device.destroyCommandPool(m_command_pool, nullptr);
|
|
}
|
|
m_device.destroy(nullptr);
|
|
}
|
|
if (m_instance != nullptr) {
|
|
m_instance.destroy(nullptr);
|
|
}
|
|
}
|
|
|
|
bool FindMemoryType(u32 type_bits, vk::MemoryPropertyFlags required,
|
|
u32 *index) const {
|
|
for (u32 i = 0; i < m_memory_properties.memoryTypeCount; i++) {
|
|
if ((type_bits & (1u << i)) == 0) {
|
|
continue;
|
|
}
|
|
if ((m_memory_properties.memoryTypes[i].propertyFlags & required) ==
|
|
required) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static vk::AccessFlags AccessForLayout(vk::ImageLayout layout) {
|
|
switch (layout) {
|
|
case vk::ImageLayout::eTransferDstOptimal:
|
|
return vk::AccessFlagBits::eTransferWrite;
|
|
case vk::ImageLayout::eTransferSrcOptimal:
|
|
return vk::AccessFlagBits::eTransferRead;
|
|
case vk::ImageLayout::eShaderReadOnlyOptimal:
|
|
return vk::AccessFlagBits::eShaderRead;
|
|
case vk::ImageLayout::eGeneral:
|
|
return vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
vk::ShaderModule CreateShaderModule(const char *shader_name, const std::vector<u32> &spirv) {
|
|
vk::ShaderModuleCreateInfo info{};
|
|
info.codeSize = spirv.size() * sizeof(u32);
|
|
info.pCode = spirv.data();
|
|
vk::ShaderModule module{};
|
|
RequireVk(shader_name, "graphics", m_device.createShaderModule(&info, nullptr, &module),
|
|
"vkCreateShaderModule");
|
|
return module;
|
|
}
|
|
|
|
vk::CommandBuffer BeginCommands(const char *shader_name, const char *stage) {
|
|
vk::CommandBufferAllocateInfo cmd_alloc{};
|
|
cmd_alloc.sType = vk::StructureType::eCommandBufferAllocateInfo;
|
|
cmd_alloc.commandPool = m_command_pool;
|
|
cmd_alloc.level = vk::CommandBufferLevel::ePrimary;
|
|
cmd_alloc.commandBufferCount = 1;
|
|
vk::CommandBuffer cmd = nullptr;
|
|
RequireVk(shader_name, stage,
|
|
m_device.allocateCommandBuffers(&cmd_alloc, &cmd),
|
|
"vkAllocateCommandBuffers");
|
|
|
|
vk::CommandBufferBeginInfo begin{};
|
|
begin.sType = vk::StructureType::eCommandBufferBeginInfo;
|
|
begin.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
|
RequireVk(shader_name, stage, cmd.begin(&begin), "vkBeginCommandBuffer");
|
|
return cmd;
|
|
}
|
|
|
|
void EndSubmitAndFree(const char *shader_name, const char *stage,
|
|
vk::CommandBuffer cmd) {
|
|
RequireVk(shader_name, stage, cmd.end(), "vkEndCommandBuffer");
|
|
|
|
vk::FenceCreateInfo fence_info{};
|
|
fence_info.sType = vk::StructureType::eFenceCreateInfo;
|
|
vk::Fence fence = nullptr;
|
|
RequireVk(shader_name, stage,
|
|
m_device.createFence(&fence_info, nullptr, &fence),
|
|
"vkCreateFence");
|
|
|
|
vk::SubmitInfo submit{};
|
|
submit.sType = vk::StructureType::eSubmitInfo;
|
|
submit.commandBufferCount = 1;
|
|
submit.pCommandBuffers = &cmd;
|
|
RequireVk(shader_name, stage, m_queue.submit(1, &submit, fence),
|
|
"vkQueueSubmit");
|
|
RequireVk(shader_name, stage,
|
|
m_device.waitForFences(1, &fence, true, UINT64_MAX),
|
|
"vkWaitForFences");
|
|
|
|
m_device.destroyFence(fence, nullptr);
|
|
m_device.freeCommandBuffers(m_command_pool, 1, &cmd);
|
|
}
|
|
|
|
void AddImageBarrier(vk::CommandBuffer cmd, vk::Image image,
|
|
vk::ImageLayout old_layout, vk::ImageLayout new_layout,
|
|
vk::PipelineStageFlags src_stage,
|
|
vk::PipelineStageFlags dst_stage,
|
|
vk::AccessFlags src_access, vk::AccessFlags dst_access,
|
|
u32 mip_levels = 1, u32 layers = 1) {
|
|
vk::ImageMemoryBarrier barrier{};
|
|
barrier.sType = vk::StructureType::eImageMemoryBarrier;
|
|
barrier.srcAccessMask = src_access;
|
|
barrier.dstAccessMask = dst_access;
|
|
barrier.oldLayout = old_layout;
|
|
barrier.newLayout = new_layout;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.image = image;
|
|
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
barrier.subresourceRange.baseMipLevel = 0;
|
|
barrier.subresourceRange.levelCount = mip_levels;
|
|
barrier.subresourceRange.baseArrayLayer = 0;
|
|
barrier.subresourceRange.layerCount = layers;
|
|
cmd.pipelineBarrier(src_stage, dst_stage, {}, 0, nullptr, 0, nullptr, 1,
|
|
&barrier);
|
|
}
|
|
|
|
void TransitionImage(const char *shader_name, Image *image,
|
|
vk::ImageLayout new_layout,
|
|
vk::PipelineStageFlags src_stage,
|
|
vk::PipelineStageFlags dst_stage,
|
|
vk::AccessFlags src_access, vk::AccessFlags dst_access) {
|
|
vk::CommandBuffer cmd = BeginCommands(shader_name, "dispatch");
|
|
AddImageBarrier(cmd, image->image, image->layout, new_layout, src_stage,
|
|
dst_stage, src_access, dst_access, image->mip_levels,
|
|
image->layers);
|
|
EndSubmitAndFree(shader_name, "dispatch", cmd);
|
|
image->layout = new_layout;
|
|
}
|
|
|
|
void UploadImage(const char *shader_name, Image *image, vk::Buffer staging,
|
|
vk::ImageLayout final_layout) {
|
|
UploadImageMips(shader_name, image, staging, final_layout);
|
|
}
|
|
|
|
void UploadImageMips(const char *shader_name, Image *image,
|
|
vk::Buffer staging, vk::ImageLayout final_layout) {
|
|
vk::CommandBuffer cmd = BeginCommands(shader_name, "dispatch");
|
|
AddImageBarrier(cmd, image->image, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eTransferDstOptimal,
|
|
vk::PipelineStageFlagBits::eTopOfPipe,
|
|
vk::PipelineStageFlagBits::eTransfer, {},
|
|
vk::AccessFlagBits::eTransferWrite, image->mip_levels,
|
|
image->layers);
|
|
std::vector<vk::BufferImageCopy> copies;
|
|
copies.reserve(image->mip_levels);
|
|
vk::DeviceSize offset = 0;
|
|
for (u32 level = 0; level < image->mip_levels; level++) {
|
|
vk::BufferImageCopy copy{};
|
|
copy.bufferOffset = offset;
|
|
copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
copy.imageSubresource.mipLevel = level;
|
|
copy.imageSubresource.baseArrayLayer = 0;
|
|
copy.imageSubresource.layerCount = image->layers;
|
|
copy.imageExtent.width = MipExtent(image->width, level);
|
|
copy.imageExtent.height = MipExtent(image->height, level);
|
|
copy.imageExtent.depth = 1;
|
|
copies.push_back(copy);
|
|
offset += static_cast<vk::DeviceSize>(
|
|
ImageMipDwordCount(image->width, image->height,
|
|
image->dwords_per_pixel, level, image->layers) *
|
|
sizeof(u32));
|
|
}
|
|
cmd.copyBufferToImage(staging, image->image,
|
|
vk::ImageLayout::eTransferDstOptimal,
|
|
static_cast<u32>(copies.size()), copies.data());
|
|
AddImageBarrier(cmd, image->image, vk::ImageLayout::eTransferDstOptimal,
|
|
final_layout, vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::AccessFlagBits::eTransferWrite,
|
|
AccessForLayout(final_layout), image->mip_levels,
|
|
image->layers);
|
|
EndSubmitAndFree(shader_name, "dispatch", cmd);
|
|
image->layout = final_layout;
|
|
}
|
|
|
|
Buffer CreateDeviceBuffer(const char *shader_name, vk::DeviceSize size,
|
|
vk::BufferUsageFlags usage) {
|
|
Buffer ret;
|
|
ret.size = size;
|
|
vk::BufferCreateInfo buffer_info{};
|
|
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
|
|
buffer_info.size = size;
|
|
buffer_info.usage = usage;
|
|
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createBuffer(&buffer_info, nullptr, &ret.buffer),
|
|
"vkCreateBuffer");
|
|
|
|
vk::MemoryRequirements requirements{};
|
|
m_device.getBufferMemoryRequirements(ret.buffer, &requirements);
|
|
u32 memory_type = 0;
|
|
Require(shader_name, "dispatch",
|
|
FindMemoryType(requirements.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eDeviceLocal,
|
|
&memory_type) ||
|
|
FindMemoryType(requirements.memoryTypeBits, {}, &memory_type),
|
|
"no memory type for BDA support buffer");
|
|
vk::MemoryAllocateInfo allocation{};
|
|
allocation.sType = vk::StructureType::eMemoryAllocateInfo;
|
|
allocation.allocationSize = requirements.size;
|
|
allocation.memoryTypeIndex = memory_type;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.allocateMemory(&allocation, nullptr, &ret.memory),
|
|
"vkAllocateMemory");
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.bindBufferMemory(ret.buffer, ret.memory, 0),
|
|
"vkBindBufferMemory");
|
|
return ret;
|
|
}
|
|
|
|
void EnsureBdaBuffers(const char *shader_name) {
|
|
if (m_bda_pagetable_buffer.buffer != nullptr) {
|
|
return;
|
|
}
|
|
const auto usage = vk::BufferUsageFlagBits::eStorageBuffer |
|
|
vk::BufferUsageFlagBits::eTransferDst;
|
|
m_bda_pagetable_buffer =
|
|
CreateDeviceBuffer(shader_name, BufferCache::BDA_PAGETABLE_SIZE, usage);
|
|
m_fault_buffer = CreateDeviceBuffer(
|
|
shader_name, BufferCache::CACHING_NUMPAGES / 8, usage);
|
|
}
|
|
|
|
Buffer CreateHostBuffer(const char *shader_name, vk::DeviceSize size,
|
|
vk::BufferUsageFlags usage,
|
|
const std::vector<u32> &contents) {
|
|
Buffer ret;
|
|
ret.size = std::max<vk::DeviceSize>(size, sizeof(u32));
|
|
|
|
vk::BufferCreateInfo buffer_info{};
|
|
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
|
|
buffer_info.size = ret.size;
|
|
buffer_info.usage = usage;
|
|
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.createBuffer(&buffer_info, nullptr, &ret.buffer),
|
|
"vkCreateBuffer");
|
|
|
|
vk::MemoryRequirements req{};
|
|
m_device.getBufferMemoryRequirements(ret.buffer, &req);
|
|
u32 memory_type = 0;
|
|
ret.coherent = FindMemoryType(req.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
|
vk::MemoryPropertyFlagBits::eHostCoherent,
|
|
&memory_type);
|
|
if (!ret.coherent) {
|
|
Require(shader_name, "dispatch",
|
|
FindMemoryType(req.memoryTypeBits,
|
|
vk::MemoryPropertyFlagBits::eHostVisible,
|
|
&memory_type),
|
|
"no host-visible memory type for staging buffer");
|
|
}
|
|
|
|
vk::MemoryAllocateInfo alloc{};
|
|
alloc.sType = vk::StructureType::eMemoryAllocateInfo;
|
|
alloc.allocationSize = req.size;
|
|
alloc.memoryTypeIndex = memory_type;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.allocateMemory(&alloc, nullptr, &ret.memory),
|
|
"vkAllocateMemory");
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.bindBufferMemory(ret.buffer, ret.memory, 0),
|
|
"vkBindBufferMemory");
|
|
if (!contents.empty()) {
|
|
WriteBuffer(shader_name, ret, contents);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void WriteBuffer(const char *shader_name, const Buffer &buffer,
|
|
const std::vector<u32> &contents) {
|
|
void *data = nullptr;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.mapMemory(buffer.memory, 0, buffer.size, {}, &data),
|
|
"vkMapMemory");
|
|
std::memcpy(data, contents.data(), contents.size() * sizeof(u32));
|
|
if (!buffer.coherent) {
|
|
vk::MappedMemoryRange range{};
|
|
range.sType = vk::StructureType::eMappedMemoryRange;
|
|
range.memory = buffer.memory;
|
|
range.offset = 0;
|
|
range.size = VK_WHOLE_SIZE;
|
|
RequireVk(shader_name, "dispatch",
|
|
m_device.flushMappedMemoryRanges(1, &range),
|
|
"vkFlushMappedMemoryRanges");
|
|
}
|
|
m_device.unmapMemory(buffer.memory);
|
|
}
|
|
|
|
vk::Instance m_instance = nullptr;
|
|
vk::PhysicalDevice m_physical_device = nullptr;
|
|
vk::Device m_device = nullptr;
|
|
vk::Queue m_queue = nullptr;
|
|
vk::CommandPool m_command_pool = nullptr;
|
|
u32 m_queue_family = 0;
|
|
vk::PhysicalDeviceMemoryProperties m_memory_properties{};
|
|
Buffer m_bda_pagetable_buffer;
|
|
Buffer m_fault_buffer;
|
|
GraphicContext m_runtime_context{};
|
|
std::unique_ptr<RenderContext> m_renderer;
|
|
};
|
|
|
|
void CompareWords(const TestCase &test, const char *stage,
|
|
const std::vector<u32> &expected,
|
|
const std::vector<u32> &actual) {
|
|
if (actual == expected) {
|
|
return;
|
|
}
|
|
std::ostringstream out;
|
|
out << "expected [";
|
|
for (size_t i = 0; i < expected.size(); i++) {
|
|
out << (i == 0 ? "" : ", ") << Hex(expected[i]);
|
|
}
|
|
out << "] actual [";
|
|
for (size_t i = 0; i < actual.size(); i++) {
|
|
out << (i == 0 ? "" : ", ") << Hex(actual[i]);
|
|
}
|
|
out << "]";
|
|
Fail(test.name, stage, out.str());
|
|
}
|
|
|
|
void CompareGraphicsWords(const GraphicsCase &test,
|
|
const std::vector<u32> &actual) {
|
|
if (actual == test.expected_pixel) {
|
|
return;
|
|
}
|
|
std::ostringstream out;
|
|
out << "expected [";
|
|
for (size_t i = 0; i < test.expected_pixel.size(); i++) {
|
|
out << (i == 0 ? "" : ", ") << Hex(test.expected_pixel[i]);
|
|
}
|
|
out << "] actual [";
|
|
for (size_t i = 0; i < actual.size(); i++) {
|
|
out << (i == 0 ? "" : ", ") << Hex(actual[i]);
|
|
}
|
|
out << "]";
|
|
Fail(test.name, "graphics readback", out.str());
|
|
}
|
|
|
|
void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
|
auto compiled = CompileCase(test, vulkan != nullptr ? vulkan->SubgroupSize() : 64u);
|
|
if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7) &&
|
|
!compiled.program.info.images.empty()) {
|
|
Require(test.name, "resource specialization",
|
|
compiled.program.info.images[0].shader_swizzle ==
|
|
test.image_descriptor_swizzle,
|
|
"storage image descriptor swizzle did not reach the specialized "
|
|
"program");
|
|
}
|
|
if (test.expected_force_point_sampler) {
|
|
Require(test.name, "sampler specialization",
|
|
compiled.program.info.samplers.size() == 1u &&
|
|
compiled.program.info.samplers[0].force_point_filtering,
|
|
"bit-packed sampled image did not force point filtering");
|
|
}
|
|
if (test.compile_only) {
|
|
std::printf("[compute] %-32s ok\n", test.name);
|
|
return;
|
|
}
|
|
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
|
|
auto Has = [&](Kind kind) {
|
|
return ShaderRecompiler::IR::FindBinding(compiled.program.bindings, kind) !=
|
|
nullptr;
|
|
};
|
|
const auto dwords = std::max<size_t>(
|
|
{test.initial.size(), test.expected.size(), static_cast<size_t>(1)});
|
|
auto buffer = vulkan->CreateStorageBuffer(
|
|
test.name, test.initial, dwords, Has(Kind::BdaPagetable));
|
|
VulkanHarness::Image sampled_image;
|
|
VulkanHarness::Image storage_image;
|
|
VulkanHarness::Image storage_image_uint;
|
|
VulkanHarness::Buffer gds_buffer;
|
|
vk::Sampler sampler = nullptr;
|
|
bool needs_sampled_image = false;
|
|
bool needs_storage_image = false;
|
|
for (const auto &binding : compiled.program.bindings.descriptors) {
|
|
const auto resource_class =
|
|
ShaderRecompiler::IR::ImageBindingResourceClass(binding.kind);
|
|
if (resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
|
needs_sampled_image = true;
|
|
} else if (resource_class ==
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage) {
|
|
needs_storage_image = true;
|
|
}
|
|
}
|
|
const bool needs_sampler = Has(Kind::Samplers);
|
|
const bool needs_gds = Has(Kind::Gds);
|
|
if (needs_gds) {
|
|
const auto gds_dwords = std::max<size_t>(
|
|
{test.gds_initial.size(), test.expected_gds.size(), 1u});
|
|
gds_buffer =
|
|
vulkan->CreateStorageBuffer(test.name, test.gds_initial, gds_dwords);
|
|
}
|
|
if (needs_sampled_image) {
|
|
auto sampled_mips = test.sampled_image_rgba_mips;
|
|
auto sampled_format = test.sampled_image_format;
|
|
auto sampled_dwords_per_pixel = test.sampled_image_dwords_per_pixel;
|
|
if (!test.sampled_image_rgba_mips.empty()) {
|
|
sampled_format = vk::Format::eR32G32B32A32Sfloat;
|
|
sampled_dwords_per_pixel = 4;
|
|
} else if (!test.sampled_image_rgba.empty()) {
|
|
sampled_mips.push_back(test.sampled_image_rgba);
|
|
}
|
|
sampled_image = vulkan->CreateImageMips(
|
|
test.name, test.image_width, test.image_height, sampled_format,
|
|
vk::ImageUsageFlagBits::eSampled, sampled_mips,
|
|
sampled_dwords_per_pixel, vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
test.sampled_image_type, test.sampled_image_view_type,
|
|
test.sampled_image_layers, test.sampled_image_view_base_layer,
|
|
test.sampled_image_view_layers);
|
|
}
|
|
if (needs_storage_image) {
|
|
storage_image = vulkan->CreateImage2D(
|
|
test.name, test.image_width, test.image_height,
|
|
test.storage_image_format, vk::ImageUsageFlagBits::eStorage,
|
|
test.storage_image_rgba, test.storage_image_dwords_per_pixel,
|
|
vk::ImageLayout::eGeneral);
|
|
storage_image_uint = vulkan->CreateImage2D(
|
|
test.name, test.image_width, test.image_height, vk::Format::eR32Uint,
|
|
vk::ImageUsageFlagBits::eStorage, test.storage_image_r32ui, 1,
|
|
vk::ImageLayout::eGeneral);
|
|
}
|
|
if (needs_sampler) {
|
|
sampler = vulkan->CreateNearestSampler(test.name);
|
|
}
|
|
|
|
vulkan->Dispatch(test, compiled, buffer, needs_gds ? &gds_buffer : nullptr,
|
|
needs_sampled_image ? &sampled_image : nullptr,
|
|
needs_storage_image ? &storage_image : nullptr,
|
|
needs_storage_image ? &storage_image_uint : nullptr,
|
|
sampler);
|
|
auto actual = vulkan->ReadBuffer(test.name, buffer, test.expected.size());
|
|
if (!test.expected_gds.empty()) {
|
|
const auto gds_actual =
|
|
vulkan->ReadBuffer(test.name, gds_buffer, test.expected_gds.size());
|
|
CompareWords(test, "GDS readback", test.expected_gds, gds_actual);
|
|
}
|
|
if (!test.expected_storage_image_rgba.empty()) {
|
|
auto image_actual = vulkan->ReadImage(test.name, &storage_image);
|
|
image_actual.resize(test.expected_storage_image_rgba.size());
|
|
CompareWords(test, "storage image readback",
|
|
test.expected_storage_image_rgba, image_actual);
|
|
}
|
|
if (!test.expected_storage_image_r32ui.empty()) {
|
|
auto image_actual = vulkan->ReadImage(test.name, &storage_image_uint);
|
|
image_actual.resize(test.expected_storage_image_r32ui.size());
|
|
CompareWords(test, "uint storage image readback",
|
|
test.expected_storage_image_r32ui, image_actual);
|
|
}
|
|
if (sampler != nullptr) {
|
|
vulkan->Device().destroySampler(sampler, nullptr);
|
|
}
|
|
vulkan->DestroyImage(&sampled_image);
|
|
vulkan->DestroyImage(&storage_image);
|
|
vulkan->DestroyImage(&storage_image_uint);
|
|
vulkan->DestroyBuffer(&gds_buffer);
|
|
vulkan->DestroyBuffer(&buffer);
|
|
CompareWords(test, "readback", test.expected, actual);
|
|
std::printf("[compute] %-32s ok\n", test.name);
|
|
}
|
|
|
|
void RunGraphicsCase(VulkanHarness *vulkan, const GraphicsCase &test) {
|
|
auto compiled = CompileFragmentCase(test);
|
|
auto actual = vulkan->RenderFragment(test, compiled);
|
|
CompareGraphicsWords(test, actual);
|
|
std::printf("[graphics] %-31s ok\n", test.name);
|
|
}
|
|
|
|
enum class CoverageClass {
|
|
Covered,
|
|
ControlOrMarker,
|
|
NeedsAluCase,
|
|
NeedsFloatCase,
|
|
NeedsMemoryCase,
|
|
NeedsImageCase,
|
|
NeedsGraphicsStageCase,
|
|
};
|
|
|
|
bool IsCovered(const std::set<ShaderOpcode> &covered, ShaderOpcode opcode) {
|
|
return covered.find(opcode) != covered.end();
|
|
}
|
|
|
|
CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
|
const std::set<ShaderOpcode> &covered) {
|
|
using ShaderRecompiler::Decoder::Opcode;
|
|
|
|
if (IsCovered(covered, opcode)) {
|
|
return CoverageClass::Covered;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case Opcode::S_GETPC_B64:
|
|
case Opcode::S_SETPC_B64:
|
|
case Opcode::S_NOP:
|
|
case Opcode::S_WAITCNT:
|
|
case Opcode::S_BARRIER:
|
|
case Opcode::S_BRANCH:
|
|
case Opcode::S_CBRANCH_SCC0:
|
|
case Opcode::S_CBRANCH_SCC1:
|
|
case Opcode::S_CBRANCH_VCCZ:
|
|
case Opcode::S_CBRANCH_VCCNZ:
|
|
case Opcode::S_CBRANCH_EXECZ:
|
|
case Opcode::S_CBRANCH_EXECNZ:
|
|
case Opcode::S_SENDMSG:
|
|
case Opcode::S_SETREG_B32:
|
|
case Opcode::S_SLEEP:
|
|
case Opcode::S_TTRACEDATA:
|
|
case Opcode::S_INST_PREFETCH:
|
|
case Opcode::S_ENDPGM:
|
|
return CoverageClass::ControlOrMarker;
|
|
|
|
case Opcode::V_ADD_F32:
|
|
case Opcode::V_SUB_F32:
|
|
case Opcode::V_SUBREV_F32:
|
|
case Opcode::V_MUL_F32:
|
|
case Opcode::V_MAC_F32:
|
|
case Opcode::V_MADMK_F32:
|
|
case Opcode::V_MADAK_F32:
|
|
case Opcode::V_MIN_F32:
|
|
case Opcode::V_MAX_F32:
|
|
case Opcode::V_MAD_F32:
|
|
case Opcode::V_FMA_F32:
|
|
case Opcode::V_MIN3_F32:
|
|
case Opcode::V_MAX3_F32:
|
|
case Opcode::V_MED3_F32:
|
|
case Opcode::V_DOT2C_F32_F16:
|
|
case Opcode::V_CVT_F32_I32:
|
|
case Opcode::V_CVT_F32_U32:
|
|
case Opcode::V_CVT_U32_F32:
|
|
case Opcode::V_CVT_I32_F32:
|
|
case Opcode::V_CVT_F16_F32:
|
|
case Opcode::V_CVT_F32_F16:
|
|
case Opcode::V_CVT_U16_F16:
|
|
case Opcode::V_CVT_RPI_I32_F32:
|
|
case Opcode::V_CVT_FLR_I32_F32:
|
|
case Opcode::V_CVT_OFF_F32_I4:
|
|
case Opcode::V_CVT_F32_UBYTE0:
|
|
case Opcode::V_CVT_F32_UBYTE1:
|
|
case Opcode::V_CVT_F32_UBYTE2:
|
|
case Opcode::V_CVT_F32_UBYTE3:
|
|
case Opcode::V_RCP_F32:
|
|
case Opcode::V_FRACT_F32:
|
|
case Opcode::V_TRUNC_F32:
|
|
case Opcode::V_CEIL_F32:
|
|
case Opcode::V_RNDNE_F32:
|
|
case Opcode::V_FLOOR_F32:
|
|
case Opcode::V_EXP_F32:
|
|
case Opcode::V_LOG_F32:
|
|
case Opcode::V_RSQ_F32:
|
|
case Opcode::V_SQRT_F32:
|
|
case Opcode::V_SIN_F32:
|
|
case Opcode::V_COS_F32:
|
|
case Opcode::V_CUBEID_F32:
|
|
case Opcode::V_CUBESC_F32:
|
|
case Opcode::V_CUBETC_F32:
|
|
case Opcode::V_CUBEMA_F32:
|
|
case Opcode::V_LDEXP_F32:
|
|
case Opcode::V_CVT_PK_U8_F32:
|
|
case Opcode::V_CVT_PKNORM_I16_F32:
|
|
case Opcode::V_CVT_PKNORM_U16_F32:
|
|
case Opcode::V_CVT_PKRTZ_F16_F32:
|
|
case Opcode::V_PK_ADD_F16:
|
|
case Opcode::V_PK_MUL_F16:
|
|
case Opcode::V_PK_MIN_F16:
|
|
case Opcode::V_PK_MAX_F16:
|
|
case Opcode::V_PK_FMA_F16:
|
|
case Opcode::V_ADD_F16:
|
|
case Opcode::V_SUB_F16:
|
|
case Opcode::V_SUBREV_F16:
|
|
case Opcode::V_MUL_F16:
|
|
case Opcode::V_MAX_F16:
|
|
case Opcode::V_MIN_F16:
|
|
case Opcode::V_MIN3_F16:
|
|
case Opcode::V_MAX3_F16:
|
|
case Opcode::V_MED3_F16:
|
|
case Opcode::V_RCP_F16:
|
|
case Opcode::V_RSQ_F16:
|
|
case Opcode::V_LOG_F16:
|
|
case Opcode::V_EXP_F16:
|
|
case Opcode::V_SIN_F16:
|
|
case Opcode::V_COS_F16:
|
|
case Opcode::V_MAD_MIXLO_F16:
|
|
case Opcode::V_MAD_MIXHI_F16:
|
|
case Opcode::DS_MIN_F32:
|
|
case Opcode::DS_MAX_F32:
|
|
case Opcode::V_CMP_F_F32:
|
|
case Opcode::V_CMP_LT_F32:
|
|
case Opcode::V_CMP_EQ_F32:
|
|
case Opcode::V_CMP_LE_F32:
|
|
case Opcode::V_CMP_GT_F32:
|
|
case Opcode::V_CMP_LG_F32:
|
|
case Opcode::V_CMP_GE_F32:
|
|
case Opcode::V_CMP_O_F32:
|
|
case Opcode::V_CMP_U_F32:
|
|
case Opcode::V_CMP_NGE_F32:
|
|
case Opcode::V_CMP_NLG_F32:
|
|
case Opcode::V_CMP_NGT_F32:
|
|
case Opcode::V_CMP_NLE_F32:
|
|
case Opcode::V_CMP_NEQ_F32:
|
|
case Opcode::V_CMP_NLT_F32:
|
|
case Opcode::V_CMP_TRU_F32:
|
|
case Opcode::V_CMPX_LT_F32:
|
|
case Opcode::V_CMPX_EQ_F32:
|
|
case Opcode::V_CMPX_LE_F32:
|
|
case Opcode::V_CMPX_GT_F32:
|
|
case Opcode::V_CMPX_LG_F32:
|
|
case Opcode::V_CMPX_GE_F32:
|
|
case Opcode::V_CMPX_NGE_F32:
|
|
case Opcode::V_CMPX_NLG_F32:
|
|
case Opcode::V_CMPX_NGT_F32:
|
|
case Opcode::V_CMPX_NLE_F32:
|
|
case Opcode::V_CMPX_NEQ_F32:
|
|
case Opcode::V_CMPX_NLT_F32:
|
|
case Opcode::V_CMP_CLASS_F32:
|
|
case Opcode::V_CMPX_CLASS_F32:
|
|
case Opcode::V_CMP_LT_F16:
|
|
case Opcode::V_CMP_EQ_F16:
|
|
case Opcode::V_CMP_LE_F16:
|
|
case Opcode::V_CMP_GT_F16:
|
|
case Opcode::V_CMP_LG_F16:
|
|
case Opcode::V_CMP_GE_F16:
|
|
case Opcode::V_CMP_NEQ_F16:
|
|
case Opcode::V_CMPX_LT_F16:
|
|
case Opcode::V_CMPX_EQ_F16:
|
|
case Opcode::V_CMPX_LE_F16:
|
|
case Opcode::V_CMPX_GT_F16:
|
|
case Opcode::V_CMPX_GE_F16:
|
|
case Opcode::V_CMPX_NEQ_F16:
|
|
case Opcode::V_CMPX_NLT_F16:
|
|
return CoverageClass::NeedsFloatCase;
|
|
|
|
case Opcode::S_LOAD_DWORD:
|
|
case Opcode::S_LOAD_DWORDX2:
|
|
case Opcode::S_LOAD_DWORDX4:
|
|
case Opcode::S_LOAD_DWORDX8:
|
|
case Opcode::S_LOAD_DWORDX16:
|
|
case Opcode::S_BUFFER_LOAD_DWORD:
|
|
case Opcode::S_BUFFER_LOAD_DWORDX2:
|
|
case Opcode::S_BUFFER_LOAD_DWORDX4:
|
|
case Opcode::S_BUFFER_LOAD_DWORDX8:
|
|
case Opcode::S_BUFFER_LOAD_DWORDX16:
|
|
case Opcode::BUFFER_LOAD_FORMAT_X:
|
|
case Opcode::BUFFER_LOAD_FORMAT_XY:
|
|
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
|
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
|
case Opcode::BUFFER_STORE_FORMAT_X:
|
|
case Opcode::BUFFER_STORE_FORMAT_XY:
|
|
case Opcode::BUFFER_STORE_FORMAT_XYZ:
|
|
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
|
case Opcode::BUFFER_LOAD_UBYTE:
|
|
case Opcode::BUFFER_LOAD_SBYTE:
|
|
case Opcode::BUFFER_LOAD_USHORT:
|
|
case Opcode::BUFFER_LOAD_SSHORT:
|
|
case Opcode::BUFFER_LOAD_DWORDX2:
|
|
case Opcode::BUFFER_LOAD_DWORDX3:
|
|
case Opcode::BUFFER_LOAD_DWORDX4:
|
|
case Opcode::BUFFER_STORE_BYTE:
|
|
case Opcode::BUFFER_STORE_SHORT:
|
|
case Opcode::BUFFER_STORE_DWORDX2:
|
|
case Opcode::BUFFER_STORE_DWORDX3:
|
|
case Opcode::BUFFER_STORE_DWORDX4:
|
|
case Opcode::TBUFFER_LOAD_FORMAT_X:
|
|
case Opcode::TBUFFER_LOAD_FORMAT_XY:
|
|
case Opcode::TBUFFER_LOAD_FORMAT_XYZ:
|
|
case Opcode::TBUFFER_LOAD_FORMAT_XYZW:
|
|
case Opcode::TBUFFER_STORE_FORMAT_X:
|
|
case Opcode::TBUFFER_STORE_FORMAT_XY:
|
|
case Opcode::TBUFFER_STORE_FORMAT_XYZ:
|
|
case Opcode::TBUFFER_STORE_FORMAT_XYZW:
|
|
case Opcode::BUFFER_ATOMIC_SWAP:
|
|
case Opcode::BUFFER_ATOMIC_CMPSWAP:
|
|
case Opcode::BUFFER_ATOMIC_ADD:
|
|
case Opcode::BUFFER_ATOMIC_SUB:
|
|
case Opcode::BUFFER_ATOMIC_SMIN:
|
|
case Opcode::BUFFER_ATOMIC_UMIN:
|
|
case Opcode::BUFFER_ATOMIC_SMAX:
|
|
case Opcode::BUFFER_ATOMIC_UMAX:
|
|
case Opcode::BUFFER_ATOMIC_AND:
|
|
case Opcode::BUFFER_ATOMIC_OR:
|
|
case Opcode::BUFFER_ATOMIC_XOR:
|
|
case Opcode::BUFFER_ATOMIC_FMIN:
|
|
case Opcode::BUFFER_ATOMIC_FMAX:
|
|
case Opcode::FLAT_LOAD_UBYTE:
|
|
case Opcode::FLAT_LOAD_SBYTE:
|
|
case Opcode::FLAT_LOAD_USHORT:
|
|
case Opcode::FLAT_LOAD_SSHORT:
|
|
case Opcode::FLAT_LOAD_DWORD:
|
|
case Opcode::FLAT_LOAD_DWORDX2:
|
|
case Opcode::FLAT_LOAD_DWORDX3:
|
|
case Opcode::FLAT_LOAD_DWORDX4:
|
|
case Opcode::FLAT_STORE_BYTE:
|
|
case Opcode::FLAT_STORE_SHORT:
|
|
case Opcode::FLAT_STORE_DWORD:
|
|
case Opcode::FLAT_STORE_DWORDX2:
|
|
case Opcode::FLAT_STORE_DWORDX3:
|
|
case Opcode::FLAT_STORE_DWORDX4:
|
|
case Opcode::DS_ADD_U32:
|
|
case Opcode::DS_ADD_RTN_U32:
|
|
case Opcode::DS_SUB_U32:
|
|
case Opcode::DS_SUB_RTN_U32:
|
|
case Opcode::DS_INC_RTN_U32:
|
|
case Opcode::DS_DEC_RTN_U32:
|
|
case Opcode::DS_MIN_I32:
|
|
case Opcode::DS_MIN_RTN_I32:
|
|
case Opcode::DS_MAX_I32:
|
|
case Opcode::DS_MAX_RTN_I32:
|
|
case Opcode::DS_MIN_U32:
|
|
case Opcode::DS_MIN_RTN_U32:
|
|
case Opcode::DS_MAX_U32:
|
|
case Opcode::DS_MAX_RTN_U32:
|
|
case Opcode::DS_AND_B32:
|
|
case Opcode::DS_AND_RTN_B32:
|
|
case Opcode::DS_OR_B32:
|
|
case Opcode::DS_OR_RTN_B32:
|
|
case Opcode::DS_XOR_B32:
|
|
case Opcode::DS_XOR_RTN_B32:
|
|
case Opcode::DS_WRXCHG_RTN_B32:
|
|
case Opcode::DS_SWIZZLE_B32:
|
|
case Opcode::DS_BPERMUTE_B32:
|
|
case Opcode::DS_READ_I8:
|
|
case Opcode::DS_READ_U8:
|
|
case Opcode::DS_READ_I16:
|
|
case Opcode::DS_READ_U16:
|
|
case Opcode::DS_READ_U16_D16:
|
|
case Opcode::DS_READ_U16_D16_HI:
|
|
case Opcode::DS_READ2_B32:
|
|
case Opcode::DS_READ_B32:
|
|
case Opcode::DS_READ_B64:
|
|
case Opcode::DS_READ2_B64:
|
|
case Opcode::DS_READ_B96:
|
|
case Opcode::DS_READ_B128:
|
|
case Opcode::DS_WRITE_B8:
|
|
case Opcode::DS_WRITE_B16:
|
|
case Opcode::DS_WRITE_B16_D16_HI:
|
|
case Opcode::DS_WRITE2_B32:
|
|
case Opcode::DS_WRITE2ST64_B32:
|
|
case Opcode::DS_WRITE_B32:
|
|
case Opcode::DS_WRITE_B64:
|
|
case Opcode::DS_WRITE_B96:
|
|
case Opcode::DS_WRITE_B128:
|
|
case Opcode::DS_WRITE_ADDTID_B32:
|
|
case Opcode::DS_READ_ADDTID_B32:
|
|
return CoverageClass::NeedsMemoryCase;
|
|
|
|
case Opcode::IMAGE_GET_RESINFO:
|
|
case Opcode::IMAGE_GET_LOD:
|
|
case Opcode::IMAGE_LOAD:
|
|
case Opcode::IMAGE_LOAD_MIP:
|
|
case Opcode::IMAGE_STORE:
|
|
case Opcode::IMAGE_STORE_MIP:
|
|
case Opcode::IMAGE_ATOMIC_SWAP:
|
|
case Opcode::IMAGE_ATOMIC_ADD:
|
|
case Opcode::IMAGE_ATOMIC_UMIN:
|
|
case Opcode::IMAGE_ATOMIC_AND:
|
|
case Opcode::IMAGE_ATOMIC_OR:
|
|
case Opcode::IMAGE_ATOMIC_XOR:
|
|
case Opcode::IMAGE_SAMPLE:
|
|
case Opcode::IMAGE_GATHER4_LZ:
|
|
case Opcode::IMAGE_GATHER4_C:
|
|
case Opcode::IMAGE_GATHER4_C_LZ:
|
|
case Opcode::IMAGE_GATHER4_LZ_O:
|
|
case Opcode::IMAGE_GATHER4_C_O:
|
|
case Opcode::IMAGE_GATHER4_C_LZ_O:
|
|
return CoverageClass::NeedsImageCase;
|
|
|
|
case Opcode::V_INTERP_P1_F32:
|
|
case Opcode::V_INTERP_P2_F32:
|
|
case Opcode::V_INTERP_MOV_F32:
|
|
case Opcode::EXP:
|
|
return CoverageClass::NeedsGraphicsStageCase;
|
|
|
|
default:
|
|
return CoverageClass::NeedsAluCase;
|
|
}
|
|
}
|
|
|
|
const char *CoverageClassName(CoverageClass status) {
|
|
switch (status) {
|
|
case CoverageClass::Covered:
|
|
return "covered";
|
|
case CoverageClass::ControlOrMarker:
|
|
return "control";
|
|
case CoverageClass::NeedsAluCase:
|
|
return "alu";
|
|
case CoverageClass::NeedsFloatCase:
|
|
return "float";
|
|
case CoverageClass::NeedsMemoryCase:
|
|
return "memory";
|
|
case CoverageClass::NeedsImageCase:
|
|
return "image";
|
|
case CoverageClass::NeedsGraphicsStageCase:
|
|
return "graphics";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void PrintPendingOpcodes(CoverageClass status,
|
|
const std::vector<ShaderOpcode> &opcodes) {
|
|
if (opcodes.empty()) {
|
|
return;
|
|
}
|
|
|
|
std::printf("[coverage] pending_%s:", CoverageClassName(status));
|
|
for (auto opcode : opcodes) {
|
|
const auto name = magic_enum::enum_name(opcode);
|
|
std::printf(" %.*s", static_cast<int>(name.size()), name.data());
|
|
}
|
|
std::printf("\n");
|
|
}
|
|
|
|
void CheckOpcodeCoverage(const std::vector<TestCase> &tests,
|
|
const std::vector<GraphicsCase> &graphics_tests) {
|
|
using ShaderRecompiler::Decoder::Opcode;
|
|
|
|
std::set<Opcode> covered;
|
|
for (const auto &test : tests) {
|
|
for (auto opcode : test.opcodes) {
|
|
covered.insert(opcode);
|
|
}
|
|
}
|
|
for (const auto &test : graphics_tests) {
|
|
for (auto opcode : test.opcodes) {
|
|
covered.insert(opcode);
|
|
}
|
|
}
|
|
|
|
uint32_t counts[7] = {};
|
|
std::vector<ShaderOpcode> pending[7];
|
|
for (auto value = static_cast<int>(Opcode::S_MOV_B32);
|
|
value < static_cast<int>(Opcode::COUNT); value++) {
|
|
const auto opcode = static_cast<Opcode>(value);
|
|
const auto status = ClassifyOpcode(opcode, covered);
|
|
counts[static_cast<uint32_t>(status)]++;
|
|
if (status != CoverageClass::Covered &&
|
|
status != CoverageClass::ControlOrMarker) {
|
|
pending[static_cast<uint32_t>(status)].push_back(opcode);
|
|
}
|
|
}
|
|
|
|
std::printf(
|
|
"[coverage] decoder opcodes: covered=%u control=%u alu_pending=%u "
|
|
"float_pending=%u memory_pending=%u image_pending=%u "
|
|
"graphics_pending=%u\n",
|
|
counts[static_cast<uint32_t>(CoverageClass::Covered)],
|
|
counts[static_cast<uint32_t>(CoverageClass::ControlOrMarker)],
|
|
counts[static_cast<uint32_t>(CoverageClass::NeedsAluCase)],
|
|
counts[static_cast<uint32_t>(CoverageClass::NeedsFloatCase)],
|
|
counts[static_cast<uint32_t>(CoverageClass::NeedsMemoryCase)],
|
|
counts[static_cast<uint32_t>(CoverageClass::NeedsImageCase)],
|
|
counts[static_cast<uint32_t>(CoverageClass::NeedsGraphicsStageCase)]);
|
|
PrintPendingOpcodes(
|
|
CoverageClass::NeedsAluCase,
|
|
pending[static_cast<uint32_t>(CoverageClass::NeedsAluCase)]);
|
|
PrintPendingOpcodes(
|
|
CoverageClass::NeedsFloatCase,
|
|
pending[static_cast<uint32_t>(CoverageClass::NeedsFloatCase)]);
|
|
PrintPendingOpcodes(
|
|
CoverageClass::NeedsMemoryCase,
|
|
pending[static_cast<uint32_t>(CoverageClass::NeedsMemoryCase)]);
|
|
PrintPendingOpcodes(
|
|
CoverageClass::NeedsImageCase,
|
|
pending[static_cast<uint32_t>(CoverageClass::NeedsImageCase)]);
|
|
PrintPendingOpcodes(
|
|
CoverageClass::NeedsGraphicsStageCase,
|
|
pending[static_cast<uint32_t>(CoverageClass::NeedsGraphicsStageCase)]);
|
|
}
|
|
|
|
TestCase IntegerAddSubMul() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeSMovB32(0, InlineU32(7)),
|
|
EncodeSMovB32(1, InlineU32(3)),
|
|
EncodeSop2(0x00, 2, 0, 1),
|
|
EncodeSop2(0x01, 3, 2, InlineU32(1)),
|
|
EncodeSop2(0x26, 4, 3, InlineU32(2)),
|
|
EncodeVop1(0x01, 0, 4),
|
|
};
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"IntegerAddSubMul",
|
|
code,
|
|
{},
|
|
{18},
|
|
{O::S_MOV_B32, O::S_ADD_U32, O::S_SUB_U32, O::S_MUL_I32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BitwiseOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeSMovB32(0, InlineU32(60)), EncodeSMovB32(1, InlineU32(15)),
|
|
EncodeSop2(0x0e, 2, 0, 1), EncodeSop2(0x10, 3, 0, 1),
|
|
EncodeSop2(0x12, 4, 3, 2), EncodeVop1(0x37, 0, 4),
|
|
};
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"BitwiseAndOrXorNot",
|
|
code,
|
|
{},
|
|
{~0x33u},
|
|
{O::S_MOV_B32, O::S_AND_B32, O::S_OR_B32, O::S_XOR_B32, O::V_NOT_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Shifts() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeSMovB32(0, InlineU32(3)),
|
|
EncodeSop2(0x1e, 1, 0, InlineU32(2)),
|
|
EncodeSop2(0x20, 2, 1, InlineU32(1)),
|
|
EncodeVop1(0x01, 0, 2),
|
|
};
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"Shifts",
|
|
code,
|
|
{},
|
|
{6},
|
|
{O::S_MOV_B32, O::S_LSHL_B32, O::S_LSHR_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ExactPushConstantExtent() {
|
|
using O = ShaderOpcode;
|
|
|
|
constexpr std::array<u32, 10> values{
|
|
0x10203040u, 0x21314151u, 0x32425262u, 0x43536373u, 0x54647484u,
|
|
0x65758595u, 0x768696a6u, 0x8797a7b7u, 0x98a8b8c8u, 0xa9b9c9d9u};
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < values.size(); i++) {
|
|
AppendStoreSgpr(&code, 4u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ExactPushConstantExtent";
|
|
test.code = std::move(code);
|
|
test.initial.resize(values.size());
|
|
test.expected.assign(values.begin(), values.end());
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data =
|
|
MakeStructuredStorageBufferData(0, values.size() * sizeof(u32));
|
|
std::copy(values.begin(), values.end(), test.user_data.begin() + 4);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarShiftCountsMaskLowBits() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(1)));
|
|
AppendSMovLiteral(&code, 1, 0x80000000u);
|
|
code.push_back(EncodeSMovB32(2, InlineU32(32)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(33)));
|
|
code.push_back(EncodeSop2(0x1e, 10, 0, 2));
|
|
code.push_back(EncodeSop2(0x1e, 11, 0, 3));
|
|
code.push_back(EncodeSop2(0x20, 12, 1, 2));
|
|
code.push_back(EncodeSop2(0x20, 13, 1, 3));
|
|
code.push_back(EncodeSop2(0x22, 14, 1, 2));
|
|
code.push_back(EncodeSop2(0x22, 15, 1, 3));
|
|
|
|
for (u32 i = 0; i < 6u; i++) {
|
|
AppendStoreSgpr(&code, 10u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarShiftCountsMaskLowBits",
|
|
code,
|
|
{},
|
|
{1, 2, 0x80000000u, 0x40000000u, 0x80000000u, 0xc0000000u},
|
|
{O::S_MOV_B32, O::S_LSHL_B32, O::S_LSHR_B32, O::S_ASHR_I32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Rdna2ScalarOpcodes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(2, InlineU32(7)));
|
|
code.push_back(EncodeSMovB32(106, InlineU32(16)));
|
|
code.push_back(EncodeSop1(0x1d, 106, InlineU32(0)));
|
|
AppendStoreSgpr(&code, 106, 0);
|
|
code.push_back(EncodeSopk(0x13, 106, 0x1019u));
|
|
AppendStoreSgpr(&code, 106, 1);
|
|
code.push_back(EncodeSop2(0x02, 106, 2, 239u));
|
|
AppendStoreSgpr(&code, 106, 2);
|
|
code.push_back(EncodeSopp(0x0e, 0));
|
|
code.push_back(EncodeSop2(0x02, 106, 239u, 2));
|
|
AppendStoreSgpr(&code, 106, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"Rdna2ScalarOpcodes",
|
|
code,
|
|
{},
|
|
{0x11u, 0x11u, 7u, 7u},
|
|
{O::S_MOV_B32, O::S_BITSET1_B32, O::S_SETREG_B32, O::S_ADD_I32,
|
|
O::S_SLEEP, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarTrapDisabledFallsThroughExactRaw() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 7);
|
|
code.push_back(0xbf920001u); // s_trap 1 from the game shader
|
|
AppendVMovU32(&code, 0, 42);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ScalarTrapDisabledFallsThroughExactRaw";
|
|
test.code = code;
|
|
test.expected = {42};
|
|
test.opcodes = {O::V_MOV_B32, O::S_TRAP, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"S_TRAP 0x00000001", 1}};
|
|
test.compute_info.threads_num[0] = 1;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.wave_size = 32;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarWaitcntDepctrCapturedVmVsrc() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 7, 7);
|
|
AppendVMovU32(&code, 31, 0);
|
|
AppendBufferStoreDword(&code, 7, 31);
|
|
code.push_back(0xbfa3ffe3u); // s_waitcnt_depctr vm_vsrc(0)
|
|
AppendVMovU32(&code, 7, 42);
|
|
AppendStoreVgpr(&code, 7, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ScalarWaitcntDepctrCapturedVmVsrc";
|
|
test.code = std::move(code);
|
|
test.initial = {0, 0};
|
|
test.expected = {7, 42};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_WAITCNT_DEPCTR,
|
|
O::S_ENDPGM};
|
|
test.decoded_counts = {{"0x00000010: S_WAITCNT_DEPCTR 0x0000ffe3\n", 1}};
|
|
test.forbidden_spirv = {"OpControlBarrier", "OpMemoryBarrier"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarExtendedArithmetic() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0xffffffffu);
|
|
code.push_back(EncodeSMovB32(1, InlineU32(1)));
|
|
code.push_back(EncodeSop2(0x00, 2, 0, 1));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(5)));
|
|
code.push_back(EncodeSMovB32(4, InlineU32(6)));
|
|
code.push_back(EncodeSop2(0x04, 5, 3, 4));
|
|
code.push_back(EncodeSop2(0x02, 6, InlineU32(7), InlineU32(8)));
|
|
code.push_back(EncodeSop2(0x03, 7, InlineU32(7), InlineU32(9)));
|
|
AppendSMovLiteral(&code, 8, 0xfffffffbu);
|
|
code.push_back(EncodeSMovB32(9, InlineU32(3)));
|
|
code.push_back(EncodeSop2(0x06, 10, 8, 9));
|
|
code.push_back(EncodeSop2(0x08, 11, 8, 9));
|
|
AppendSMovLiteral(&code, 12, 0xfffffffeu);
|
|
code.push_back(EncodeSMovB32(13, InlineU32(3)));
|
|
code.push_back(EncodeSop2(0x07, 14, 12, 13));
|
|
code.push_back(EncodeSop2(0x09, 15, 12, 13));
|
|
code.push_back(EncodeSMovB32(16, 8));
|
|
code.push_back(EncodeSop1(0x34, 16, 16));
|
|
code.push_back(EncodeSopk(0x00, 17, 0xfff5u));
|
|
code.push_back(EncodeSopk(0x10, 17, 0xfffdu));
|
|
|
|
const u32 results[] = {2, 5, 6, 7, 10, 11, 14, 15, 16, 17};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreSgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarExtendedArithmetic",
|
|
code,
|
|
{},
|
|
{0, 12, 15, 0xfffffffeu, 0xfffffffbu, 3, 3, 0xfffffffeu, 5, 33},
|
|
{O::S_MOV_B32, O::S_ADD_U32, O::S_ADDC_U32, O::S_ADD_I32,
|
|
O::S_SUB_I32, O::S_MIN_I32, O::S_MAX_I32, O::S_MIN_U32, O::S_MAX_U32,
|
|
O::S_ABS_I32, O::S_MOVK_I32, O::S_MULK_I32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarArithmeticSccCarryBorrowOverflow() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
auto append_case = [&](bool prior_scc, u32 instruction, u32 out_sgpr) {
|
|
set_scc(prior_scc);
|
|
code.push_back(instruction);
|
|
capture_scc(out_sgpr);
|
|
};
|
|
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(1, InlineU32(1)));
|
|
append_case(false, EncodeSop2(0x01, 10, 0, 1), 20);
|
|
|
|
code.push_back(EncodeSMovB32(2, InlineU32(5)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(3)));
|
|
append_case(true, EncodeSop2(0x01, 11, 2, 3), 21);
|
|
|
|
AppendSMovLiteral(&code, 4, 0x7fffffffu);
|
|
code.push_back(EncodeSMovB32(5, InlineU32(1)));
|
|
append_case(false, EncodeSop2(0x02, 12, 4, 5), 22);
|
|
|
|
code.push_back(EncodeSMovB32(6, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(7, InlineU32(2)));
|
|
append_case(true, EncodeSop2(0x02, 13, 6, 7), 23);
|
|
|
|
AppendSMovLiteral(&code, 8, 0x80000000u);
|
|
code.push_back(EncodeSMovB32(9, InlineU32(1)));
|
|
append_case(false, EncodeSop2(0x03, 14, 8, 9), 24);
|
|
|
|
code.push_back(EncodeSMovB32(15, InlineU32(5)));
|
|
code.push_back(EncodeSMovB32(16, InlineU32(3)));
|
|
append_case(true, EncodeSop2(0x03, 17, 15, 16), 25);
|
|
|
|
AppendSMovLiteral(&code, 18, 0x7fffffffu);
|
|
set_scc(false);
|
|
code.push_back(EncodeSopk(0x0f, 18, 1));
|
|
capture_scc(26);
|
|
|
|
code.push_back(EncodeSMovB32(19, InlineU32(4)));
|
|
set_scc(true);
|
|
code.push_back(EncodeSopk(0x0f, 19, 0xfffeu));
|
|
capture_scc(27);
|
|
|
|
for (u32 i = 0; i < 8u; i++) {
|
|
AppendStoreSgpr(&code, 20u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarArithmeticSccCarryBorrowOverflow",
|
|
code,
|
|
{},
|
|
{1, 0, 1, 0, 1, 0, 1, 0},
|
|
{O::S_MOV_B32, O::S_SUB_U32, O::S_ADD_I32, O::S_SUB_I32,
|
|
O::S_CMP_EQ_U32, O::S_CSELECT_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarMinMaxSccComparisonEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
auto append_case = [&](bool prior_scc, u32 instruction, u32 out_sgpr) {
|
|
set_scc(prior_scc);
|
|
code.push_back(instruction);
|
|
capture_scc(out_sgpr);
|
|
};
|
|
|
|
AppendSMovLiteral(&code, 0, 0xfffffffeu);
|
|
code.push_back(EncodeSMovB32(1, InlineU32(3)));
|
|
append_case(false, EncodeSop2(0x06, 10, 0, 1), 20);
|
|
append_case(true, EncodeSop2(0x06, 11, 1, 0), 21);
|
|
append_case(false, EncodeSop2(0x08, 12, 1, 0), 22);
|
|
append_case(true, EncodeSop2(0x08, 13, 0, 1), 23);
|
|
|
|
code.push_back(EncodeSMovB32(2, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(3)));
|
|
append_case(false, EncodeSop2(0x07, 14, 2, 3), 24);
|
|
append_case(true, EncodeSop2(0x07, 15, 3, 2), 25);
|
|
append_case(false, EncodeSop2(0x09, 16, 3, 2), 26);
|
|
append_case(true, EncodeSop2(0x09, 17, 2, 3), 27);
|
|
|
|
for (u32 i = 0; i < 8u; i++) {
|
|
AppendStoreSgpr(&code, 20u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarMinMaxSccComparisonEdges",
|
|
code,
|
|
{},
|
|
{1, 0, 1, 0, 1, 0, 1, 0},
|
|
{O::S_MOV_B32, O::S_MIN_I32, O::S_MAX_I32, O::S_MIN_U32, O::S_MAX_U32,
|
|
O::S_CMP_EQ_U32, O::S_CSELECT_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarAbsI32UpdatesScc() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
set_scc(true);
|
|
code.push_back(EncodeSop1(0x34, 1, 0));
|
|
capture_scc(2);
|
|
|
|
AppendSMovLiteral(&code, 3, 0xfffffffbu);
|
|
set_scc(false);
|
|
code.push_back(EncodeSop1(0x34, 4, 3));
|
|
capture_scc(5);
|
|
|
|
AppendStoreSgpr(&code, 1, 0);
|
|
AppendStoreSgpr(&code, 2, 1);
|
|
AppendStoreSgpr(&code, 4, 2);
|
|
AppendStoreSgpr(&code, 5, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarAbsI32UpdatesScc",
|
|
code,
|
|
{},
|
|
{0, 0, 5, 1},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_ABS_I32, O::S_CSELECT_B32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarAbsdiffBitset64AndSetprio() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(
|
|
EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
code.push_back(EncodeSMovB32(0, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(1, InlineU32(5)));
|
|
code.push_back(EncodeSop2(0x2c, 10, 0, 1));
|
|
capture_scc(20);
|
|
AppendSMovLiteral(&code, 0, 0xffffffffu);
|
|
code.push_back(EncodeSMovB32(1, InlineU32(0)));
|
|
code.push_back(EncodeSop2(0x2c, 11, 0, 1));
|
|
capture_scc(21);
|
|
AppendSMovLiteral(&code, 0, 0x80000000u);
|
|
code.push_back(EncodeSop2(0x2c, 12, 0, 1));
|
|
capture_scc(22);
|
|
code.push_back(EncodeSMovB32(1, InlineU32(1)));
|
|
code.push_back(EncodeSop2(0x2c, 13, 0, 1));
|
|
capture_scc(23);
|
|
code.push_back(EncodeSop2(0x2c, 14, 0, 0));
|
|
capture_scc(24);
|
|
|
|
AppendSMovLiteral(&code, 2, 65u);
|
|
AppendSMovLiteral(&code, 3, 101u);
|
|
code.push_back(EncodeSMovB32(4, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(5, InlineU32(0)));
|
|
code.push_back(EncodeSop1(0x1e, 4, 2));
|
|
AppendStoreSgprPair(&code, 4, 10);
|
|
code.push_back(EncodeSop1(0x1e, 4, 3));
|
|
AppendStoreSgprPair(&code, 4, 12);
|
|
code.push_back(EncodeSop1(0x1c, 4, 2));
|
|
AppendStoreSgprPair(&code, 4, 14);
|
|
code.push_back(EncodeSop1(0x1c, 4, 3));
|
|
AppendStoreSgprPair(&code, 4, 16);
|
|
code.push_back(EncodeSopp(0x0f, 3));
|
|
|
|
AppendStoreSgpr(&code, 10, 0);
|
|
AppendStoreSgpr(&code, 11, 1);
|
|
AppendStoreSgpr(&code, 12, 2);
|
|
AppendStoreSgpr(&code, 13, 3);
|
|
AppendStoreSgpr(&code, 14, 4);
|
|
AppendStoreSgpr(&code, 20, 5);
|
|
AppendStoreSgpr(&code, 21, 6);
|
|
AppendStoreSgpr(&code, 22, 7);
|
|
AppendStoreSgpr(&code, 23, 8);
|
|
AppendStoreSgpr(&code, 24, 9);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ScalarAbsdiffBitset64AndSetprio";
|
|
test.code = std::move(code);
|
|
test.expected = {3u, 1u, 0x80000000u, 0x7fffffffu, 0u,
|
|
1u, 1u, 1u, 1u, 0u,
|
|
2u, 0u, 2u, 32u, 0u,
|
|
32u, 0u, 0u};
|
|
test.opcodes = {O::S_MOV_B32, O::S_ABSDIFF_I32, O::S_CSELECT_B32,
|
|
O::S_BITSET1_B64, O::S_BITSET0_B64, O::S_SETPRIO,
|
|
O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"S_ABSDIFF_I32", 5},
|
|
{"S_BITSET1_B64", 2},
|
|
{"S_BITSET0_B64", 2},
|
|
{"S_SETPRIO 0x00000003", 1}};
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarShiftLeftAddSccCarryEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
auto append_case = [&](bool prior_scc, u32 instruction, u32 out_sgpr) {
|
|
set_scc(prior_scc);
|
|
code.push_back(instruction);
|
|
capture_scc(out_sgpr);
|
|
};
|
|
|
|
AppendSMovLiteral(&code, 0, 0x80000000u);
|
|
AppendSMovLiteral(&code, 1, 0x40000000u);
|
|
AppendSMovLiteral(&code, 2, 0x20000000u);
|
|
AppendSMovLiteral(&code, 3, 0x10000000u);
|
|
code.push_back(EncodeSMovB32(4, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(5, InlineU32(0)));
|
|
append_case(false, EncodeSop2(0x2e, 10, 0, 5), 20);
|
|
append_case(true, EncodeSop2(0x2e, 11, 4, 4), 21);
|
|
append_case(false, EncodeSop2(0x2f, 12, 1, 5), 22);
|
|
append_case(true, EncodeSop2(0x2f, 13, 4, 4), 23);
|
|
append_case(false, EncodeSop2(0x30, 14, 2, 5), 24);
|
|
append_case(true, EncodeSop2(0x30, 15, 4, 4), 25);
|
|
append_case(false, EncodeSop2(0x31, 16, 3, 5), 26);
|
|
append_case(true, EncodeSop2(0x31, 17, 4, 4), 27);
|
|
|
|
for (u32 i = 0; i < 8u; i++) {
|
|
AppendStoreSgpr(&code, 20u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarShiftLeftAddSccCarryEdges",
|
|
code,
|
|
{},
|
|
{1, 0, 1, 0, 1, 0, 1, 0},
|
|
{O::S_MOV_B32, O::S_LSHL1_ADD_U32, O::S_LSHL2_ADD_U32,
|
|
O::S_LSHL3_ADD_U32, O::S_LSHL4_ADD_U32, O::S_CMP_EQ_U32,
|
|
O::S_CSELECT_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarCompareOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0xfffffffeu);
|
|
code.push_back(EncodeSMovB32(1, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(2, InlineU32(5)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(4, InlineU32(3)));
|
|
AppendSMovLiteral(&code, 10, 0x00000001u);
|
|
code.push_back(EncodeSMovB32(11, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(12, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(13, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(14, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(15, InlineU32(1)));
|
|
|
|
u32 dst = 20;
|
|
auto append_compare = [&](u32 opcode, u32 src0, u32 src1) {
|
|
code.push_back(EncodeSopc(opcode, src0, src1));
|
|
code.push_back(EncodeSop2(0x0a, dst++, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
append_compare(0x00, 2, 2);
|
|
append_compare(0x01, 0, 1);
|
|
append_compare(0x02, 1, 0);
|
|
append_compare(0x03, 1, 1);
|
|
append_compare(0x04, 0, 1);
|
|
append_compare(0x05, 0, 0);
|
|
append_compare(0x07, 3, 4);
|
|
append_compare(0x08, 4, 3);
|
|
append_compare(0x09, 4, 4);
|
|
append_compare(0x0b, 3, 4);
|
|
append_compare(0x12, 10, 10);
|
|
append_compare(0x12, 10, 12);
|
|
append_compare(0x12, 10, 14);
|
|
append_compare(0x13, 10, 12);
|
|
append_compare(0x13, 10, 14);
|
|
|
|
for (u32 i = 0; i < 15u; i++) {
|
|
AppendStoreSgpr(&code, 20u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarCompareOps",
|
|
code,
|
|
{},
|
|
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_I32, O::S_CMP_LG_I32, O::S_CMP_GT_I32,
|
|
O::S_CMP_GE_I32, O::S_CMP_LT_I32, O::S_CMP_LE_I32, O::S_CMP_LG_U32,
|
|
O::S_CMP_GT_U32, O::S_CMP_GE_U32, O::S_CMP_LE_U32, O::S_CMP_EQ_U64,
|
|
O::S_CMP_LG_U64, O::S_CSELECT_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarShiftAddAndMaskOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(1, InlineU32(3)));
|
|
code.push_back(EncodeSop2(0x2e, 2, 0, 1));
|
|
code.push_back(EncodeSop2(0x2f, 3, 0, 1));
|
|
code.push_back(EncodeSop2(0x30, 4, 0, 1));
|
|
code.push_back(EncodeSop2(0x31, 5, 0, 1));
|
|
AppendSMovLiteral(&code, 6, 0xfffffff8u);
|
|
code.push_back(EncodeSop2(0x22, 7, 6, InlineU32(2)));
|
|
AppendSMovLiteral(&code, 8, 0xffffffffu);
|
|
code.push_back(EncodeSMovB32(9, InlineU32(2)));
|
|
code.push_back(EncodeSop2(0x35, 10, 8, 9));
|
|
AppendSMovLiteral(&code, 12, 0x80000011u);
|
|
AppendSMovLiteral(&code, 13, 0x80000001u);
|
|
code.push_back(EncodeSop1(0x08, 14, 12));
|
|
code.push_back(EncodeSop1(0x0a, 16, 12));
|
|
|
|
AppendStoreSgpr(&code, 2, 0);
|
|
AppendStoreSgpr(&code, 3, 1);
|
|
AppendStoreSgpr(&code, 4, 2);
|
|
AppendStoreSgpr(&code, 5, 3);
|
|
AppendStoreSgpr(&code, 7, 4);
|
|
AppendStoreSgpr(&code, 10, 5);
|
|
AppendStoreSgprPair(&code, 14, 6);
|
|
AppendStoreSgprPair(&code, 16, 8);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarShiftAddAndMaskOps",
|
|
code,
|
|
{},
|
|
{7, 11, 19, 35, 0xfffffffeu, 1, 0x7fffffeeu, 0x7ffffffeu, 0xf00000ffu,
|
|
0xf000000fu},
|
|
{O::S_MOV_B32, O::S_LSHL1_ADD_U32, O::S_LSHL2_ADD_U32,
|
|
O::S_LSHL3_ADD_U32, O::S_LSHL4_ADD_U32, O::S_ASHR_I32,
|
|
O::S_MUL_HI_U32, O::S_NOT_B64, O::S_WQM_B64, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarNotB64UpdatesScc() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
AppendSMovLiteral(&code, 0, 0xffffffffu);
|
|
AppendSMovLiteral(&code, 1, 0xffffffffu);
|
|
set_scc(true);
|
|
code.push_back(EncodeSop1(0x08, 2, 0));
|
|
capture_scc(4);
|
|
|
|
AppendSMovLiteral(&code, 6, 0xfffffffeu);
|
|
AppendSMovLiteral(&code, 7, 0xffffffffu);
|
|
set_scc(false);
|
|
code.push_back(EncodeSop1(0x08, 8, 6));
|
|
capture_scc(10);
|
|
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 12, 106));
|
|
code.push_back(EncodeSop1(0x08, 14, 12));
|
|
capture_scc(16);
|
|
|
|
AppendStoreSgpr(&code, 4, 0);
|
|
AppendStoreSgpr(&code, 10, 1);
|
|
AppendStoreSgprPair(&code, 2, 2);
|
|
AppendStoreSgprPair(&code, 8, 4);
|
|
AppendStoreSgpr(&code, 16, 6);
|
|
AppendStoreSgprPair(&code, 14, 7);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarNotB64UpdatesScc",
|
|
code,
|
|
{},
|
|
{0, 1, 0, 0, 1, 0, 1, 0xfffffffeu, 0xffffffffu},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_NOT_B64, O::S_CSELECT_B32,
|
|
O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarQuadmaskB64() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
AppendSMovLiteral(&code, 106, 0x80000011u);
|
|
AppendSMovLiteral(&code, 107, 0x000000f0u);
|
|
code.push_back(EncodeSop1(0x2d, 106, 106));
|
|
capture_scc(0);
|
|
AppendStoreSgprPair(&code, 106, 0);
|
|
AppendStoreSgpr(&code, 0, 2);
|
|
|
|
code.push_back(EncodeSMovB32(106, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(107, InlineU32(0)));
|
|
code.push_back(EncodeSop1(0x2d, 106, 106));
|
|
capture_scc(0);
|
|
AppendStoreSgprPair(&code, 106, 3);
|
|
AppendStoreSgpr(&code, 0, 5);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarQuadmaskB64",
|
|
code,
|
|
{},
|
|
{0x00000283u, 0, 1, 0, 0, 0},
|
|
{O::S_MOV_B32, O::S_QUADMASK_B64, O::S_CSELECT_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarFlbitI32B64Gpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0x00000000u);
|
|
AppendSMovLiteral(&code, 1, 0x80000000u);
|
|
code.push_back(EncodeSop1(0x16, 20, 0));
|
|
|
|
AppendSMovLiteral(&code, 2, 0x00008000u);
|
|
AppendSMovLiteral(&code, 3, 0x00000000u);
|
|
code.push_back(EncodeSop1(0x16, 21, 2));
|
|
|
|
AppendSMovLiteral(&code, 4, 0x00000000u);
|
|
AppendSMovLiteral(&code, 5, 0x00000000u);
|
|
code.push_back(EncodeSop1(0x16, 22, 4));
|
|
|
|
AppendSMovLiteral(&code, 14, 0x00000008u);
|
|
AppendSMovLiteral(&code, 15, 0x00000000u);
|
|
code.push_back(EncodeSop1(0x16, 106, 14));
|
|
|
|
AppendStoreSgpr(&code, 20, 0);
|
|
AppendStoreSgpr(&code, 21, 1);
|
|
AppendStoreSgpr(&code, 22, 2);
|
|
AppendStoreSgpr(&code, 106, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarFlbitI32B64Gpu",
|
|
code,
|
|
{},
|
|
{0, 48, 0xffffffffu, 60},
|
|
{O::S_MOV_B32, O::S_FLBIT_I32_B64, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarFlbitI32B32Gpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0x00000000u);
|
|
code.push_back(EncodeSop1(0x15, 20, 0));
|
|
|
|
AppendSMovLiteral(&code, 0, 0x0000ccccu);
|
|
code.push_back(EncodeSop1(0x15, 21, 0));
|
|
|
|
AppendSMovLiteral(&code, 0, 0x80000000u);
|
|
code.push_back(EncodeSop1(0x15, 22, 0));
|
|
|
|
AppendSMovLiteral(&code, 0, 0x7fffffffu);
|
|
code.push_back(EncodeSop1(0x15, 23, 0));
|
|
|
|
AppendStoreSgpr(&code, 20, 0);
|
|
AppendStoreSgpr(&code, 21, 1);
|
|
AppendStoreSgpr(&code, 22, 2);
|
|
AppendStoreSgpr(&code, 23, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarFlbitI32B32Gpu",
|
|
code,
|
|
{},
|
|
{0xffffffffu, 16, 0, 1},
|
|
{O::S_MOV_B32, O::S_FLBIT_I32_B32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarSaveExecOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 106, 0xffffffffu);
|
|
AppendSMovLiteral(&code, 107, 0xffffffffu);
|
|
code.push_back(0xbe80246au);
|
|
code.push_back(EncodeSop1(0x28, 2, 106));
|
|
code.push_back(EncodeSMovB32(106, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(107, InlineU32(0)));
|
|
code.push_back(EncodeSop1(0x37, 4, 106));
|
|
AppendSMovLiteral(&code, 106, 0x00000003u);
|
|
code.push_back(EncodeSop1(0x3c, 8, 106));
|
|
code.push_back(EncodeSop1(0x04, 10, 126));
|
|
code.push_back(EncodeSMovB32(106, InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x44, 12, 106));
|
|
code.push_back(EncodeSop1(0x04, 14, 126));
|
|
AppendSMovLiteral(&code, 126, 0xffffffffu);
|
|
AppendSMovLiteral(&code, 127, 0xffffffffu);
|
|
|
|
AppendStoreSgprPair(&code, 0, 0);
|
|
AppendStoreSgprPair(&code, 2, 2);
|
|
AppendStoreSgprPair(&code, 4, 4);
|
|
AppendStoreSgpr(&code, 8, 6);
|
|
AppendStoreSgprPair(&code, 10, 7);
|
|
AppendStoreSgpr(&code, 12, 9);
|
|
AppendStoreSgprPair(&code, 14, 10);
|
|
AppendStoreSgpr(&code, 253, 12);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarSaveExecOps",
|
|
code,
|
|
{},
|
|
{1, 0, 1, 0, 0xffffffffu, 0xffffffffu, 0xffffffffu,
|
|
3, 0xffffffffu, 3, 2, 0xffffffffu, 1},
|
|
{O::S_MOV_B32, O::S_AND_SAVEEXEC_B64, O::S_ORN2_SAVEEXEC_B64,
|
|
O::S_ANDN1_SAVEEXEC_B64, O::S_AND_SAVEEXEC_B32,
|
|
O::S_ANDN1_SAVEEXEC_B32, O::S_MOV_B64, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarOrn2SaveexecUsesSourceOrNotExec() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 126, 0x0000000cu);
|
|
AppendSMovLiteral(&code, 127, 0x80000000u);
|
|
AppendSMovLiteral(&code, 0, 0x00000001u);
|
|
AppendSMovLiteral(&code, 1, 0x00000001u);
|
|
code.push_back(EncodeSop1(0x28, 2, 0));
|
|
code.push_back(EncodeSop1(0x04, 4, 126));
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
|
AppendStoreSgprPair(&code, 2, 0);
|
|
AppendStoreSgprPair(&code, 4, 2);
|
|
AppendStoreSgpr(&code, 253, 4);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarOrn2SaveexecUsesSourceOrNotExec",
|
|
code,
|
|
{},
|
|
{0x0000000cu, 0x80000000u, 0xfffffff3u, 0x7fffffffu, 1},
|
|
{O::S_MOV_B32, O::S_ORN2_SAVEEXEC_B64, O::S_MOV_B64, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarOrn2SaveexecB32(u32 wave_size, u32 threads) {
|
|
using O = ShaderOpcode;
|
|
constexpr u32 exec_hi = 0xa5a55a5au;
|
|
constexpr u32 vcc_hi = 0x13579bdfu;
|
|
struct Case {
|
|
u32 exec_lo, source, destination, result;
|
|
};
|
|
constexpr Case cases[] = {
|
|
{0x8000000cu, 126, 106, 0xffffffffu}, // Captured vcc_lo, exec_lo.
|
|
{0x0000000fu, 6, 6, 0xfffffff3u}, // Aliased SGPR source/destination.
|
|
{0xffffffffu, 128, 8, 0}, // SCC ignores the nonzero high half.
|
|
{0xffffffffu, 107, 8, vcc_hi},
|
|
{0xffffffffu, 127, 8, exec_hi},
|
|
{0xffffffffu, 253, 8, 1}, // SCC is a scalar value, not a mask.
|
|
{0xffffffffu, 251, 8, 0}, // VCCZ summarizes the whole mask.
|
|
{0x00000001u, 252, 8, 0xfffffffeu}, // EXECZ is uniform across lanes.
|
|
};
|
|
TestCase test;
|
|
test.name = wave_size == 64 ? "ScalarOrn2SaveexecB32Wave64"
|
|
: threads == 32 ? "ScalarOrn2SaveexecB32Wave32"
|
|
: "ScalarOrn2SaveexecB32PartialWave";
|
|
auto &code = test.code;
|
|
// Zero flags ignore high bits only in wave32 and include inactive low bits.
|
|
constexpr u32 masks[][2] = {{0, 0}, {0x80000000u, 0}, {0, 0x80000000u}};
|
|
for (const auto &mask : masks) {
|
|
AppendSMovLiteral(&code, 106, mask[0]);
|
|
AppendSMovLiteral(&code, 107, mask[1]);
|
|
AppendSMovLiteral(&code, 126, mask[0]);
|
|
AppendSMovLiteral(&code, 127, mask[1]);
|
|
code.push_back(EncodeSMovB32(20, 251));
|
|
code.push_back(EncodeSMovB32(21, 252));
|
|
code.push_back(EncodeSop1(0x04, 126, 193u));
|
|
const u32 zero = mask[0] == 0 && (wave_size == 32 || mask[1] == 0);
|
|
for (u32 reg = 20; reg <= 21; ++reg) {
|
|
AppendStoreSgprAtLaneDwordOffset(&code, reg, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
test.expected.insert(test.expected.end(), threads, zero);
|
|
}
|
|
}
|
|
for (const auto &item : cases) {
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendSMovLiteral(&code, 126, item.exec_lo);
|
|
AppendSMovLiteral(&code, 127, exec_hi);
|
|
AppendSMovLiteral(&code, 106, 1);
|
|
AppendSMovLiteral(&code, 107, vcc_hi);
|
|
AppendSMovLiteral(&code, 6, 3);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(0)));
|
|
code.push_back(item.source == 126 ? 0xbeea407eu
|
|
: EncodeSop1(0x40, item.destination, item.source));
|
|
const u32 snapshots[] = {item.destination, 126, 127, 107, 253};
|
|
for (u32 i = 0; i < std::size(snapshots); ++i) {
|
|
code.push_back(EncodeSMovB32(20 + i, snapshots[i]));
|
|
}
|
|
AppendVMovU32(&code, 2, 1);
|
|
code.push_back(EncodeSop1(0x04, 126, 193u)); // Restore EXEC for readback.
|
|
const u32 expected[] = {item.exec_lo, item.result, exec_hi, vcc_hi,
|
|
item.result != 0 ? 1u : 0u};
|
|
for (u32 i = 0; i < std::size(expected); ++i) {
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 20 + i, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
test.expected.insert(test.expected.end(), threads, expected[i]);
|
|
}
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
for (u32 lane = 0; lane < threads; ++lane) {
|
|
const u32 mask = lane < 32 ? item.result : exec_hi;
|
|
test.expected.push_back((mask >> (lane % 32)) & 1u);
|
|
}
|
|
}
|
|
AppendEnd(&code);
|
|
test.initial.resize(test.expected.size());
|
|
test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::S_CMP_EQ_U32,
|
|
O::S_ORN2_SAVEEXEC_B32, O::V_MOV_B32, O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"S_ORN2_SAVEEXEC_B32 vcc_lo, exec_lo", 1}};
|
|
test.compute_info.threads_num[0] = threads;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = wave_size;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarSubvectorLoops(u32 wave_size) {
|
|
using O = ShaderOpcode;
|
|
using namespace ShaderRecompiler::Decoder;
|
|
TestCase test;
|
|
test.name = wave_size == 64 ? "ScalarSubvectorLoopsWave64"
|
|
: "ScalarSubvectorLoopsWave32";
|
|
// The captured mesh shader's signed SOPK targets are relative to PC + 4.
|
|
std::array<u32, 47> captured{};
|
|
captured[0x30 / 4] = 0xbda60022u;
|
|
captured[0xb8 / 4] = 0xbe26ffdeu;
|
|
Instruction begin, end;
|
|
DecodeInstruction(captured, 0x30 / 4, begin);
|
|
DecodeInstruction(captured, 0xb8 / 4, end);
|
|
Require(test.name, "captured branch decode",
|
|
begin.opcode == O::S_SUBVECTOR_LOOP_BEGIN && begin.dst.reg == 38 &&
|
|
begin.dst.kind == OperandKind::Sgpr && begin.branch_target == 0xbc &&
|
|
end.opcode == O::S_SUBVECTOR_LOOP_END && end.dst.reg == 38 &&
|
|
end.dst.kind == OperandKind::Sgpr && end.branch_target == 0x34 &&
|
|
begin.word_count == 1 && end.word_count == 1,
|
|
"subvector SDST or signed PC-relative target was decoded incorrectly");
|
|
|
|
constexpr u32 sentinel = 0x5a13abcdu;
|
|
constexpr u32 low = 0x80000005u, high = 0x42000002u;
|
|
struct Case {
|
|
u32 lo, hi, body_mode, saved, final_lo, final_hi, passes;
|
|
};
|
|
constexpr Case cases[] = {
|
|
{0, 0, 0, sentinel, 0, 0, 0},
|
|
{low, 0, 0, 0, low, 0, 1},
|
|
{0, high, 0, 0, 0, high, 1},
|
|
{low, high, 0, low, low, high, 2},
|
|
{~0u, ~0u, 0, ~0u, ~0u, ~0u, 2},
|
|
// The game resets the full EXEC mask before END, selecting its HI != 0 arm.
|
|
{low, high, 1, high, high, ~0u, 1},
|
|
// END must restore the low mask saved after the body has changed it.
|
|
{low, high, 2, 0, 0, high, 2},
|
|
// BEGIN EXEC_LO first copies HI to its aliased destination, then clears HI.
|
|
{low, high, 3, 0, 0, high, 2},
|
|
};
|
|
auto &code = test.code;
|
|
for (u32 index = 0; index < std::size(cases); ++index) {
|
|
const auto &item = cases[index];
|
|
const u32 scc = index & 1u;
|
|
const u32 saved_reg = item.body_mode == 3 ? 126u : 38u;
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovU32(&code, 3, 0);
|
|
AppendSMovLiteral(&code, 38, sentinel);
|
|
AppendSMovLiteral(&code, 32, 1); // Scalar 2^passes; MULK preserves SCC.
|
|
AppendSMovLiteral(&code, 33, 0xff);
|
|
AppendSMovLiteral(&code, 126, item.lo);
|
|
AppendSMovLiteral(&code, 127, item.hi);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(scc == 0 ? 1 : 0)));
|
|
const auto begin_word = static_cast<u32>(code.size());
|
|
code.push_back(0);
|
|
const auto body_word = static_cast<u32>(code.size());
|
|
code.push_back(EncodeSMovB32(33, 253)); // Observe SCC immediately after BEGIN.
|
|
code.push_back(EncodeSopk(0x10, 32, 2));
|
|
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 2));
|
|
code.push_back(EncodeVop1(0x01, 3, 32));
|
|
if (item.body_mode == 1) {
|
|
code.push_back(EncodeSop1(0x04, 126, 193)); // S_MOV_B64 EXEC, -1.
|
|
} else if (item.body_mode == 2) {
|
|
code.push_back(EncodeSMovB32(126, InlineU32(0)));
|
|
}
|
|
const auto end_word = static_cast<u32>(code.size());
|
|
code.push_back(EncodeSopk(0x1c, saved_reg, body_word - end_word - 1u));
|
|
code[begin_word] = EncodeSopk(0x1b, saved_reg, end_word - begin_word);
|
|
const u32 sources[] = {saved_reg, 126, 127, 253, 32, 33};
|
|
for (u32 i = 0; i < std::size(sources); ++i) {
|
|
code.push_back(EncodeSMovB32(20 + i, sources[i]));
|
|
}
|
|
code.push_back(EncodeSop1(0x04, 126, 193)); // Restore all lanes for readback.
|
|
const u32 expected[] = {item.saved, item.final_lo, item.final_hi, scc,
|
|
1u << item.passes, item.passes == 0 ? 0xff : scc};
|
|
for (u32 i = 0; i < std::size(expected); ++i) {
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 20 + i, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
test.expected.insert(test.expected.end(), wave_size, expected[i]);
|
|
}
|
|
for (u32 reg : {2u, 3u}) {
|
|
AppendStoreVgprAtLaneDwordOffset(&code, reg, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
for (u32 lane = 0; lane < wave_size; ++lane) {
|
|
const bool upper = lane >= 32;
|
|
const u32 mask = upper || item.body_mode == 3 ? item.hi : item.lo;
|
|
const bool active = ((mask >> (lane % 32)) & 1u) != 0 &&
|
|
!(upper && item.body_mode == 1);
|
|
const u32 pass_value = upper && item.lo != 0 ? 4u : 2u;
|
|
test.expected.push_back(active ? (reg == 2 ? 1u : pass_value) : 0u);
|
|
}
|
|
}
|
|
}
|
|
AppendEnd(&code);
|
|
test.initial.resize(test.expected.size());
|
|
test.opcodes = {O::S_SUBVECTOR_LOOP_BEGIN, O::S_SUBVECTOR_LOOP_END,
|
|
O::S_MOV_B32, O::S_MOV_B64, O::S_MULK_I32, O::S_CMP_EQ_U32,
|
|
O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = wave_size;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = wave_size;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarGetpcWritesNextInstructionPc() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSop1(0x1f, 0, 0));
|
|
AppendStoreSgprPair(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarGetpcWritesNextInstructionPc",
|
|
code,
|
|
{},
|
|
{4, 0},
|
|
{O::S_GETPC_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarBitfieldPack() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(15)));
|
|
code.push_back(EncodeSop1(0x0b, 1, 0));
|
|
AppendSMovLiteral(&code, 2, 0xf0000000u);
|
|
code.push_back(EncodeSop1(0x10, 3, 0));
|
|
AppendSMovLiteral(&code, 19, 0xf0f00001u);
|
|
code.push_back(EncodeSop1(0x0f, 20, 19));
|
|
code.push_back(EncodeSop2(0x0a, 21, InlineU32(1), InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(22, InlineU32(0)));
|
|
code.push_back(EncodeSop1(0x0f, 23, 22));
|
|
code.push_back(EncodeSop2(0x0a, 24, InlineU32(1), InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(4, InlineU32(3)));
|
|
code.push_back(EncodeSop1(0x3b, 5, 4));
|
|
code.push_back(EncodeSop2(0x24, 7, InlineU32(4), InlineU32(8)));
|
|
AppendSMovLiteral(&code, 8, 0x00f00000u);
|
|
AppendSMovLiteral(&code, 9, 0x00040014u);
|
|
code.push_back(EncodeSop2(0x27, 10, 8, 9));
|
|
AppendSMovLiteral(&code, 11, 0xaaaabbbbu);
|
|
AppendSMovLiteral(&code, 12, 0xccccddddu);
|
|
code.push_back(EncodeSop2(0x32, 13, 11, 12));
|
|
code.push_back(EncodeSop2(0x33, 14, 11, 12));
|
|
code.push_back(EncodeSop2(0x34, 15, 11, 12));
|
|
code.push_back(EncodeSopc(0x0d, InlineU32(4), InlineU32(2)));
|
|
code.push_back(EncodeSop2(0x0a, 17, InlineU32(1), InlineU32(0)));
|
|
code.push_back(EncodeSopc(0x0c, InlineU32(4), InlineU32(1)));
|
|
code.push_back(EncodeSop2(0x0a, 18, InlineU32(1), InlineU32(0)));
|
|
AppendSMovLiteral(&code, 25, 0u);
|
|
AppendSMovLiteral(&code, 26, 0x00000100u);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x14, 31, 25));
|
|
code.push_back(EncodeSop2(0x0a, 34, InlineU32(1), InlineU32(0)));
|
|
AppendSMovLiteral(&code, 27, 0x00000020u);
|
|
AppendSMovLiteral(&code, 28, 0x00000100u);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x14, 32, 27));
|
|
code.push_back(EncodeSop2(0x0a, 35, InlineU32(1), InlineU32(0)));
|
|
AppendSMovLiteral(&code, 29, 0u);
|
|
AppendSMovLiteral(&code, 30, 0u);
|
|
code.push_back(EncodeSop1(0x14, 33, 29));
|
|
|
|
AppendStoreSgpr(&code, 1, 0);
|
|
AppendStoreSgpr(&code, 3, 1);
|
|
AppendStoreSgpr(&code, 20, 2);
|
|
AppendStoreSgpr(&code, 21, 3);
|
|
AppendStoreSgpr(&code, 23, 4);
|
|
AppendStoreSgpr(&code, 24, 5);
|
|
AppendStoreSgprPair(&code, 5, 6);
|
|
AppendStoreSgpr(&code, 7, 8);
|
|
AppendStoreSgpr(&code, 10, 9);
|
|
AppendStoreSgpr(&code, 13, 10);
|
|
AppendStoreSgpr(&code, 14, 11);
|
|
AppendStoreSgpr(&code, 15, 12);
|
|
AppendStoreSgpr(&code, 17, 13);
|
|
AppendStoreSgpr(&code, 18, 14);
|
|
AppendStoreSgpr(&code, 31, 15);
|
|
AppendStoreSgpr(&code, 32, 16);
|
|
AppendStoreSgpr(&code, 33, 17);
|
|
AppendStoreSgpr(&code, 34, 18);
|
|
AppendStoreSgpr(&code, 35, 19);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarBitfieldPack",
|
|
code,
|
|
{},
|
|
{0xf0000000u, 8, 9, 1, 0,
|
|
0, 0x0000000fu, 0, 0x00000f00u, 0x0000000fu,
|
|
0xddddbbbbu, 0xccccbbbbu, 0xccccaaaau, 1, 1,
|
|
40, 5, 0xffffffffu, 0, 1},
|
|
{O::S_MOV_B32, O::S_BREV_B32, O::S_BCNT1_I32_B32, O::S_BCNT1_I32_B64,
|
|
O::S_FF1_I32_B64, O::S_BITREPLICATE_B64_B32, O::S_BFM_B32,
|
|
O::S_BFE_U32, O::S_PACK_LL_B32_B16, O::S_PACK_LH_B32_B16,
|
|
O::S_PACK_HH_B32_B16, O::S_BITCMP0_B32, O::S_BITCMP1_B32,
|
|
O::S_CSELECT_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarBrevB32PreservesScc() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
auto set_scc = [&](bool value) {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(value ? 1u : 0u)));
|
|
};
|
|
auto capture_scc = [&](u32 dst_sgpr) {
|
|
code.push_back(EncodeSop2(0x0a, dst_sgpr, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
code.push_back(EncodeSMovB32(0, InlineU32(1)));
|
|
set_scc(false);
|
|
code.push_back(EncodeSop1(0x0b, 1, 0));
|
|
capture_scc(2);
|
|
|
|
code.push_back(EncodeSMovB32(3, InlineU32(0)));
|
|
set_scc(true);
|
|
code.push_back(EncodeSop1(0x0b, 4, 3));
|
|
capture_scc(5);
|
|
|
|
AppendStoreSgpr(&code, 1, 0);
|
|
AppendStoreSgpr(&code, 2, 1);
|
|
AppendStoreSgpr(&code, 4, 2);
|
|
AppendStoreSgpr(&code, 5, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarBrevB32PreservesScc",
|
|
code,
|
|
{},
|
|
{0x80000000u, 0, 0, 1},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_BREV_B32, O::S_CSELECT_B32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarBfeI32CapturedRawSignExtends() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 0, 30);
|
|
code.push_back(EncodeVop1(0x02, 20, Vgpr(0)));
|
|
code.push_back(0x946aff14u); // s_bfe_i32 vcc_lo, s20, literal
|
|
code.push_back(0x0004001cu); // offset 28, width 4
|
|
code.push_back(EncodeSop2(0x0a, 21, InlineU32(1), InlineU32(0)));
|
|
code.push_back(EncodeSop2(0x28, 22, 20, 255u));
|
|
code.push_back(0u); // zero width produces zero and clears SCC
|
|
code.push_back(EncodeSop2(0x0a, 23, InlineU32(1), InlineU32(0)));
|
|
AppendStoreSgpr(&code, 106, 1);
|
|
AppendStoreSgpr(&code, 21, 2);
|
|
AppendStoreSgpr(&code, 22, 3);
|
|
AppendStoreSgpr(&code, 23, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ScalarBfeI32CapturedRawSignExtends";
|
|
test.code = std::move(code);
|
|
test.initial = {0xf0000000u, 0, 0, 0, 0};
|
|
test.expected = {0xf0000000u, 0xffffffffu, 1, 0, 0};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32,
|
|
O::S_BFE_I32, O::S_CSELECT_B32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpBitFieldSExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase BitfieldExtractWidthPastEndEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0xf0000000u);
|
|
AppendSMovLiteral(&code, 1, (20u << 16u) | 20u);
|
|
code.push_back(EncodeSop2(0x27, 2, 0, 1));
|
|
AppendSMovLiteral(&code, 3, 0x80000000u);
|
|
AppendSMovLiteral(&code, 4, (7u << 16u) | 31u);
|
|
code.push_back(EncodeSop2(0x27, 5, 3, 4));
|
|
AppendVMovLiteral(&code, 6, 0xf0000000u);
|
|
AppendVMovU32(&code, 7, 28);
|
|
AppendVMovU32(&code, 8, 8);
|
|
AppendVop3(&code, 0x148, 9, Vgpr(6), Vgpr(7), Vgpr(8));
|
|
AppendStoreSgpr(&code, 2, 0);
|
|
AppendStoreSgpr(&code, 5, 1);
|
|
AppendStoreVgpr(&code, 9, 2);
|
|
AppendEnd(&code);
|
|
|
|
return {"BitfieldExtractWidthPastEndEdges",
|
|
code,
|
|
{},
|
|
{0x00000f00u, 1, 0x0000000fu},
|
|
{O::S_MOV_B32, O::S_BFE_U32, O::V_MOV_B32, O::V_BFE_U32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Scalar64BitOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0x0f0f0f0fu);
|
|
AppendSMovLiteral(&code, 1, 0x00ff00ffu);
|
|
AppendSMovLiteral(&code, 2, 0x33333333u);
|
|
AppendSMovLiteral(&code, 3, 0x0f0f0f0fu);
|
|
code.push_back(EncodeSop2(0x0f, 4, 0, 2));
|
|
code.push_back(EncodeSop2(0x15, 6, 0, 2));
|
|
code.push_back(EncodeSop2(0x11, 8, 0, 2));
|
|
code.push_back(EncodeSop2(0x17, 10, 0, 2));
|
|
code.push_back(EncodeSop2(0x13, 12, 0, 2));
|
|
code.push_back(EncodeSop2(0x19, 14, 0, 2));
|
|
code.push_back(EncodeSop2(0x1b, 16, 0, 2));
|
|
code.push_back(EncodeSop2(0x1d, 18, 0, 2));
|
|
code.push_back(EncodeSop1(0x04, 20, 0));
|
|
code.push_back(EncodeSop2(0x1f, 22, 0, InlineU32(4)));
|
|
code.push_back(EncodeSop2(0x21, 24, 0, InlineU32(8)));
|
|
code.push_back(EncodeSop2(0x25, 26, InlineU32(36), InlineU32(4)));
|
|
code.push_back(EncodeSop2(0x21, 34, 193u, InlineU32(1)));
|
|
AppendSMovLiteral(&code, 28, 0x000c0004u);
|
|
code.push_back(EncodeSop2(0x29, 30, 0, 28));
|
|
code.push_back(EncodeSopc(0x06, 0, 0));
|
|
code.push_back(EncodeSop2(0x0b, 32, 0, 2));
|
|
|
|
const u32 result_pairs[] = {4, 6, 8, 10, 12, 14, 16, 18,
|
|
20, 22, 24, 26, 34, 30, 32};
|
|
u32 out = 0;
|
|
for (auto sgpr : result_pairs) {
|
|
AppendStoreSgprPair(&code, sgpr, out);
|
|
out += 2;
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"Scalar64BitOps",
|
|
code,
|
|
{},
|
|
{0x03030303u, 0x000f000fu, 0x0c0c0c0cu, 0x00f000f0u, 0x3f3f3f3fu,
|
|
0x0fff0fffu, 0xcfcfcfcfu, 0xf0fff0ffu, 0x3c3c3c3cu, 0x0ff00ff0u,
|
|
0xfcfcfcfcu, 0xfff0fff0u, 0xc0c0c0c0u, 0xf000f000u, 0xc3c3c3c3u,
|
|
0xf00ff00fu, 0x0f0f0f0fu, 0x00ff00ffu, 0xf0f0f0f0u, 0x0ff00ff0u,
|
|
0xff0f0f0fu, 0x0000ff00u, 0xfffffff0u, 0x000000ffu, 0xffffffffu,
|
|
0x7fffffffu, 0x000000f0u, 0, 0x0f0f0f0fu, 0x00ff00ffu},
|
|
{O::S_MOV_B32, O::S_MOV_B64, O::S_AND_B64, O::S_ANDN2_B64,
|
|
O::S_OR_B64, O::S_ORN2_B64, O::S_XOR_B64, O::S_NAND_B64,
|
|
O::S_NOR_B64, O::S_XNOR_B64, O::S_LSHL_B64, O::S_LSHR_B64,
|
|
O::S_BFM_B64, O::S_BFE_U64, O::S_CMP_EQ_U32, O::S_CSELECT_B64,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarConditionalMoveB64() {
|
|
using O = ShaderOpcode;
|
|
TestCase test;
|
|
test.name = "ScalarConditionalMoveB64";
|
|
auto &code = test.code;
|
|
for (u32 scc : {0u, 1u}) {
|
|
AppendSMovLiteral(&code, 0, 0x13579bdfu);
|
|
AppendSMovLiteral(&code, 1, 0x2468ace0u);
|
|
AppendSMovLiteral(&code, 2, 0xabcdef01u);
|
|
AppendSMovLiteral(&code, 3, 0xfedcba98u);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(scc), InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x06, 0, 2));
|
|
code.push_back(EncodeSop2(0x0a, 4, InlineU32(1), InlineU32(0)));
|
|
AppendStoreSgprPair(&code, 0, scc * 3);
|
|
AppendStoreSgpr(&code, 4, scc * 3 + 2);
|
|
}
|
|
AppendEnd(&code);
|
|
test.expected = {0x13579bdfu, 0x2468ace0u, 0,
|
|
0xabcdef01u, 0xfedcba98u, 1};
|
|
test.opcodes = {O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CMOV_B64,
|
|
O::S_CSELECT_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarConditionalMoveB64PreservesMasks() {
|
|
using O = ShaderOpcode;
|
|
TestCase test;
|
|
test.name = "ScalarConditionalMoveB64PreservesMasks";
|
|
auto &code = test.code;
|
|
code.push_back(EncodeSop1(0x04, 16, 126)); // Save full EXEC.
|
|
AppendVMovU32(&code, 1, 3);
|
|
AppendVMovU32(&code, 2, 9);
|
|
u32 offset = 0;
|
|
for (u32 destination : {126u, 106u, 8u}) {
|
|
for (u32 scc : {0u, 1u}) {
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 1)); // Only lane 3.
|
|
code.push_back(EncodeSop1(0x04, destination, 106));
|
|
code.push_back(EncodeSopc(0x06, InlineU32(scc), InlineU32(1)));
|
|
// EXEC encoding is the captured Pathless instruction, 0xbefe06c1.
|
|
code.push_back(EncodeSop1(0x06, destination, 193u));
|
|
code.push_back(EncodeSop2(0x0a, 4, InlineU32(1), InlineU32(0)));
|
|
code.push_back(EncodeSop1(0x04, 126, destination));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset);
|
|
code.push_back(EncodeSop1(0x04, 126, 16));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 4, 0, offset + 64);
|
|
for (u32 lane = 0; lane < 64; ++lane) {
|
|
test.expected.push_back(scc || lane == 3 ? 9u : 0xdeadbeefu);
|
|
}
|
|
test.expected.insert(test.expected.end(), 64, scc);
|
|
offset += 128;
|
|
}
|
|
}
|
|
AppendEnd(&code);
|
|
test.initial.assign(test.expected.size(), 0xdeadbeefu);
|
|
test.opcodes = {O::S_MOV_B64, O::V_MOV_B32, O::V_CMP_EQ_U32,
|
|
O::S_CMP_EQ_U32, O::S_CMOV_B64, O::S_CSELECT_B32,
|
|
O::V_LSHLREV_B32, O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = 64;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarAndn2B64SccUsesMaskShadow() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVopc(0xc2, InlineU32(0), 0),
|
|
EncodeSop1(0x04, 0, 106),
|
|
EncodeSop2(0x15, 4, 0, InlineU32(0)),
|
|
EncodeVop1(0x01, 0, InlineU32(1)),
|
|
EncodeSopp(0x04, 1),
|
|
EncodeVop1(0x01, 0, InlineU32(7)),
|
|
};
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarAndn2B64SccUsesMaskShadow",
|
|
code,
|
|
{},
|
|
{7},
|
|
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_ANDN2_B64, O::V_MOV_B32,
|
|
O::S_CBRANCH_SCC0, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarMaskHighWriteInvalidatesProvenance() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVopc(0xc2, Vgpr(0), 0), EncodeSop1(0x04, 0, 106),
|
|
EncodeSMovB32(1, InlineU32(1)), EncodeSop1(0x08, 2, 0),
|
|
EncodeVop1(0x01, 0, InlineU32(1)), EncodeSopp(0x04, 1),
|
|
EncodeVop1(0x01, 0, InlineU32(7)),
|
|
};
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarMaskHighWriteInvalidatesProvenance",
|
|
code,
|
|
{},
|
|
{7},
|
|
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_MOV_B32, O::S_NOT_B64,
|
|
O::V_MOV_B32, O::S_CBRANCH_SCC0, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarSelectB64PreservesMaskProvenance() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
const auto set_scc = [&] {
|
|
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(1)));
|
|
};
|
|
const auto capture_scc = [&](u32 dst) {
|
|
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
AppendSMovLiteral(&code, 0, 0xfffffffeu);
|
|
AppendSMovLiteral(&code, 1, 0xffffffffu);
|
|
code.push_back(EncodeSMovB32(2, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(0)));
|
|
set_scc();
|
|
code.push_back(EncodeSop2(0x0b, 4, 0, 2));
|
|
code.push_back(EncodeSop1(0x08, 6, 4));
|
|
capture_scc(8);
|
|
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 10, 106));
|
|
set_scc();
|
|
code.push_back(EncodeSop2(0x0b, 12, 10, 2));
|
|
code.push_back(EncodeSop1(0x08, 14, 12));
|
|
capture_scc(16);
|
|
|
|
AppendStoreSgpr(&code, 8, 0);
|
|
AppendStoreSgpr(&code, 16, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarSelectB64PreservesMaskProvenance",
|
|
code,
|
|
{},
|
|
{1, 1},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CSELECT_B64, O::S_NOT_B64,
|
|
O::S_CSELECT_B32, O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarWqmB32Masks(u32 wave_size) {
|
|
using O = ShaderOpcode;
|
|
TestCase test;
|
|
test.name = wave_size == 64 ? "ScalarWqmB32MasksWave64" : "ScalarWqmB32MasksWave32";
|
|
auto &code = test.code;
|
|
const auto store_scalar = [&](u32 reg, u32 expected) {
|
|
AppendStoreSgprAtLaneDwordOffset(&code, reg, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
test.expected.insert(test.expected.end(), wave_size, expected);
|
|
};
|
|
const auto store_marker = [&](u32 lo, u32 hi) {
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0,
|
|
static_cast<u32>(test.expected.size()));
|
|
for (u32 lane = 0; lane < wave_size; ++lane) {
|
|
const u32 mask = lane < 32 ? lo : hi;
|
|
test.expected.push_back(((mask >> (lane % 32)) & 1u) != 0 ? 9u : 0u);
|
|
}
|
|
};
|
|
constexpr u32 other_half = 0x2468ace0u;
|
|
constexpr u32 masks[] = {0, 1, 8, 0x10, 0x100, 0x80000000u, 0x12481248u, ~0u};
|
|
for (u32 mask : masks) {
|
|
u32 expanded = 0;
|
|
for (u32 bit = 0; bit < 32; bit += 4) {
|
|
if ((mask & (0xfu << bit)) != 0) expanded |= 0xfu << bit;
|
|
}
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendSMovLiteral(&code, 126, mask);
|
|
AppendSMovLiteral(&code, 127, other_half);
|
|
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(mask == 0 ? 0 : 1)));
|
|
code.push_back(0xbefe097eu); // Captured S_WQM_B32 EXEC_LO, EXEC_LO.
|
|
code.push_back(EncodeSMovB32(20, 126));
|
|
code.push_back(EncodeSMovB32(21, 127));
|
|
code.push_back(EncodeSMovB32(22, 253));
|
|
AppendVMovU32(&code, 2, 9);
|
|
code.push_back(EncodeSop1(0x04, 126, 193));
|
|
store_scalar(20, expanded);
|
|
store_scalar(21, other_half);
|
|
store_scalar(22, mask != 0 ? 1u : 0u);
|
|
store_marker(expanded, other_half);
|
|
}
|
|
// VALU-produced masks keep WQM dynamic and carry provenance through S_MOV_B64.
|
|
// Updating either word must invalidate an old SGPR-pair predicate and preserve
|
|
// the other word. Wave32 still reads/writes the scalar high half independently.
|
|
for (u32 base : {8u, 106u, 126u}) {
|
|
for (u32 half : {0u, 1u}) {
|
|
AppendVMovU32(&code, 2, 0);
|
|
code.push_back(EncodeVop2(0x1b, 1, InlineU32(31), 0));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(3), 1)); // Lanes 3 and 35.
|
|
code.push_back(EncodeSop1(0x04, base, 106));
|
|
code.push_back(EncodeSop1(0x09, base + half, base + half));
|
|
code.push_back(EncodeSMovB32(20, base));
|
|
code.push_back(EncodeSMovB32(21, base + 1));
|
|
code.push_back(EncodeSMovB32(22, 253));
|
|
code.push_back(EncodeSop1(0x04, 126, base));
|
|
AppendVMovU32(&code, 2, 9);
|
|
code.push_back(EncodeSop1(0x04, 126, 193));
|
|
const u32 lo = half == 0 ? 0xfu : 8u;
|
|
const u32 hi = wave_size == 32 ? 0u : half == 1 ? 0xfu : 8u;
|
|
store_scalar(20, lo);
|
|
store_scalar(21, hi);
|
|
store_scalar(22, wave_size == 32 && half == 1 ? 0u : 1u);
|
|
store_marker(lo, hi);
|
|
}
|
|
}
|
|
AppendEnd(&code);
|
|
test.initial.resize(test.expected.size());
|
|
test.opcodes = {O::S_WQM_B32, O::S_MOV_B32, O::S_MOV_B64, O::S_CMP_EQ_U32,
|
|
O::V_CMP_EQ_U32, O::V_MOV_B32, O::V_AND_B32, O::V_ADD_NC_U32,
|
|
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"S_WQM_B32 exec_lo, exec_lo", 9}};
|
|
test.compute_info.threads_num[0] = wave_size;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = wave_size;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarWqmB64SelectsSccDomain() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
const auto capture_scc = [&](u32 dst) {
|
|
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(1, InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x0a, 2, 0));
|
|
capture_scc(4);
|
|
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 6, 106));
|
|
code.push_back(EncodeSop1(0x08, 8, 6));
|
|
code.push_back(EncodeSop1(0x0a, 10, 8));
|
|
capture_scc(12);
|
|
|
|
AppendStoreSgpr(&code, 4, 0);
|
|
AppendStoreSgpr(&code, 12, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarWqmB64SelectsSccDomain",
|
|
code,
|
|
{},
|
|
{1, 1},
|
|
{O::S_MOV_B32, O::S_WQM_B64, O::S_CSELECT_B32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64, O::S_NOT_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarWqmB64PreservesPartialMasks() {
|
|
using O = ShaderOpcode;
|
|
TestCase test;
|
|
test.name = "ScalarWqmB64PreservesPartialMasks";
|
|
auto &code = test.code;
|
|
code.push_back(EncodeSop1(0x04, 16, 126)); // Save full EXEC.
|
|
AppendVMovU32(&code, 1, 3);
|
|
AppendVMovU32(&code, 2, 9);
|
|
u32 offset = 0;
|
|
for (const u32 destination : {126u, 106u, 8u}) {
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 1)); // Only lane 3.
|
|
code.push_back(EncodeSop1(0x04, destination, 106));
|
|
code.push_back(EncodeSop1(0x0a, destination, destination));
|
|
code.push_back(EncodeSop1(0x04, 126, destination));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset);
|
|
code.push_back(EncodeSop1(0x04, 126, 16));
|
|
offset += 64;
|
|
}
|
|
code.push_back(EncodeSMovB32(8, InlineU32(32))); // Raw bits 5 and 33.
|
|
code.push_back(EncodeSMovB32(9, InlineU32(2)));
|
|
code.push_back(EncodeSop1(0x0a, 10, 8));
|
|
code.push_back(EncodeSop1(0x04, 126, 10));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset);
|
|
AppendEnd(&code);
|
|
test.initial.assign(256, 0xdeadbeef);
|
|
test.expected = test.initial;
|
|
for (u32 lane = 0; lane < 64; ++lane) {
|
|
for (u32 region = 0; region < 3; ++region) {
|
|
if (lane < 4) test.expected[region * 64 + lane] = 9;
|
|
}
|
|
if ((lane >= 4 && lane < 8) || (lane >= 32 && lane < 36)) {
|
|
test.expected[192 + lane] = 9;
|
|
}
|
|
}
|
|
test.opcodes = {O::S_MOV_B64, O::S_MOV_B32, O::S_WQM_B64, O::V_MOV_B32,
|
|
O::V_CMP_EQ_U32, O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarMaskProvenanceOverlapAndMixedBinary() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
const auto capture_scc = [&](u32 dst) {
|
|
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
|
|
};
|
|
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 0, 106));
|
|
code.push_back(EncodeSop1(0x04, 1, 0));
|
|
code.push_back(EncodeSop1(0x08, 3, 1));
|
|
capture_scc(5);
|
|
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 8, 106));
|
|
code.push_back(EncodeSMovB32(10, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(11, InlineU32(0)));
|
|
code.push_back(EncodeSop2(0x19, 12, 8, 10));
|
|
capture_scc(14);
|
|
|
|
AppendStoreSgpr(&code, 5, 0);
|
|
AppendStoreSgpr(&code, 14, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarMaskProvenanceOverlapAndMixedBinary",
|
|
code,
|
|
{},
|
|
{1, 1},
|
|
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_NOT_B64, O::S_CSELECT_B32,
|
|
O::S_MOV_B32, O::S_NAND_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarLiteral() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeVop1(0x01, 0, 0));
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"ScalarLiteral",
|
|
code,
|
|
{},
|
|
{0x12345678u},
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMoves() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x00, 0, 0),
|
|
EncodeVop1(0x01, 0, InlineU32(5)),
|
|
EncodeVop1(0x01, 2, Vgpr(0)),
|
|
};
|
|
AppendBufferStoreDword(&code, 2, 30);
|
|
AppendEnd(&code);
|
|
return {"VectorRegisterMoves",
|
|
code,
|
|
{},
|
|
{5},
|
|
{O::V_NOP, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3MoveAppliesFloatSourceModifiers() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 24, 0x40000000u);
|
|
AppendVop3(&code, 0x181, 1, 24, 0, 0, 0, 0, false, 0, 0x1);
|
|
AppendStoreVgpr(&code, 1, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3MoveAppliesFloatSourceModifiers";
|
|
test.code = code;
|
|
test.expected = {0xc0000000u};
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpFNegate"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorIntegerOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xfffffff8u);
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(5)));
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(7)));
|
|
AppendVMovLiteral(&code, 10, 0x01000001u);
|
|
code.push_back(EncodeVop1(0x01, 11, InlineU32(2)));
|
|
AppendVMovLiteral(&code, 12, 0x0f0f0f0fu);
|
|
AppendVMovLiteral(&code, 13, 0x33333333u);
|
|
AppendVMovLiteral(&code, 14, 0xfffffff0u);
|
|
code.push_back(EncodeVop1(0x01, 15, InlineU32(4)));
|
|
code.push_back(EncodeVop1(0x01, 29, InlineU32(15)));
|
|
|
|
code.push_back(EncodeVop2(0x11, 2, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x12, 3, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x13, 5, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x14, 6, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x25, 7, Vgpr(4), 1));
|
|
code.push_back(EncodeVop2(0x26, 8, Vgpr(4), 1));
|
|
code.push_back(EncodeVop2(0x27, 9, InlineU32(3), 4));
|
|
code.push_back(EncodeVop2(0x26, 35, 249, 11));
|
|
code.push_back(EncodeVop2Sdwa(12, 6, 0, 0, 6));
|
|
code.push_back(EncodeVop2(0x27, 36, 249, 11));
|
|
code.push_back(EncodeVop2Sdwa(12, 6, 0, 0, 6));
|
|
code.push_back(EncodeVop2(0x0b, 16, Vgpr(10), 11));
|
|
code.push_back(EncodeVop2(0x09, 37, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x1b, 17, Vgpr(12), 13));
|
|
code.push_back(EncodeVop2(0x1c, 18, Vgpr(12), 13));
|
|
code.push_back(EncodeVop2(0x1d, 19, Vgpr(12), 13));
|
|
code.push_back(EncodeVop2(0x1e, 20, Vgpr(12), 13));
|
|
code.push_back(EncodeVop2(0x19, 21, Vgpr(11), 15));
|
|
code.push_back(EncodeVop2(0x1a, 22, InlineU32(4), 11));
|
|
code.push_back(EncodeVop2(0x15, 23, Vgpr(14), 15));
|
|
code.push_back(EncodeVop2(0x16, 24, InlineU32(4), 14));
|
|
code.push_back(EncodeVop2(0x17, 25, Vgpr(14), 15));
|
|
code.push_back(EncodeVop2(0x18, 26, InlineU32(4), 14));
|
|
code.push_back(EncodeVop1(0x37, 27, Vgpr(12)));
|
|
code.push_back(EncodeVop1(0x38, 28, Vgpr(29)));
|
|
code.push_back(EncodeVop1(0x3a, 30, Vgpr(11)));
|
|
code.push_back(EncodeVop1(0x39, 32, Vgpr(11)));
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(4), 4));
|
|
code.push_back(EncodeVop2(0x01, 33, InlineU32(3), 4));
|
|
code.push_back(EncodeVop2(0x11, 34, 249, 4));
|
|
code.push_back(EncodeVop2Sdwa(0));
|
|
|
|
const u32 results[] = {2, 3, 5, 6, 7, 8, 9, 35, 36, 16, 37, 17, 18, 19,
|
|
20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 32, 33, 34};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorIntegerOps",
|
|
code,
|
|
{},
|
|
{0xfffffff8u, 5, 5, 0xfffffff8u, 12,
|
|
2, 4, 13, 0xfffffff3u, 2,
|
|
0xffffffd8u, 0x03030303u, 0x3f3f3f3fu, 0x3c3c3c3cu, 0xc3c3c3c3u,
|
|
32, 32, 0x0fffffffu, 0x0fffffffu, 0xffffffffu,
|
|
0xffffffffu, 0xf0f0f0f0u, 0xf0000000u, 1, 30,
|
|
7, 0xfffffff8u},
|
|
{O::V_MOV_B32, O::V_MIN_I32, O::V_MAX_I32,
|
|
O::V_MIN_U32, O::V_MAX_U32, O::V_ADD_NC_U32,
|
|
O::V_SUB_NC_U32, O::V_SUBREV_NC_U32, O::V_MUL_U32_U24,
|
|
O::V_MUL_I32_I24, O::V_AND_B32, O::V_OR_B32,
|
|
O::V_XOR_B32, O::V_XNOR_B32, O::V_LSHL_B32,
|
|
O::V_LSHLREV_B32, O::V_LSHR_B32, O::V_LSHRREV_B32,
|
|
O::V_ASHR_I32, O::V_ASHRREV_I32, O::V_NOT_B32,
|
|
O::V_BFREV_B32, O::V_FFBL_B32, O::V_FFBH_U32,
|
|
O::V_CMP_EQ_U32, O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorFfbhI32NativeAndVop3OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
constexpr std::array inputs = {0x00000000u, 0x40000000u, 0x80000000u,
|
|
0x0fffffffu, 0xffff0000u, 0xfffffffeu,
|
|
0xffffffffu, 0x80000000u};
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < inputs.size() - 1u; i++) {
|
|
AppendVMovU32(&code, 30, i * 4u);
|
|
AppendBufferLoadDword(&code, 14, 30);
|
|
code.push_back(0x7e1c770eu); // v_ffbh_i32 v14, v14
|
|
AppendStoreVgpr(&code, 14, i);
|
|
}
|
|
AppendVMovU32(&code, 30, (inputs.size() - 1u) * 4u);
|
|
AppendBufferLoadDword(&code, 14, 30);
|
|
AppendVop3(&code, 0x1bbu, 15, Vgpr(14), 0);
|
|
AppendStoreVgpr(&code, 15, inputs.size() - 1u);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorFfbhI32NativeAndVop3OnGpu";
|
|
test.code = std::move(code);
|
|
test.initial.assign(inputs.begin(), inputs.end());
|
|
test.expected = {0xffffffffu, 1u, 1u, 4u, 16u, 31u, 0xffffffffu, 1u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FFBH_I32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_FFBH_I32 v14, v14", inputs.size() - 1u},
|
|
{"V_FFBH_I32 v15, v14", 1u}};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop1SdwaFfblCapturedHighWordSource() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 2, 30);
|
|
code.push_back(0x7e2074f9u);
|
|
code.push_back(0x00050602u); // v_ffbl_b32 v16, v2.word1
|
|
AppendStoreVgpr(&code, 16, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop1SdwaFfblCapturedHighWordSource";
|
|
test.code = std::move(code);
|
|
test.initial = {0x00400000u};
|
|
test.expected = {6u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_FFBL_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_FFBL_B32 v16, v2.sdwa(sel=5,sext=0)", 1}};
|
|
test.ir_counts = {{" = BitFieldUExtract ", 1}, {" = FindILsb32 ", 1}};
|
|
test.required_spirv = {"OpBitFieldUExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop1SdwaNotCapturedByte0Source() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 2, 30);
|
|
code.push_back(0x7e046ef9u);
|
|
code.push_back(0x00000602u); // v_not_b32 v2, v2.byte0
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop1SdwaNotCapturedByte0Source";
|
|
test.code = std::move(code);
|
|
test.initial = {0x12345678u};
|
|
test.expected = {0xffffff87u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_NOT_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_NOT_B32 v2, v2.sdwa(sel=0,sext=0)", 1}};
|
|
test.ir_counts = {{" = BitFieldUExtract ", 1}, {" = BitwiseNot32 ", 1}};
|
|
test.required_spirv = {"OpBitFieldUExtract", "OpNot"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaSubNcExactByte2Destination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 11, 2);
|
|
code.push_back(0x4c1616f9u);
|
|
code.push_back(0x0686128du);
|
|
AppendStoreVgpr(&code, 11, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaSubNcExactByte2Destination";
|
|
test.code = code;
|
|
test.expected = {0x000b0002u};
|
|
test.opcodes = {O::V_MOV_B32, O::V_SUB_NC_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaAddNcCapturedHighWordDestination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 0, 30);
|
|
code.push_back(EncodeVop1(0x02, 106, Vgpr(0)));
|
|
AppendVMovLiteral(&code, 97, 0x00030004u);
|
|
AppendVMovLiteral(&code, 4, 0xa1b2c3d4u);
|
|
code.push_back(0x4a08c2f9u);
|
|
code.push_back(0x0686156au);
|
|
AppendStoreVgpr(&code, 4, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaAddNcCapturedHighWordDestination";
|
|
test.code = std::move(code);
|
|
test.initial = {0x00010002u, 0};
|
|
test.expected = {0x00010002u, 0x0006c3d4u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::V_MOV_B32,
|
|
O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpIAdd", "OpShiftLeftLogical", "OpBitwiseOr"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaAshrrevCapturedWord0SignExtends() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 1, 30);
|
|
code.push_back(0x303202f9u);
|
|
code.push_back(0x0c860686u); // v_ashrrev_i32 v25, 6, sign_extend(v1.word0)
|
|
AppendStoreVgpr(&code, 25, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaAshrrevCapturedWord0SignExtends";
|
|
test.code = std::move(code);
|
|
test.initial = {0xabcd8000u, 0};
|
|
test.expected = {0xabcd8000u, 0xfffffe00u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_ASHRREV_I32, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_ASHRREV_I32 v25, 6, v1.sdwa(sel=4,sext=1)", 1}};
|
|
test.ir_counts = {{" = BitFieldSExtract ", 1},
|
|
{" = ShiftRightArithmetic32 ", 1}};
|
|
test.required_spirv = {"OpBitFieldSExtract", "OpShiftRightArithmetic"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaLshrrevCapturedByte1Source() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 106, 4);
|
|
AppendBufferLoadDword(&code, 8, 30);
|
|
code.push_back(0x2c1210f9u);
|
|
code.push_back(0x0186066au); // v_lshrrev_b32 v9, vcc_lo, v8.byte1
|
|
AppendStoreVgpr(&code, 9, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaLshrrevCapturedByte1Source";
|
|
test.code = std::move(code);
|
|
test.initial = {0x12348678u, 0};
|
|
test.expected = {0x12348678u, 8};
|
|
test.opcodes = {O::S_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_LSHRREV_B32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {
|
|
{"V_LSHRREV_B32 v9, vcc_lo, v8.sdwa(sel=1,sext=0)", 1}};
|
|
test.ir_counts = {{" = BitFieldUExtract ", 1},
|
|
{" = ShiftRightLogical32 ", 1}};
|
|
test.required_spirv = {"OpBitFieldUExtract", "OpShiftRightLogical"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaSubNcPreservesByteAndWordDestinations() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 3);
|
|
for (u32 dst = 10; dst <= 15; dst++) {
|
|
AppendVMovLiteral(&code, dst, 0xa1b2c3d4u);
|
|
}
|
|
for (u32 sel = 0; sel <= 5; sel++) {
|
|
code.push_back(EncodeVop2(0x26, 10 + sel, 249, 0));
|
|
code.push_back(
|
|
EncodeVop2Sdwa(InlineU32(20), sel, 2, 6, 6, 0, 0, 0, 0, 0, 0, 1));
|
|
AppendStoreVgpr(&code, 10 + sel, sel);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop2SdwaSubNcPreservesByteAndWordDestinations",
|
|
code,
|
|
{},
|
|
{0xa1b2c311u, 0xa1b211d4u, 0xa111c3d4u, 0x11b2c3d4u, 0xa1b20011u,
|
|
0x0011c3d4u},
|
|
{O::V_MOV_B32, O::V_SUB_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop3CvtPkI16I32Captured() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 7, 30);
|
|
AppendVMovU32(&code, 30, 4);
|
|
AppendBufferLoadDword(&code, 16, 30);
|
|
code.push_back(0xd76b0005u);
|
|
code.push_back(0x00022107u); // v_cvt_pk_i16_i32 v5, v7, v16
|
|
AppendStoreVgpr(&code, 5, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop3CvtPkI16I32Captured";
|
|
test.code = std::move(code);
|
|
test.initial = {0x00018001u, 0xfffe7ffeu, 0};
|
|
test.expected = {0x00018001u, 0xfffe7ffeu, 0x7ffe8001u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_MOV_B32, O::V_CVT_PK_I16_I32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"0x00000014: V_CVT_PK_I16_I32 v5, v7, v16\n", 1}};
|
|
test.ir_counts = {{" = BitwiseAnd32 ", 2},
|
|
{" = ShiftLeftLogical32 ", 1},
|
|
{" = BitwiseOr32 ", 1}};
|
|
test.required_spirv = {"OpBitwiseAnd", "OpShiftLeftLogical", "OpBitwiseOr"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop3Med3I16Captured() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 5, 0x8ad003e8u); // high=-30000, low=1000
|
|
AppendSMovLiteral(&code, 25, 0xb1e04e20u); // high=-20000, low=20000
|
|
AppendSMovLiteral(&code, 27, 0x7530fc18u); // high=30000, low=-1000
|
|
AppendVMovLiteral(&code, 3, 0x1234cafeu);
|
|
code.push_back(0xd7584803u);
|
|
code.push_back(0x006c3305u); // v_med3_i16 v3.hi, v5.hi, s25.lo, s27.lo
|
|
AppendStoreVgpr(&code, 3, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop3Med3I16Captured";
|
|
test.code = std::move(code);
|
|
test.expected = {0xfc18cafeu};
|
|
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_MED3_I16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"0x00000020: V_MED3_I16 v3.sdwa(sel=5,sext=0), "
|
|
"v5.opsel(lo=1,hi=0,neghi=0), s25, s27\n",
|
|
1}};
|
|
test.required_spirv = {"OpSLessThan"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop2SdwaMinU32PreservesWordDestination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 10, 0xa1b2c3d4u);
|
|
AppendVMovU32(&code, 12, 7);
|
|
code.push_back(0x261418f9u);
|
|
code.push_back(0x0686149fu);
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendVMovLiteral(&code, 10, 0xa1b2c3d4u);
|
|
AppendVMovU32(&code, 12, 64);
|
|
code.push_back(0x261418f9u);
|
|
code.push_back(0x0686149fu);
|
|
AppendStoreVgpr(&code, 10, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaMinU32PreservesWordDestination";
|
|
test.code = code;
|
|
test.expected = {0xa1b20007u, 0xa1b2001fu};
|
|
test.opcodes = {O::V_MOV_B32, O::V_MIN_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpULessThan", "OpSelect"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorShiftCountsMaskLowBits() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 1);
|
|
AppendVMovU32(&code, 1, 32);
|
|
AppendVMovU32(&code, 2, 33);
|
|
AppendVMovLiteral(&code, 3, 0x80000000u);
|
|
code.push_back(EncodeVop2(0x19, 10, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x1a, 11, Vgpr(2), 0));
|
|
code.push_back(EncodeVop2(0x15, 12, Vgpr(3), 1));
|
|
code.push_back(EncodeVop2(0x16, 13, Vgpr(2), 3));
|
|
code.push_back(EncodeVop2(0x17, 14, Vgpr(3), 1));
|
|
code.push_back(EncodeVop2(0x18, 15, Vgpr(2), 3));
|
|
|
|
for (u32 i = 0; i < 6u; i++) {
|
|
AppendStoreVgpr(&code, 10u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorShiftCountsMaskLowBits",
|
|
code,
|
|
{},
|
|
{1, 2, 0x80000000u, 0x40000000u, 0x80000000u, 0xc0000000u},
|
|
{O::V_MOV_B32, O::V_LSHL_B32, O::V_LSHLREV_B32, O::V_LSHR_B32,
|
|
O::V_LSHRREV_B32, O::V_ASHR_I32, O::V_ASHRREV_I32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3LshrrevB64Captured() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 106, 0x89abcdefu);
|
|
AppendSMovLiteral(&code, 107, 0x01234567u);
|
|
AppendVMovU32(&code, 12, 68);
|
|
code.push_back(0xd7000001u);
|
|
code.push_back(0x0000d50cu); // v_lshrrev_b64 v[1:2], v12, vcc
|
|
AppendStoreVgpr(&code, 1, 0);
|
|
AppendStoreVgpr(&code, 2, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3LshrrevB64Captured";
|
|
test.code = std::move(code);
|
|
test.expected = {0x789abcdeu, 0x00123456u};
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::V_LSHRREV_B64,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpShiftRightLogical", "OpSelect"};
|
|
test.forbidden_spirv = {"OpTypeInt 64"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3LshlrevB64Captured() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 3, 30);
|
|
code.push_back(0xd6ff0021u);
|
|
code.push_back(0x00010303u); // v_lshlrev_b64 v[33:34], v3, 1
|
|
AppendStoreVgpr(&code, 33, 1);
|
|
AppendStoreVgpr(&code, 34, 2);
|
|
code.push_back(EncodeVop2(0x03, 3, InlineU32(31), 3));
|
|
code.push_back(0xd6ff0021u);
|
|
code.push_back(0x00010303u);
|
|
AppendStoreVgpr(&code, 33, 3);
|
|
AppendStoreVgpr(&code, 34, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3LshlrevB64Captured";
|
|
test.code = std::move(code);
|
|
test.initial = {33u, 0u, 0u, 0u, 0u};
|
|
test.expected = {33u, 0u, 2u, 1u, 0u};
|
|
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_LSHLREV_B64, O::V_ADD_NC_U32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpShiftLeftLogical", "OpSelect"};
|
|
test.forbidden_spirv = {"OpTypeInt 64"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3IntegerOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 0, InlineU32(2)));
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(3)));
|
|
code.push_back(EncodeVop1(0x01, 2, InlineU32(4)));
|
|
AppendVMovLiteral(&code, 3, 0xfffffff8u);
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(5)));
|
|
AppendVMovLiteral(&code, 5, 0x11223344u);
|
|
AppendVMovLiteral(&code, 6, 0x55667788u);
|
|
AppendVMovLiteral(&code, 7, 0x0f0f0f0fu);
|
|
AppendVMovLiteral(&code, 8, 0x33333333u);
|
|
AppendVMovLiteral(&code, 9, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 14, 0x00f00000u);
|
|
code.push_back(EncodeVop1(0x01, 15, InlineU32(8)));
|
|
AppendVMovLiteral(&code, 30, 0x00010000u);
|
|
AppendVMovLiteral(&code, 31, 0x00010000u);
|
|
|
|
AppendVop3(&code, 0x36d, 10, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x152, 11, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x155, 12, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x158, 13, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x153, 16, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x156, 17, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x159, 18, Vgpr(3), Vgpr(4), Vgpr(0));
|
|
AppendVop3(&code, 0x15d, 19, Vgpr(0), Vgpr(4), Vgpr(2));
|
|
AppendVop3(&code, 0x346, 20, Vgpr(1), Vgpr(0), Vgpr(2));
|
|
AppendVop3(&code, 0x347, 21, Vgpr(0), Vgpr(1), Vgpr(0));
|
|
AppendVop3(&code, 0x345, 22, Vgpr(7), Vgpr(8), Vgpr(0));
|
|
AppendVop3(&code, 0x36f, 23, Vgpr(1), Vgpr(2), Vgpr(0));
|
|
AppendVop3(&code, 0x371, 24, Vgpr(7), Vgpr(8), Vgpr(2));
|
|
AppendVop3(&code, 0x372, 25, Vgpr(7), Vgpr(8), Vgpr(2));
|
|
AppendVop3(&code, 0x178, 26, Vgpr(7), Vgpr(8), Vgpr(2));
|
|
AppendVop3(&code, 0x148, 27, Vgpr(14), InlineU32(20), Vgpr(2));
|
|
AppendVop3(&code, 0x149, 28, Vgpr(3), Vgpr(2), Vgpr(2));
|
|
AppendVop3(&code, 0x14a, 29, Vgpr(8), Vgpr(7), Vgpr(9));
|
|
AppendVop3(&code, 0x14e, 32, Vgpr(5), Vgpr(6), Vgpr(15));
|
|
AppendVop3(&code, 0x363, 33, Vgpr(2), Vgpr(15));
|
|
AppendVop3(&code, 0x169, 34, Vgpr(30), Vgpr(31));
|
|
AppendVop3(&code, 0x16a, 35, Vgpr(30), Vgpr(31));
|
|
AppendVop3(&code, 0x16b, 36, Vgpr(30), Vgpr(31));
|
|
AppendVop3(&code, 0x16c, 44, Vgpr(3), Vgpr(30));
|
|
AppendVop3(&code, 0x142, 37, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x143, 38, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x30f, 39, Vgpr(0), Vgpr(1));
|
|
AppendVop3(&code, 0x310, 40, Vgpr(0), Vgpr(1));
|
|
AppendVop3(&code, 0x319, 41, Vgpr(0), Vgpr(1));
|
|
|
|
const u32 results[] = {10, 11, 12, 13, 16, 17, 18, 19, 20, 21,
|
|
22, 23, 24, 25, 26, 27, 28, 29, 32, 33,
|
|
34, 35, 36, 44, 37, 38, 39, 40, 41};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3IntegerOps",
|
|
code,
|
|
{},
|
|
{9,
|
|
0xfffffff8u,
|
|
5,
|
|
2,
|
|
2,
|
|
0xfffffff8u,
|
|
5,
|
|
7,
|
|
16,
|
|
20,
|
|
0x3c3c3c3eu,
|
|
0x32,
|
|
0x03030307u,
|
|
0x3f3f3f3fu,
|
|
0x3c3c3c38u,
|
|
0x0fu,
|
|
0xffffffffu,
|
|
0x8b8b8b8bu,
|
|
0x44556677u,
|
|
0x00000f00u,
|
|
0,
|
|
1,
|
|
0,
|
|
0xffffffffu,
|
|
10,
|
|
10,
|
|
5,
|
|
0xffffffffu,
|
|
1},
|
|
{O::V_MOV_B32, O::V_ADD3_U32, O::V_MIN3_I32,
|
|
O::V_MAX3_I32, O::V_MED3_I32, O::V_MIN3_U32,
|
|
O::V_MAX3_U32, O::V_MED3_U32, O::V_SAD_U32,
|
|
O::V_LSHL_ADD_U32, O::V_ADD_LSHL_U32, O::V_XAD_U32,
|
|
O::V_LSHL_OR_B32, O::V_AND_OR_B32, O::V_OR3_B32,
|
|
O::V_XOR3_B32, O::V_BFE_U32, O::V_BFE_I32,
|
|
O::V_BFI_B32, O::V_ALIGNBIT_B32, O::V_BFM_B32,
|
|
O::V_MUL_LO_U32, O::V_MUL_HI_U32, O::V_MUL_LO_I32,
|
|
O::V_MUL_HI_I32, O::V_MAD_I32_I24, O::V_MAD_U32_U24,
|
|
O::V_ADD_I32, O::V_SUB_I32, O::V_SUBREV_I32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorBfeI32SignExtendsField() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
for (u32 vgpr = 0; vgpr < 6; vgpr++) {
|
|
AppendVMovU32(&code, 30, vgpr * sizeof(u32));
|
|
AppendBufferLoadDword(&code, vgpr, 30);
|
|
}
|
|
AppendVop3(&code, 0x149, 10, Vgpr(0), Vgpr(2), Vgpr(3));
|
|
AppendVop3(&code, 0x149, 11, Vgpr(1), Vgpr(4), Vgpr(5));
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendStoreVgpr(&code, 11, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorBfeI32SignExtendsField";
|
|
test.code = std::move(code);
|
|
test.initial = {0x80000000u, 0xfffffff8u, 31, 1, 3, 4};
|
|
test.expected = {0xffffffffu, 0xffffffffu};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_BFE_I32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpBitFieldSExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorAlignByteUsesFiveBitByteOffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x11223344u);
|
|
AppendVMovLiteral(&code, 1, 0x55667788u);
|
|
constexpr u32 offsets[] = {0, 1, 3, 4, 5, 7, 8, 31};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(offsets)); i++) {
|
|
AppendVMovU32(&code, 2, offsets[i]);
|
|
AppendVop3(&code, 0x14f, 10u + i, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendStoreVgpr(&code, 10u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"VectorAlignByteUsesFiveBitByteOffset",
|
|
code,
|
|
{},
|
|
{0x55667788u, 0x44556677u, 0x22334455u, 0x11223344u, 0x00112233u,
|
|
0x00000011u, 0u, 0u},
|
|
{O::V_MOV_B32, O::V_ALIGNBYTE_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCarryAndBitCountOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xffffffffu);
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(0)));
|
|
AppendVMovLiteral(&code, 3, 0x0000f0f0u);
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(5)));
|
|
|
|
code.push_back(EncodeVopc(0xc7, Vgpr(1), 1));
|
|
code.push_back(EncodeVop2(0x28, 2, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x22, 5, Vgpr(3), 4));
|
|
AppendVMovLiteral(&code, 6, 0xffffffffu);
|
|
code.push_back(EncodeVop2(0x23, 7, Vgpr(6), 4));
|
|
code.push_back(EncodeVop2(0x24, 8, Vgpr(6), 4));
|
|
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
AppendStoreSgpr(&code, 106, 1);
|
|
AppendStoreVgpr(&code, 5, 2);
|
|
AppendStoreVgpr(&code, 7, 3);
|
|
AppendStoreVgpr(&code, 8, 4);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCarryAndBitCountOps",
|
|
code,
|
|
{},
|
|
{0, 1, 13, 5, 5},
|
|
{O::V_MOV_B32, O::V_CMP_T_U32, O::V_ADDC_U32, O::V_BCNT_U32_B32,
|
|
O::V_MBCNT_LO_U32_B32, O::V_MBCNT_HI_U32_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMbcntUsesThreadMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0),
|
|
EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1),
|
|
EncodeVop2(0x1a, 3, InlineU32(2), 1),
|
|
};
|
|
AppendVMovLiteral(&code, 2, 0xffffffffu);
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(0)));
|
|
code.push_back(EncodeVop2(0x23, 5, Vgpr(2), 4));
|
|
code.push_back(EncodeVop2(0x24, 6, Vgpr(2), 5));
|
|
AppendBufferStoreDword(&code, 6, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorMbcntUsesThreadMask";
|
|
test.code = code;
|
|
test.expected = {0, 1, 2, 3, 0, 1, 2, 3};
|
|
test.opcodes = {O::V_MOV_B32,
|
|
O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32,
|
|
O::V_MBCNT_LO_U32_B32,
|
|
O::V_MBCNT_HI_U32_B32,
|
|
O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorAddcUsesPerLaneCarryIn() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0),
|
|
EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1),
|
|
EncodeVopc(0xc7, Vgpr(0), 0),
|
|
EncodeVop1(0x01, 2, InlineU32(0)),
|
|
EncodeVop2(0x28, 3, Vgpr(2), 2),
|
|
EncodeVop2(0x1a, 4, InlineU32(2), 1),
|
|
};
|
|
AppendBufferStoreDword(&code, 3, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorAddcUsesPerLaneCarryIn";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(8, 1);
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::V_CMP_T_U32, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorAddcWritesPerLaneCarryOut() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {EncodeVop1(0x01, 1, Vgpr(0)),
|
|
EncodeVop2(0x1a, 4, InlineU32(2), 1),
|
|
EncodeVopc(0xc7, Vgpr(0), 0)};
|
|
AppendBufferLoadDword(&code, 26, 4);
|
|
code.push_back(0x500434f9u);
|
|
code.push_back(0x0c860680u);
|
|
code.push_back(EncodeVop1(0x01, 6, 106));
|
|
AppendBufferStoreDword(&code, 6, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorAddcWritesPerLaneCarryOut";
|
|
test.code = code;
|
|
test.initial = {0x0000ffffu, 0u, 0x0000ffffu, 0u};
|
|
test.expected = {5u, 5u, 5u, 5u};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_CMP_T_U32,
|
|
O::BUFFER_LOAD_DWORD, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.decoded_counts = {
|
|
{"V_ADDC_U32 v2, vcc_lo, 0, v26.sdwa(sel=4,sext=1), vcc_lo", 1}};
|
|
test.ir_counts = {{" = BitFieldSExtract ", 1}, {" = IAddCarry32 ", 2}};
|
|
test.required_spirv = {"OpBitFieldSExtract"};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorSubCoCiU32CompactAndVop3() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
|
|
AppendBufferLoadDword(&code, 1, 4);
|
|
code.push_back(EncodeVop2(0x25, 5, InlineU32(16), 4));
|
|
AppendBufferLoadDword(&code, 2, 5);
|
|
code.push_back(EncodeVop2(0x1b, 3, InlineU32(1), 0));
|
|
code.push_back(EncodeVopc(0xc5, InlineU32(0), 3));
|
|
code.push_back(EncodeSop1(0x04, 20, 106));
|
|
code.push_back(EncodeVop2(0x29, 10, Vgpr(1), 2));
|
|
AppendVMovU32(&code, 6, 1);
|
|
code.push_back(EncodeVop2(0x01, 11, InlineU32(0), 6));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 10, 0, 0);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 11, 0, 4);
|
|
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(0), 6));
|
|
AppendVop3B(&code, 0x129u, 12, 22, Vgpr(1), Vgpr(2), 20);
|
|
AppendVop3(&code, 0x101u, 13, InlineU32(0), Vgpr(6), 22);
|
|
code.push_back(EncodeVop2(0x01, 14, InlineU32(0), 6));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 12, 0, 8);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 13, 0, 12);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 14, 0, 16);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorSubCoCiU32CompactAndVop3";
|
|
test.code = std::move(code);
|
|
test.initial = {5u, 5u, 3u, 3u, 3u, 3u, 5u, 3u, 0u, 0u,
|
|
0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u};
|
|
test.expected = {2u, 1u, 0xfffffffeu, 0xffffffffu, 0u, 0u, 1u,
|
|
1u, 2u, 1u, 0xfffffffeu, 0xffffffffu, 0u, 0u,
|
|
1u, 1u, 0u, 0u, 0u, 0u};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::V_AND_B32, O::V_CMP_NE_U32, O::V_CMP_EQ_U32,
|
|
O::V_SUB_CO_CI_U32, O::V_CNDMASK_B32, O::S_MOV_B64,
|
|
O::BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_SUB_CO_CI_U32", 2}};
|
|
test.required_spirv = {"OpISub", "OpUGreaterThan", "OpLogicalOr"};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = 32;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorSubrevCoCiU32ExactRawOnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 24, Vgpr(0)));
|
|
code.push_back(EncodeSop1(0x04, 20, 126)); // s_mov_b64 s[20:21], exec
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(2)));
|
|
code.push_back(EncodeVop1(0x01, 2, InlineU32(1)));
|
|
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
|
|
code.push_back(EncodeVop2(0x25, 5, InlineU32(16), 4));
|
|
code.push_back(EncodeVopc(0xc1, Vgpr(0), 1));
|
|
code.push_back(EncodeSop1(0x04, 22, 106)); // s_mov_b64 s[22:23], vcc
|
|
code.push_back(EncodeVopc(0xc5, InlineU32(0), 0));
|
|
AppendVMovLiteral(&code, 0, 0xdeadbeefu);
|
|
code.push_back(EncodeSop1(0x04, 126, 106)); // s_mov_b64 exec, vcc
|
|
code.push_back(EncodeSop1(0x04, 106, 22)); // s_mov_b64 vcc, s[22:23]
|
|
code.push_back(0x54003080u); // v_subrev_co_ci_u32 v0, vcc, 0, v24, vcc
|
|
code.push_back(EncodeSop1(0x04, 126, 20)); // s_mov_b64 exec, s[20:21]
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 2));
|
|
AppendBufferStoreDword(&code, 0, 4);
|
|
AppendBufferStoreDword(&code, 3, 5);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorSubrevCoCiU32ExactRawOnGpu";
|
|
test.code = code;
|
|
test.expected = {0xdeadbeefu, 0, 2, 3, 0, 0, 0, 0};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32, O::V_CMP_LT_U32,
|
|
O::S_MOV_B64, O::V_CMP_NE_U32,
|
|
O::V_SUBREV_CO_CI_U32, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpISub", "OpUGreaterThan"};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3BSubrevCoCiUsesEncodedMasks() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 24, Vgpr(0)));
|
|
code.push_back(EncodeVop2(0x25, 24, InlineU32(3), 24));
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(2)));
|
|
code.push_back(EncodeVop1(0x01, 2, InlineU32(1)));
|
|
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
|
|
code.push_back(EncodeVop2(0x25, 5, InlineU32(16), 4));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(1), 0));
|
|
code.push_back(EncodeSop1(0x04, 20, 106)); // s_mov_b64 s[20:21], vcc
|
|
AppendSMovLiteral(&code, 106, 0);
|
|
AppendSMovLiteral(&code, 107, 0);
|
|
AppendVop3B(&code, 0x12au, 10, 22, InlineU32(5), Vgpr(24), 20);
|
|
AppendVop3(&code, 0x101u, 3, InlineU32(0), Vgpr(2), 22);
|
|
AppendBufferStoreDword(&code, 10, 4);
|
|
AppendBufferStoreDword(&code, 3, 5);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3BSubrevCoCiUsesEncodedMasks";
|
|
test.code = code;
|
|
test.expected = {0xfffffffeu, 0xfffffffeu, 0, 1, 1, 1, 0, 0};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64, O::S_MOV_B32,
|
|
O::V_SUBREV_CO_CI_U32, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3BCarryOutWritesSgprMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0xffffffffu);
|
|
AppendVMovU32(&code, 2, 1);
|
|
AppendVMovU32(&code, 3, 0);
|
|
|
|
AppendVop3B(&code, 0x30fu, 10, 0, Vgpr(1), Vgpr(2));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 0, 0, 0);
|
|
AppendVop3B(&code, 0x310u, 11, 0, Vgpr(3), Vgpr(2));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 0, 0, 4);
|
|
AppendVop3B(&code, 0x319u, 12, 0, Vgpr(2), Vgpr(3));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 0, 0, 8);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3BCarryOutWritesSgprMask";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(12, 15u);
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
|
|
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3BCarryOutUsesEncodedSdst() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0xffffffffu);
|
|
AppendVMovU32(&code, 2, 1);
|
|
AppendVMovU32(&code, 3, 0);
|
|
|
|
AppendVop3B(&code, 0x30fu, 10, 20, Vgpr(1), Vgpr(2));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 20, 0, 0);
|
|
AppendVop3B(&code, 0x310u, 11, 22, Vgpr(3), Vgpr(2));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 22, 0, 4);
|
|
AppendVop3B(&code, 0x319u, 12, 24, Vgpr(2), Vgpr(3));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 24, 0, 8);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3BCarryOutUsesEncodedSdst";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(12, 15u);
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
|
|
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3BSubCoU32UsesRdna2Opcode310() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovU32(&code, 2, 1);
|
|
AppendVMovU32(&code, 3, 1);
|
|
AppendVMovLiteral(&code, 4, 0xffffffffu);
|
|
AppendVMovLiteral(&code, 5, 0x80000000u);
|
|
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(1), 0));
|
|
code.push_back(EncodeVop2(0x01, 1, Vgpr(1), Vgpr(3)));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(2), 0));
|
|
code.push_back(EncodeVop2(0x01, 1, Vgpr(1), Vgpr(4)));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(3), 0));
|
|
code.push_back(EncodeVop2(0x01, 1, Vgpr(1), Vgpr(5)));
|
|
code.push_back(EncodeVop2(0x01, 2, Vgpr(2), Vgpr(4)));
|
|
|
|
AppendVop3B(&code, 0x310u, 10, 20, Vgpr(1), Vgpr(2));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 10, 0, 0);
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 20, 0, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorVop3BSubCoU32UsesRdna2Opcode310";
|
|
test.code = code;
|
|
test.expected = {0xffffffffu, 0, 0xfffffffeu, 0x80000001u, 9, 9, 9, 9};
|
|
test.opcodes = {O::V_MOV_B32, O::V_CMP_EQ_U32, O::V_CNDMASK_B32,
|
|
O::V_SUB_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorMadU64U32UnsignedCarryOut() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0xffffffffu);
|
|
AppendVMovU32(&code, 2, 2);
|
|
AppendVMovU32(&code, 4, 2);
|
|
AppendVMovLiteral(&code, 5, 0xffffffffu);
|
|
AppendVop3B(&code, 0x176u, 10, 20, Vgpr(1), Vgpr(2), Vgpr(4));
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendStoreVgpr(&code, 11, 1);
|
|
AppendStoreSgprPair(&code, 20, 2);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorMadU64U32UnsignedCarryOut",
|
|
code,
|
|
{},
|
|
{0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u},
|
|
{O::V_MOV_B32, O::V_MAD_U64_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorLaneAndPackedOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(0)));
|
|
AppendVMovLiteral(&code, 4, 0x40003c00u);
|
|
AppendVMovLiteral(&code, 5, 0x44004200u);
|
|
AppendVMovLiteral(&code, 6, 0x3c003c00u);
|
|
AppendVMovLiteral(&code, 7, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 8, 0x40000000u);
|
|
AppendVMovLiteral(&code, 9, 0x12345678u);
|
|
AppendVMovLiteral(&code, 10, 0xabcdef01u);
|
|
|
|
code.push_back(EncodeVop1(0x02, 0, Vgpr(0)));
|
|
AppendVop3(&code, 0x360, 1, Vgpr(0), InlineU32(0));
|
|
AppendVop3(&code, 0x361, 1, 0, InlineU32(0));
|
|
AppendVop3(&code, 0x377, 2, Vgpr(0), InlineU32(0), InlineU32(0));
|
|
code.push_back(EncodeVop2(0x2f, 3, Vgpr(7), 8));
|
|
AppendVop3p(&code, 0x0f, 11, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
AppendVop3p(&code, 0x10, 12, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
AppendVop3p(&code, 0x11, 13, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
AppendVop3p(&code, 0x12, 14, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
AppendVop3p(&code, 0x0e, 15, Vgpr(4), Vgpr(5), Vgpr(6), 0x7);
|
|
AppendVop3(&code, 0x362, 16, Vgpr(7), InlineU32(1));
|
|
AppendVop3(&code, 0x36a, 17, Vgpr(9), Vgpr(10));
|
|
|
|
AppendStoreSgpr(&code, 0, 0);
|
|
AppendStoreSgpr(&code, 1, 1);
|
|
const u32 results[] = {1, 2, 3, 11, 12, 13, 14, 15, 16, 17};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i + 2u);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorLaneAndPackedOps",
|
|
code,
|
|
{},
|
|
{0x12345678u, 0x12345678u, 0x12345678u, 0x12345678u, 0x40003c00u,
|
|
0x46004400u, 0x48004200u, 0x40003c00u, 0x44004200u, 0x48804400u,
|
|
0x40000000u, 0xef015678u},
|
|
{O::V_MOV_B32, O::V_READFIRSTLANE_B32, O::V_READLANE_B32,
|
|
O::V_WRITELANE_B32, O::V_PERMLANE16_B32, O::V_CVT_PKRTZ_F16_F32,
|
|
O::V_PK_ADD_F16, O::V_PK_MUL_F16, O::V_PK_MIN_F16, O::V_PK_MAX_F16,
|
|
O::V_PK_FMA_F16, O::V_LDEXP_F32, O::V_CVT_PK_U16_U32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop2PkFmacF16AccumulatesPackedHalvesIndependently() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x50004c00u); // low=16.0h, high=32.0h
|
|
AppendVMovLiteral(&code, 1, 0x42003c00u); // low=1.0h, high=3.0h
|
|
AppendVMovLiteral(&code, 2, 0x48004400u); // low=4.0h, high=8.0h
|
|
|
|
code.push_back(EncodeVop2(0x3c, 0, Vgpr(1), 2));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
code.push_back(EncodeVop2(0x3c, 0, Vgpr(1), 2));
|
|
AppendStoreVgpr(&code, 0, 1);
|
|
|
|
AppendVMovLiteral(&code, 3, 0x50004c00u);
|
|
code.push_back(EncodeVop2(0x3c, 3, 242, 2)); // Inline 1.0h is {1.0h, 0.0h}.
|
|
AppendStoreVgpr(&code, 3, 2);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop2PkFmacF16AccumulatesPackedHalvesIndependently",
|
|
code,
|
|
{},
|
|
{0x53004d00u, 0x55004e00u, 0x50004d00u},
|
|
{O::V_MOV_B32, O::V_PK_FMAC_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop2PkFmacF16DppNegatesBothPackedHalves() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x50004c00u); // low=16.0h, high=32.0h
|
|
AppendVMovLiteral(&code, 1, 0x42003c00u); // low=1.0h, high=3.0h
|
|
AppendVMovLiteral(&code, 2, 0x48004400u); // low=4.0h, high=8.0h
|
|
|
|
code.push_back(EncodeVop2(0x3c, 0, 250, 2));
|
|
code.push_back(EncodeVop2Dpp(1, 0, 0xf, 0xf, 1));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop2PkFmacF16DppNegatesBothPackedHalves",
|
|
code,
|
|
{},
|
|
{0x48004a00u},
|
|
{O::V_MOV_B32, O::V_PK_FMAC_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop3pOpselHiUsesArchitecturalSourceBits() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x42003c00u); // low=1.0h, high=3.0h
|
|
AppendVMovLiteral(&code, 1, 0x48004400u); // low=4.0h, high=8.0h
|
|
AppendVMovLiteral(&code, 2, 0x50004c00u); // low=16.0h, high=32.0h
|
|
|
|
// Raw V_PK_FMA_F16 words keep this test independent of the encoders above.
|
|
code.insert(code.end(), {0xcc0e000au, 0x0c0a0300u}); // src0 high: 3*4+16=28
|
|
code.insert(code.end(), {0xcc0e000bu, 0x140a0300u}); // src1 high: 1*8+16=24
|
|
code.insert(code.end(), {0xcc0e400cu, 0x040a0300u}); // src2 high: 1*4+32=36
|
|
|
|
for (u32 i = 0; i < 3; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop3pOpselHiUsesArchitecturalSourceBits",
|
|
code,
|
|
{},
|
|
{0x4f004d00u, 0x4e004d00u, 0x50804d00u},
|
|
{O::V_MOV_B32, O::V_PK_FMA_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase CvtPkU8F32PacksSelectedByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
// RDNA2 V_CVT_PK_U8_F32: uint8(S0) replaces byte S1[1:0] in S2.
|
|
AppendVMovLiteral(&code, 0, 0x414c0000u); // 12.75f truncates to 12.
|
|
AppendVMovLiteral(&code, 1, 0x43960000u); // 300.0f saturates to 255.
|
|
AppendVMovLiteral(&code, 2, 0xbf800000u); // Negative values saturate to zero.
|
|
AppendVMovLiteral(&code, 3, 0x7fc00000u); // NaN saturates to zero.
|
|
AppendVMovLiteral(&code, 4, 0x11223344u);
|
|
|
|
AppendVop3(&code, 0x15eu, 10, Vgpr(0), InlineU32(0), Vgpr(4));
|
|
AppendVop3(&code, 0x15eu, 11, Vgpr(1), InlineU32(5), Vgpr(4));
|
|
AppendVop3(&code, 0x15eu, 12, Vgpr(2), InlineU32(2), Vgpr(4));
|
|
AppendVop3(&code, 0x15eu, 13, Vgpr(3), InlineU32(3), Vgpr(4));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"CvtPkU8F32PacksSelectedByte",
|
|
code,
|
|
{},
|
|
{0x1122330cu, 0x1122ff44u, 0x11003344u, 0x00223344u},
|
|
{O::V_MOV_B32, O::V_CVT_PK_U8_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase CvtPkrtzF16F32SubnormalRoundsTowardZero() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x33c00000u);
|
|
AppendVMovLiteral(&code, 1, 0xb3c00000u);
|
|
code.push_back(EncodeVop2(0x2f, 2, Vgpr(0), 1));
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"CvtPkrtzF16F32SubnormalRoundsTowardZero",
|
|
code,
|
|
{},
|
|
{0x80010001u},
|
|
{O::V_MOV_B32, O::V_CVT_PKRTZ_F16_F32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase CvtPkrtzF16F32SdwaAndOutputModifiers() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u); // 0.5f
|
|
AppendVMovLiteral(&code, 1, 0x40000000u); // 2.0f
|
|
AppendVMovLiteral(&code, 2, 0x3f802000u); // exactly 0x3c01h
|
|
AppendVMovLiteral(&code, 3, 0x00000000u);
|
|
AppendVMovLiteral(&code, 4, 0xbf000000u); // -0.5f
|
|
for (u32 dst = 10; dst <= 18; dst++) {
|
|
AppendVMovLiteral(&code, dst, 0xa1b2c3d4u);
|
|
}
|
|
|
|
code.push_back(EncodeVop2(0x2f, 10, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0, 6, 2, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1));
|
|
code.push_back(EncodeVop2(0x2f, 11, 249, 3));
|
|
code.push_back(EncodeVop2Sdwa(2, 0, 0));
|
|
code.push_back(EncodeVop2(0x2f, 12, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0, 6, 0, 3, 5));
|
|
code.push_back(EncodeVop2(0x2f, 13, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(4, 6, 0, 6, 6, 0, 0, 0, 1, 1));
|
|
|
|
code.push_back(EncodeVop2(0x2f, 14, 250, 1));
|
|
code.push_back(EncodeVop2Dpp(0, 0, 0xf, 0xf, 1));
|
|
AppendVop3(&code, 0x12f, 15, Vgpr(0), Vgpr(1), 0, 0, 0, true);
|
|
code.push_back(EncodeVop2(0x2f, 16, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0, 4, 2));
|
|
code.push_back(EncodeVop2(0x2f, 17, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0, 6, 2, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 1));
|
|
AppendVop3(&code, 0x12f, 18, Vgpr(0), Vgpr(1), 0, 0, 0, false, 1);
|
|
|
|
for (u32 dst = 10; dst <= 18; dst++) {
|
|
AppendStoreVgpr(&code, dst, dst - 10);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"CvtPkrtzF16F32SdwaAndOutputModifiers",
|
|
code,
|
|
{},
|
|
{0x3c003800u, 0x00000001u, 0x00000000u, 0xc0003800u,
|
|
0x4000b800u, 0x3c003800u, 0xa1b23800u, 0x44003c00u,
|
|
0x44003c00u},
|
|
{O::V_MOV_B32, O::V_CVT_PKRTZ_F16_F32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase PackedMinMaxF16NanAndSignedZeroEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x80004000u);
|
|
AppendVMovLiteral(&code, 1, 0x00007e00u);
|
|
AppendVMovLiteral(&code, 2, 0x00004000u);
|
|
AppendVMovLiteral(&code, 3, 0x80007e00u);
|
|
AppendVMovLiteral(&code, 4, 0x40007d01u);
|
|
AppendVMovLiteral(&code, 5, 0x7d014000u);
|
|
|
|
AppendVop3p(&code, 0x11, 10, Vgpr(0), Vgpr(1), 0, 0x3);
|
|
AppendVop3p(&code, 0x12, 11, Vgpr(2), Vgpr(3), 0, 0x3);
|
|
AppendVop3p(&code, 0x11, 12, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
AppendVop3p(&code, 0x12, 13, Vgpr(4), Vgpr(5), 0, 0x3);
|
|
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"PackedMinMaxF16NanAndSignedZeroEdges",
|
|
code,
|
|
{},
|
|
{0x80004000u, 0x00004000u, 0x7f017f01u, 0x7f017f01u},
|
|
{O::V_MOV_B32, O::V_PK_MIN_F16, O::V_PK_MAX_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMinMaxF16Ops() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xaaaa4000u); // low=2.0h
|
|
AppendVMovLiteral(&code, 1, 0xbbbb3c00u); // low=1.0h
|
|
AppendVMovLiteral(&code, 2, 0x12345678u);
|
|
AppendVMovLiteral(&code, 3, 0x87654321u);
|
|
AppendVMovLiteral(&code, 4, 0x5555aaaau);
|
|
AppendVMovLiteral(&code, 5, 0x6666bbbbu);
|
|
AppendVMovLiteral(&code, 6, 0x7777bbbbu);
|
|
AppendVMovLiteral(&code, 7, 0x8888bbbbu);
|
|
AppendVMovLiteral(&code, 8, 0x9999bbbbu);
|
|
AppendVMovLiteral(&code, 9, 0x99990000u);
|
|
code.push_back(EncodeVop2(0x32, 5, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x33, 6, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x34, 7, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x35, 4, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x39, 2, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x3a, 3, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x35, 9, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0, 4, 2, 4, 4));
|
|
code.push_back(0x660000f9u);
|
|
code.push_back(EncodeVop2Sdwa(0, 4, 2, 4, 4));
|
|
AppendStoreVgpr(&code, 5, 0);
|
|
AppendStoreVgpr(&code, 6, 1);
|
|
AppendStoreVgpr(&code, 7, 2);
|
|
AppendStoreVgpr(&code, 0, 3);
|
|
AppendStoreVgpr(&code, 4, 4);
|
|
AppendStoreVgpr(&code, 2, 5);
|
|
AppendStoreVgpr(&code, 3, 6);
|
|
AppendStoreVgpr(&code, 9, 7);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorMinMaxF16Ops",
|
|
code,
|
|
{},
|
|
{0x66664200u, 0x77773c00u, 0x8888bc00u, 0xaaaa0000u, 0x55554000u,
|
|
0x12344000u, 0x87653c00u, 0x99994000u},
|
|
{O::V_MOV_B32, O::V_ADD_F16, O::V_SUB_F16, O::V_SUBREV_F16,
|
|
O::V_MUL_F16, O::V_MAX_F16, O::V_MIN_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCvtU16F16Sdwa() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x45004200u); // low=3.0h, high=5.0h
|
|
AppendVMovLiteral(&code, 1, 0xbc007c00u); // low=+inf, high=-1.0h
|
|
AppendVMovLiteral(&code, 2, 0x00007e00u); // low=qNaN
|
|
AppendVMovLiteral(&code, 10, 0x12345678u);
|
|
AppendVMovLiteral(&code, 11, 0x87654321u);
|
|
AppendVMovLiteral(&code, 12, 0xabcd1111u);
|
|
AppendVMovLiteral(&code, 13, 0x77772222u);
|
|
AppendVMovLiteral(&code, 14, 0x5555aaaau);
|
|
code.push_back(EncodeVop1(0x52, 10, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 4));
|
|
code.push_back(EncodeVop1(0x52, 11, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 5, 2, 5));
|
|
code.push_back(EncodeVop1(0x52, 12, 249));
|
|
code.push_back(EncodeVop1Sdwa(1, 4, 2, 4));
|
|
code.push_back(EncodeVop1(0x52, 13, 249));
|
|
code.push_back(EncodeVop1Sdwa(1, 4, 2, 5));
|
|
code.push_back(EncodeVop1(0x52, 14, 249));
|
|
code.push_back(EncodeVop1Sdwa(2, 4, 2, 4));
|
|
for (u32 i = 0; i < 5; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCvtU16F16Sdwa",
|
|
code,
|
|
{},
|
|
{0x12340003u, 0x00054321u, 0xabcdffffu, 0x77770000u, 0x55550000u},
|
|
{O::V_MOV_B32, O::V_CVT_U16_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase NativeAndSdwa16BitDestinationWrites() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3fc00000u); // 1.5f -> 0x3e00h
|
|
AppendVMovLiteral(&code, 1, 0x00004200u); // low=3.0h
|
|
AppendVMovLiteral(&code, 2, 0x0000c000u); // low=-2.0h
|
|
AppendVMovLiteral(&code, 3, 0x00003c00u); // low=1.0h
|
|
AppendVMovLiteral(&code, 4, 0x00004000u); // low=2.0h
|
|
|
|
const u32 sentinels[] = {
|
|
0x3c001234u, 0xbbbb2345u, 0xcccc3456u, 0xdddd4567u, 0xeeee5678u,
|
|
0xffff6789u, 0xf00d789au, 0x11118888u, 0x22229999u, 0x3333aaaau,
|
|
0xaaaa0000u, 0xbbbb0000u, 0xcccc0000u, 0xabcd1234u, 0xbcde2345u,
|
|
0xcdef3456u, 0xdef01234u, 0xef012345u, 0xf0123456u, 0x01234567u,
|
|
0xaaaa1234u, 0xbbbb5678u, 0x24681234u, 0x13572468u, 0x89abcdefu,
|
|
0xfedcba98u, 0x0bad1234u,
|
|
};
|
|
// AppendStoreVgpr uses v31 as its address scratch register.
|
|
const auto output_vgpr = [](u32 index) { return 10u + index + (index >= 21u ? 1u : 0u); };
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(sentinels)); i++) {
|
|
AppendVMovLiteral(&code, output_vgpr(i), sentinels[i]);
|
|
}
|
|
|
|
code.push_back(EncodeVop1(0x0a, 10, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x0a, 11, 250));
|
|
code.push_back(EncodeVop1Dpp(0));
|
|
AppendVop3(&code, 0x18a, 12, Vgpr(0), 0);
|
|
code.push_back(EncodeVop1(0x52, 13, Vgpr(1)));
|
|
code.push_back(EncodeVop1(0x53, 14, Vgpr(2)));
|
|
|
|
code.push_back(EncodeVop1(0x0a, 15, 249));
|
|
code.push_back(EncodeVop1Sdwa(0));
|
|
code.push_back(EncodeVop1(0x54, 16, 249));
|
|
code.push_back(EncodeVop1Sdwa(10, 4, 2, 5));
|
|
code.push_back(EncodeVop1(0x52, 17, 249));
|
|
code.push_back(EncodeVop1Sdwa(1, 6, 0, 4));
|
|
code.push_back(EncodeVop1(0x52, 18, 249));
|
|
code.push_back(EncodeVop1Sdwa(1, 4, 0, 4));
|
|
code.push_back(EncodeVop1(0x53, 19, 249));
|
|
code.push_back(EncodeVop1Sdwa(2, 4, 1, 4));
|
|
|
|
code.push_back(EncodeVop2(0x32, 20, 250, 4));
|
|
code.push_back(EncodeVop2Dpp(3));
|
|
AppendVop3(&code, 0x132, 21, Vgpr(3), Vgpr(4));
|
|
code.push_back(EncodeVop2(0x32, 22, 249, 4));
|
|
code.push_back(EncodeVop2Sdwa(3, 4, 0, 4, 4));
|
|
|
|
code.push_back(EncodeVop1(0x0a, 23, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 3));
|
|
code.push_back(EncodeVop1(0x0a, 24, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 5));
|
|
code.push_back(EncodeVop1(0x0a, 25, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 6, 0, 1, 1));
|
|
|
|
code.push_back(EncodeVop1(0x0a, 26, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 6, 0, 0, 0, 0, 0, 1));
|
|
code.push_back(EncodeVop1(0x0a, 27, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 4, 2, 6, 0, 0, 0, 0, 1));
|
|
AppendVop3(&code, 0x18a, 28, Vgpr(0), 0, 0, 0, 0, false, 1);
|
|
AppendVop3(&code, 0x18a, 29, Vgpr(0), 0, 0, 0, 0, true);
|
|
AppendVop3(&code, 0x34b, 30, Vgpr(3), Vgpr(4), Vgpr(3));
|
|
AppendVop3(&code, 0x34b, 32, Vgpr(3), Vgpr(4), Vgpr(3), 0, 0x8);
|
|
code.push_back(EncodeVop1(0x52, 33, 249));
|
|
code.push_back(EncodeVop1Sdwa(1, 4, 2, 4, 0, 1, 1, 0, 1));
|
|
AppendVop3(&code, 0x1d3, 34, Vgpr(1), 0, 0, 1, 0, true, 0, 1);
|
|
code.push_back(EncodeVop1(0x07, 35, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 6, 0, 6, 0, 1, 1, 0, 1));
|
|
AppendVop3(&code, 0x188, 36, Vgpr(0), 0, 0, 1, 0, true, 0, 1);
|
|
code.push_back(EncodeVop1(0x54, 37, 249));
|
|
code.push_back(EncodeVop1Sdwa(4, 4, 2, 4, 0, 1, 1));
|
|
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(sentinels)); i++) {
|
|
AppendStoreVgpr(&code, output_vgpr(i), i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"NativeAndSdwa16BitDestinationWrites",
|
|
code,
|
|
{},
|
|
{0x3c003e00u, 0xbbbb3e00u, 0xcccc3e00u, 0xdddd0003u,
|
|
0xeeeefffeu, 0x00003e00u, 0xf00d3c00u, 0x00000003u,
|
|
0x00000003u, 0xfffffffeu, 0xaaaa4200u, 0xbbbb4200u,
|
|
0x00004200u, 0xabcd0000u, 0xbcde0000u, 0xcdefbe00u,
|
|
0xdef04200u, 0xef013c00u, 0xf0124200u, 0x01233c00u,
|
|
0xaaaa4200u, 0x42005678u, 0x24680000u, 0x1357fffdu,
|
|
0x00000000u, 0xffffffffu, 0x0badb800u},
|
|
{O::V_MOV_B32, O::V_CVT_F16_F32, O::V_CVT_U32_F32,
|
|
O::V_CVT_I32_F32, O::V_CVT_U16_F16, O::V_CVT_I16_F16,
|
|
O::V_RCP_F16, O::V_ADD_F16, O::V_FMA_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMinMaxMed3F16Ops() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x40003c00u); // low=1.0h, high=2.0h
|
|
AppendVMovLiteral(&code, 1, 0x44004200u); // low=3.0h, high=4.0h
|
|
AppendVMovLiteral(&code, 2, 0xc8004000u); // low=2.0h, high=-8.0h
|
|
AppendVMovLiteral(&code, 3, 0x00007e00u); // low=qNaN
|
|
AppendVMovLiteral(&code, 4, 0x00004000u); // low=2.0h
|
|
AppendVMovLiteral(&code, 5, 0x00003c00u); // low=1.0h
|
|
AppendVMovLiteral(&code, 10, 0xaaaa5555u);
|
|
AppendVMovLiteral(&code, 11, 0x12345678u);
|
|
AppendVMovLiteral(&code, 12, 0x77772222u);
|
|
|
|
AppendVop3(&code, 0x351, 10, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x354, 11, Vgpr(0), Vgpr(1), Vgpr(2), 0, 0xf, false, 0,
|
|
0x4);
|
|
AppendVop3(&code, 0x357, 12, Vgpr(3), Vgpr(4), Vgpr(5));
|
|
for (u32 i = 0; i < 3; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorMinMaxMed3F16Ops",
|
|
code,
|
|
{},
|
|
{0xaaaa3c00u, 0x48005678u, 0x77773c00u},
|
|
{O::V_MOV_B32, O::V_MIN3_F16, O::V_MAX3_F16, O::V_MED3_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorSpecialF16Ops() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xc000fc00u); // low=-inf, high=-2.0h
|
|
AppendVMovLiteral(&code, 1, 0x00004400u); // low=+4.0h
|
|
AppendVMovLiteral(&code, 2, 0x00003c00u); // low=+1.0h
|
|
AppendVMovLiteral(&code, 3, 0x0000bc00u); // low=-1.0h
|
|
AppendVMovLiteral(&code, 4, 0x0000fc00u); // low=-inf
|
|
AppendVMovLiteral(&code, 11, 0x12345678u);
|
|
|
|
code.push_back(EncodeVop1(0x54, 10, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x54, 11, 249));
|
|
code.push_back(EncodeVop1Sdwa(0, 5, 2, 5));
|
|
code.push_back(EncodeVop1(0x56, 12, Vgpr(1)));
|
|
code.push_back(EncodeVop1(0x57, 13, Vgpr(2)));
|
|
code.push_back(EncodeVop1(0x57, 14, Vgpr(3)));
|
|
code.push_back(EncodeVop1(0x58, 15, Vgpr(4)));
|
|
AppendVop3(&code, 0x1d7, 16, Vgpr(3), 0, 0, 0, 0, true);
|
|
for (u32 i = 0; i < 7; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorSpecialF16Ops",
|
|
code,
|
|
{},
|
|
{0x00008000u, 0xb8005678u, 0x00003800u, 0x00000000u, 0x0000fe00u,
|
|
0x00000000u, 0x00000000u},
|
|
{O::V_MOV_B32, O::V_RCP_F16, O::V_RSQ_F16, O::V_LOG_F16, O::V_EXP_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCosF16CapturedSdwaAndEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 4, 0x34001234u); // high=+0.25h, low sentinel
|
|
code.push_back(0x7e08c2f9u);
|
|
code.push_back(0x00051504u); // captured v_cos_f16 v4.word1, v4.word1
|
|
AppendVMovLiteral(&code, 5, 0x00008000u); // -0.0h
|
|
code.push_back(EncodeVop1(0x61, 6, Vgpr(5)));
|
|
AppendVMovLiteral(&code, 7, 0x00007bffu); // max finite
|
|
code.push_back(EncodeVop1(0x61, 8, Vgpr(7)));
|
|
AppendVMovLiteral(&code, 9, 0x00007c00u); // +inf
|
|
code.push_back(EncodeVop1(0x61, 10, Vgpr(9)));
|
|
AppendVMovLiteral(&code, 11, 0x00000001u); // minimum positive denormal
|
|
code.push_back(EncodeVop1(0x61, 12, Vgpr(11)));
|
|
AppendVMovLiteral(&code, 13, 0x00003800u); // +0.5h
|
|
code.push_back(EncodeVop1(0x61, 14, Vgpr(13)));
|
|
for (u32 i = 0; i < 6; i++) {
|
|
AppendStoreVgpr(&code, 4 + i * 2, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorCosF16CapturedSdwaAndEdges",
|
|
code,
|
|
{},
|
|
{0x00001234u, 0x00003c00u, 0x00003c00u, 0x0000fe00u,
|
|
0x00003c00u, 0x0000bc00u},
|
|
{O::V_MOV_B32, O::V_COS_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_COS_F16", 6},
|
|
{"V_COS_F16 v4.sdwa(sel=5,sext=0), "
|
|
"v4.sdwa(sel=5,sext=0)",
|
|
1}};
|
|
test.ir_counts = {{" = FPCos ", 6}};
|
|
test.required_spirv = {" Fract ", " Cos ", " PackHalf2x16 "};
|
|
test.forbidden_spirv = {"OpCapability Float16"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorSinF16SdwaAndEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 4, 0x34001234u); // high=+0.25h, low sentinel
|
|
code.push_back(0x7e08c0f9u);
|
|
code.push_back(0x00051504u); // v_sin_f16 v4.word1, v4.word1
|
|
AppendVMovLiteral(&code, 5, 0x00008000u); // -0.0h
|
|
code.push_back(EncodeVop1(0x60, 6, Vgpr(5)));
|
|
AppendVMovLiteral(&code, 7, 0x00007bffu); // max finite
|
|
code.push_back(EncodeVop1(0x60, 8, Vgpr(7)));
|
|
AppendVMovLiteral(&code, 9, 0x0000fbffu); // minimum finite
|
|
code.push_back(EncodeVop1(0x60, 10, Vgpr(9)));
|
|
AppendVMovLiteral(&code, 11, 0x00007c00u); // +inf
|
|
code.push_back(EncodeVop1(0x60, 12, Vgpr(11)));
|
|
AppendVMovLiteral(&code, 13, 0x0000fc00u); // -inf
|
|
code.push_back(EncodeVop1(0x60, 14, Vgpr(13)));
|
|
AppendVMovLiteral(&code, 15, 0x00003800u); // +0.5h
|
|
code.push_back(EncodeVop1(0x60, 16, Vgpr(15)));
|
|
AppendVMovLiteral(&code, 17, 0x00003a00u); // +0.75h
|
|
code.push_back(EncodeVop1(0x60, 18, Vgpr(17)));
|
|
AppendVMovLiteral(&code, 19, 0x00003400u); // +0.25h
|
|
AppendVop3(&code, 0x1e0, 20, Vgpr(19), 0);
|
|
for (u32 i = 0; i < 9; i++) {
|
|
AppendStoreVgpr(&code, 4 + i * 2, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorSinF16SdwaAndEdges",
|
|
code,
|
|
{},
|
|
{0x3c001234u, 0x00008000u, 0x00000000u, 0x00000000u,
|
|
0x0000fe00u, 0x0000fe00u, 0x00000000u, 0x0000bc00u,
|
|
0x00003c00u},
|
|
{O::V_MOV_B32, O::V_SIN_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_SIN_F16", 9},
|
|
{"V_SIN_F16 v4.sdwa(sel=5,sext=0), "
|
|
"v4.sdwa(sel=5,sext=0)",
|
|
1}};
|
|
test.ir_counts = {{" = FPSin ", 9}};
|
|
test.required_spirv = {" Fract ", " Sin ", " PackHalf2x16 "};
|
|
test.forbidden_spirv = {"OpCapability Float16"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorWritelaneIgnoresExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 2, 0xaaaaaaaau);
|
|
AppendSMovLiteral(&code, 4, 0x12345678u);
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
|
AppendVop3(&code, 0x361, 2, 4, InlineU32(1));
|
|
code.push_back(EncodeSMovB32(126, InlineU32(0xf)));
|
|
code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorWritelaneIgnoresExecMask";
|
|
test.code = code;
|
|
test.expected = {0xaaaaaaaau, 0x12345678u, 0xaaaaaaaau, 0xaaaaaaaau};
|
|
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_WRITELANE_B32,
|
|
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorReadlaneFromInactiveWrittenLane() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 4, 0x12345678u);
|
|
AppendVop3(&code, 0x361, 2, 4, InlineU32(4));
|
|
AppendSMovLiteral(&code, 4, 0xdeadbeefu);
|
|
AppendVop3(&code, 0x360, 5, Vgpr(2), InlineU32(4));
|
|
AppendStoreSgpr(&code, 5, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorReadlaneInactiveWrittenLane";
|
|
test.code = code;
|
|
test.expected = {0x12345678u};
|
|
test.opcodes = {O::S_MOV_B32, O::V_WRITELANE_B32, O::V_READLANE_B32,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.forbidden_spirv = {"OpGroupNonUniformShuffle"};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorLaneWave32RuntimeSelectorWraps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSop2(0x00, 5, 4, InlineU32(33)));
|
|
AppendSMovLiteral(&code, 6, 0x12345678u);
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVop3(&code, 0x361, 2, 6, 5);
|
|
AppendVop3(&code, 0x360, 7, Vgpr(2), InlineU32(1));
|
|
AppendStoreSgpr(&code, 7, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorLaneWave32RuntimeSelectorWraps";
|
|
test.code = code;
|
|
test.expected = {0x12345678u};
|
|
test.opcodes = {O::S_ADD_U32, O::S_MOV_B32, O::V_WRITELANE_B32,
|
|
O::V_READLANE_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpGroupNonUniformShuffle"};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 4;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorPermlanex16() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 3, 0x76543210u);
|
|
AppendVMovLiteral(&code, 4, 0xfedcba98u);
|
|
code.push_back(EncodeVop2(0x1a, 2, InlineU32(2), 0));
|
|
AppendVMovLiteral(&code, 5, 0xfeedbabeu);
|
|
AppendVop3(&code, 0x378, 1, Vgpr(5), Vgpr(3), Vgpr(4));
|
|
AppendBufferStoreDword(&code, 1, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorPermlanex16";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(32, 0xfeedbabeu);
|
|
test.opcodes = {O::V_MOV_B32, O::V_PERMLANEX16_B32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 32;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorPermlane16FetchInactiveZero() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0),
|
|
EncodeVop2(0x25, 1, InlineU32(10), 1),
|
|
EncodeVopc(0xc2, InlineU32(0), 0),
|
|
EncodeSop1(0x04, 126, 106),
|
|
EncodeSMovB32(0, InlineU32(1)),
|
|
EncodeSMovB32(1, InlineU32(0)),
|
|
};
|
|
AppendVop3(&code, 0x377, 2, Vgpr(1), 0, 1);
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorPermlane16FetchInactiveZero";
|
|
test.code = code;
|
|
test.expected = {0};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64, O::S_MOV_B32, O::V_PERMLANE16_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorPermlane16FetchInactiveFi() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, Vgpr(0)), EncodeVop2(0x25, 1, InlineU32(10), 1),
|
|
EncodeVopc(0xc2, InlineU32(0), 0), EncodeSop1(0x04, 126, 106),
|
|
EncodeSMovB32(0, InlineU32(1)), EncodeSMovB32(1, InlineU32(0)),
|
|
};
|
|
AppendVop3(&code, 0x377, 2, Vgpr(1), 0, 1, 0, 1);
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorPermlane16FetchInactiveFi";
|
|
test.code = code;
|
|
test.expected = {11};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64, O::S_MOV_B32, O::V_PERMLANE16_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorDppQuadPermuteReverse() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeVop2(0x25, 2, 250, 1));
|
|
code.push_back(EncodeVop2Dpp(0, 0x01b));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorDppQuadPermuteReverse";
|
|
test.code = code;
|
|
test.expected = {103, 102, 101, 100, 107, 106, 105, 104};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 8;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorDppRowXmask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeVop2(0x25, 2, 250, 1));
|
|
code.push_back(EncodeVop2Dpp(0, 0x164));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorDppRowXmask";
|
|
test.code = code;
|
|
test.expected = {104, 105, 106, 107, 100, 101, 102, 103};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 8;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorDppBankMaskPreservesDestination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 2, 0xaaaaaaaau);
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeVop2(0x25, 2, 250, 1));
|
|
code.push_back(EncodeVop2Dpp(0, 0x0e4, 0xf, 0xe));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorDppBankMaskPreservesDestination";
|
|
test.code = code;
|
|
test.expected = {0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau,
|
|
104, 105, 106, 107};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 8;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorDppBoundsControlZeroPreservesDestination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 2, 0xaaaaaaaau);
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeVop2(0x25, 2, 250, 1));
|
|
code.push_back(EncodeVop2Dpp(0, 0x111));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorDppBoundsControlZeroPreservesDestination";
|
|
test.code = code;
|
|
test.expected = {0xaaaaaaaau, 100, 101, 102, 103, 104, 105, 106};
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 8;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop3FmacF32NegatedSourceAccumulates() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
constexpr u32 registers[] = {13, 20, 29};
|
|
for (u32 sample = 0; sample < 2; sample++) {
|
|
for (u32 source = 0; source < 3; source++) {
|
|
AppendVMovU32(&code, 30, (sample * 3 + source) * sizeof(u32));
|
|
AppendBufferLoadDword(&code, registers[source], 30);
|
|
}
|
|
code.push_back(0xd52b001du);
|
|
code.push_back(0x4002290du); // v_fmac_f32_e64 v29, v13, -v20
|
|
AppendStoreVgpr(&code, 29, sample);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop3FmacF32NegatedSourceAccumulates";
|
|
test.code = std::move(code);
|
|
test.initial = {0x40000000u, 0x40400000u, 0x41200000u,
|
|
0x3f800001u, 0x3f7ffffeu, 0x3f800000u};
|
|
// 10 - 2*3 = 4; 1 - (1+2^-23)*(1-2^-23) = 2^-46, not rounded zero.
|
|
test.expected = {0x40800000u, 0x28800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAC_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpFNegate", "Fma"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop3LdexpSourceModifier() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 9, 0xbfc00000u);
|
|
code.push_back(0xd7620107u);
|
|
code.push_back(0x00018509u);
|
|
AppendStoreVgpr(&code, 7, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop3LdexpSourceModifier",
|
|
code,
|
|
{},
|
|
{0x3ec00000u},
|
|
{O::V_MOV_B32, O::V_LDEXP_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop1MoveRelSource() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 12, 0x11111111u);
|
|
AppendVMovLiteral(&code, 13, 0x22222222u);
|
|
AppendVMovLiteral(&code, 14, 0x33333333u);
|
|
code.push_back(EncodeSMovB32(124, InlineU32(2)));
|
|
code.push_back(0x7e6e870cu);
|
|
AppendStoreVgpr(&code, 55, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop1MoveRelSource",
|
|
code,
|
|
{},
|
|
{0x33333333u},
|
|
{O::V_MOV_B32, O::S_MOV_B32, O::V_MOVRELS_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop1MoveRelDestination() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0x12345678u);
|
|
AppendVMovLiteral(&code, 5, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 6, 0xbbbbbbbbu);
|
|
AppendVMovLiteral(&code, 7, 0xccccccccu);
|
|
code.push_back(EncodeSMovB32(124, InlineU32(2)));
|
|
code.push_back(EncodeSop1(0x04, 8, 126));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 126, 106));
|
|
code.push_back(EncodeVop1(0x42, 5, Vgpr(1)));
|
|
code.push_back(EncodeSop1(0x04, 126, 8));
|
|
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 7, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop1MoveRelDestination";
|
|
test.code = code;
|
|
test.expected = {0x12345678u, 0xccccccccu, 0xccccccccu, 0xccccccccu};
|
|
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64, O::V_MOVRELD_B32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorFloatSpecialOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x40000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40400000u);
|
|
AppendVMovLiteral(&code, 3, 0x40800000u);
|
|
AppendVMovLiteral(&code, 4, 0x40003c00u);
|
|
AppendVMovLiteral(&code, 5, 0x44004200u);
|
|
AppendVMovLiteral(&code, 6, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 7, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 8, 0xbf800000u);
|
|
AppendVMovLiteral(&code, 10, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 12, 0xaaaabbbbu);
|
|
AppendVMovLiteral(&code, 13, 0xaaaabbbbu);
|
|
|
|
code.push_back(EncodeVop2(0x02, 6, Vgpr(4), 5));
|
|
AppendVop3(&code, 0x368, 9, Vgpr(7), Vgpr(8));
|
|
AppendVop3(&code, 0x369, 11, Vgpr(7), Vgpr(10));
|
|
AppendVop3p(&code, 0x21, 12, Vgpr(1), Vgpr(2), Vgpr(0));
|
|
AppendVop3p(&code, 0x22, 13, Vgpr(1), Vgpr(2), Vgpr(0));
|
|
AppendVop3(&code, 0x144, 14, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x145, 15, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x146, 16, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x147, 17, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
|
|
const u32 results[] = {6, 9, 11, 12, 13, 14, 15, 16, 17};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorFloatSpecialOps",
|
|
code,
|
|
{},
|
|
{0x41400000u, 0x80017fffu, 0x8000ffffu, 0xaaaa4700u, 0x4700bbbbu,
|
|
0x40800000u, 0x3f800000u, 0xc0000000u, 0x40c00000u},
|
|
{O::V_MOV_B32, O::V_DOT2C_F32_F16, O::V_CVT_PKNORM_I16_F32,
|
|
O::V_CVT_PKNORM_U16_F32, O::V_MAD_MIXLO_F16, O::V_MAD_MIXHI_F16,
|
|
O::V_CUBEID_F32, O::V_CUBESC_F32, O::V_CUBETC_F32, O::V_CUBEMA_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase MadMixF16LiteralHalfSourceUsesOpsel() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 12, 0xaaaabbbbu);
|
|
AppendVMovLiteral(&code, 13, 0x11112222u);
|
|
|
|
AppendVop3p(&code, 0x21, 12, 255u, Vgpr(0), Vgpr(0), 0x1, 0x1);
|
|
code.push_back(0x40003c00u);
|
|
AppendVop3p(&code, 0x22, 13, 255u, Vgpr(0), Vgpr(0), 0x1, 0x0);
|
|
code.push_back(0x40003c00u);
|
|
|
|
AppendStoreVgpr(&code, 12, 0);
|
|
AppendStoreVgpr(&code, 13, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"MadMixF16LiteralHalfSourceUsesOpsel",
|
|
code,
|
|
{},
|
|
{0xaaaa4200u, 0x40002222u},
|
|
{O::V_MOV_B32, O::V_MAD_MIXLO_F16, O::V_MAD_MIXHI_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase MadMixF16NegHiIsAbsAndNegIsIndependent() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0xc0000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40000000u);
|
|
AppendVMovLiteral(&code, 12, 0xaaaabbbbu);
|
|
AppendVMovLiteral(&code, 13, 0x11112222u);
|
|
|
|
AppendVop3p(&code, 0x21, 12, Vgpr(1), Vgpr(0), Vgpr(0), 0, 0, 0x1);
|
|
AppendVop3p(&code, 0x22, 13, Vgpr(2), Vgpr(0), Vgpr(0), 0, 0, 0, 0x1);
|
|
|
|
AppendStoreVgpr(&code, 12, 0);
|
|
AppendStoreVgpr(&code, 13, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"MadMixF16NegHiIsAbsAndNegIsIndependent",
|
|
code,
|
|
{},
|
|
{0xaaaa4200u, 0xbc002222u},
|
|
{O::V_MOV_B32, O::V_MAD_MIXLO_F16, O::V_MAD_MIXHI_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3FmaF16UsesRdna2Opcode34b() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0x40003c00u);
|
|
AppendVMovLiteral(&code, 2, 0x44004200u);
|
|
AppendVMovLiteral(&code, 3, 0x3c003c00u);
|
|
AppendVMovLiteral(&code, 10, 0xaaaa5555u);
|
|
AppendVMovLiteral(&code, 11, 0xbbbb5555u);
|
|
|
|
AppendVop3(&code, 0x34bu, 10, Vgpr(1), Vgpr(2), Vgpr(3));
|
|
AppendVop3(&code, 0x34bu, 11, Vgpr(1), Vgpr(2), Vgpr(3), 0, 0xfu);
|
|
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendStoreVgpr(&code, 11, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3FmaF16UsesRdna2Opcode34b",
|
|
code,
|
|
{},
|
|
{0xaaaa4400u, 0x48805555u},
|
|
{O::V_MOV_B32, O::V_FMA_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase FloatInlineConstantF16UsesNumericValue() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 1, 0x38003800u); // 0.5h, 0.5h
|
|
AppendVMovLiteral(&code, 2, 0x3c003c00u); // 1.0h, 1.0h
|
|
AppendVMovLiteral(&code, 10, 0xaaaa0000u);
|
|
AppendVMovLiteral(&code, 12, 0xbbbb0000u);
|
|
|
|
AppendVop3(&code, 0x34bu, 10, 246u, Vgpr(1), Vgpr(2));
|
|
AppendVop3p(&code, 0x0eu, 11, 246u, Vgpr(1), Vgpr(2), 0x1u);
|
|
AppendVop3(&code, 0x34bu, 12, 248u, 242u, 128u);
|
|
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendStoreVgpr(&code, 11, 1);
|
|
AppendStoreVgpr(&code, 12, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "FloatInlineConstantF16UsesNumericValue";
|
|
test.code = std::move(code);
|
|
test.expected = {0xaaaa4200u, 0x3c004200u, 0xbbbb3118u};
|
|
test.opcodes = {O::V_MOV_B32, O::V_FMA_F16, O::V_PK_FMA_F16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"4.000000", 2}, {"0.159155", 1}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorFloatControlContractPreservesInfNan() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSop2(0x24, 0, InlineU32(8), InlineU32(23)));
|
|
code.push_back(EncodeVop1(0x01, 0, InlineU32(0)));
|
|
AppendVMovU32(&code, 1, 1);
|
|
AppendVMovU32(&code, 2, 3);
|
|
AppendVop3(&code, 0x108, 10, 0, Vgpr(0));
|
|
code.push_back(EncodeVopc(0x88, Vgpr(10), 2));
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorFloatControlContractPreservesInfNan";
|
|
test.code = std::move(code);
|
|
test.expected = {1u};
|
|
test.opcodes = {O::S_BFM_B32, O::V_MOV_B32, O::V_MUL_F32,
|
|
O::V_CMP_CLASS_F32, O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"SPV_KHR_float_controls",
|
|
"SignedZeroInfNanPreserve 32"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorFloatArithmeticOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x40000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40800000u);
|
|
AppendVMovLiteral(&code, 3, 0x40a00000u);
|
|
AppendVMovLiteral(&code, 4, 0x40400000u);
|
|
AppendVMovLiteral(&code, 11, 0x40800000u);
|
|
AppendVMovLiteral(&code, 17, 0x40800000u);
|
|
|
|
code.push_back(EncodeVop2(0x03, 5, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x04, 6, Vgpr(3), 1));
|
|
code.push_back(EncodeVop2(0x05, 7, Vgpr(1), 3));
|
|
code.push_back(EncodeVop2(0x08, 8, Vgpr(1), 2));
|
|
code.push_back(EncodeVop1(0x01, 21, Vgpr(8)));
|
|
code.push_back(EncodeVop2(0x0f, 9, Vgpr(3), 1));
|
|
code.push_back(EncodeVop2(0x10, 10, Vgpr(3), 1));
|
|
code.push_back(EncodeVop2(0x1f, 11, Vgpr(1), 4));
|
|
AppendVop3(&code, 0x141, 12, Vgpr(1), Vgpr(4), Vgpr(2));
|
|
AppendVop3(&code, 0x14b, 13, Vgpr(1), Vgpr(4), Vgpr(2));
|
|
AppendVop3(&code, 0x151, 14, Vgpr(3), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x154, 15, Vgpr(3), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x157, 16, Vgpr(3), Vgpr(1), Vgpr(2));
|
|
code.push_back(EncodeVop2(0x20, 18, Vgpr(1), 17));
|
|
code.push_back(0x3f800000u);
|
|
code.push_back(EncodeVop2(0x21, 19, Vgpr(1), 4));
|
|
code.push_back(0x40000000u);
|
|
code.push_back(EncodeVop2(0x03, 20, 249, 1));
|
|
code.push_back(EncodeVop2Sdwa(0));
|
|
AppendSMovLiteral(&code, 70, 0x40000000u);
|
|
code.push_back(0x06088cf9u);
|
|
code.push_back(0x868606f2u);
|
|
AppendSMovLiteral(&code, 26, 0x40000000u);
|
|
code.push_back(0x081034f9u);
|
|
code.push_back(0x868606f2u);
|
|
code.push_back(0xd51f8011u);
|
|
code.push_back(0x00020d0bu);
|
|
AppendVMovLiteral(&code, 22, 0xbf800000u);
|
|
code.push_back(EncodeVop2(0x05, 23, 249, 26));
|
|
code.push_back(EncodeVop2Sdwa(22, 6, 0, 6, 6, 0, 0, 0, 1, 0, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 24, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 25, 0x3f800000u);
|
|
code.push_back(EncodeVop2(0x03, 26, 249, 25));
|
|
code.push_back(EncodeVop2Sdwa(24, 6, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 27, 0xbf800000u);
|
|
AppendVMovLiteral(&code, 28, 0x40000000u);
|
|
AppendVop3(&code, 0x104, 29, Vgpr(27), Vgpr(28), 0, 1);
|
|
AppendVop3(&code, 0x103, 30, Vgpr(0), Vgpr(1), 0, 0, 0, false, 1);
|
|
AppendVop3(&code, 0x151, 32, Vgpr(0), Vgpr(1), Vgpr(2), 0, 0, false, 1);
|
|
AppendVMovLiteral(&code, 33, 0xbf800000u);
|
|
AppendVMovLiteral(&code, 34, 0x40000000u);
|
|
code.push_back(EncodeVop2(0x03, 35, 250, 34));
|
|
code.push_back(EncodeVop2Dpp(33, 0, 0xf, 0xf, 0, 1));
|
|
|
|
const u32 results[] = {5, 6, 7, 21, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
18, 19, 20, 4, 8, 17, 23, 26, 29, 30, 32, 35};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorFloatArithmeticOps",
|
|
code,
|
|
{},
|
|
{0x40400000u, 0x40400000u, 0x40400000u, 0x41000000u, 0x40000000u,
|
|
0x40a00000u, 0x41200000u, 0x41200000u, 0x41200000u, 0x40000000u,
|
|
0x40a00000u, 0x40800000u, 0x40c00000u, 0x41000000u, 0x40400000u,
|
|
0x40400000u, 0xbf800000u, 0x3f800000u, 0x3f800000u, 0x40800000u,
|
|
0xbf800000u, 0x40c00000u, 0x40000000u, 0x40400000u},
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::V_ADD_F32, O::V_SUB_F32,
|
|
O::V_SUBREV_F32, O::V_MUL_F32, O::V_MIN_F32, O::V_MAX_F32,
|
|
O::V_MAC_F32, O::V_MAD_F32, O::V_FMA_F32, O::V_MIN3_F32,
|
|
O::V_MAX3_F32, O::V_MED3_F32, O::V_MADMK_F32, O::V_MADAK_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMinMaxF32NanAndSignedZeroEdges() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x40000000u);
|
|
AppendVMovLiteral(&code, 1, 0x7fc00000u);
|
|
AppendVMovLiteral(&code, 2, 0x00000000u);
|
|
AppendVMovLiteral(&code, 3, 0x80000000u);
|
|
AppendVMovLiteral(&code, 4, 0x40400000u);
|
|
AppendVMovLiteral(&code, 5, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 6, 0x7fa00001u);
|
|
|
|
code.push_back(EncodeVop2(0x0f, 10, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x10, 11, Vgpr(0), 1));
|
|
code.push_back(EncodeVop2(0x0f, 12, Vgpr(3), 2));
|
|
code.push_back(EncodeVop2(0x10, 13, Vgpr(2), 3));
|
|
code.push_back(EncodeVop2(0x0f, 14, Vgpr(6), 0));
|
|
code.push_back(EncodeVop2(0x10, 15, Vgpr(0), 6));
|
|
AppendVop3(&code, 0x151, 16, Vgpr(0), Vgpr(1), Vgpr(4));
|
|
AppendVop3(&code, 0x154, 17, Vgpr(0), Vgpr(1), Vgpr(5));
|
|
|
|
for (u32 i = 0; i < 8; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorMinMaxF32NanAndSignedZeroEdges",
|
|
code,
|
|
{},
|
|
{0x40000000u, 0x40000000u, 0x80000000u, 0x00000000u, 0x40000000u,
|
|
0x40000000u, 0x40000000u, 0x40000000u},
|
|
{O::V_MOV_B32, O::V_MIN_F32, O::V_MAX_F32, O::V_MIN3_F32,
|
|
O::V_MAX3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorMed3F32NanUsesMin3Path() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x40000000u);
|
|
AppendVMovLiteral(&code, 1, 0x40400000u);
|
|
AppendVMovLiteral(&code, 2, 0x7fa00001u);
|
|
AppendVMovLiteral(&code, 3, 0x7fc00000u);
|
|
|
|
AppendVop3(&code, 0x157, 10, Vgpr(0), Vgpr(1), Vgpr(2));
|
|
AppendVop3(&code, 0x157, 11, Vgpr(0), Vgpr(1), Vgpr(3));
|
|
AppendVop3(&code, 0x157, 12, Vgpr(2), Vgpr(0), Vgpr(1));
|
|
|
|
AppendStoreVgpr(&code, 10, 0);
|
|
AppendStoreVgpr(&code, 11, 1);
|
|
AppendStoreVgpr(&code, 12, 2);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorMed3F32NanUsesMin3Path",
|
|
code,
|
|
{},
|
|
{0x40000000u, 0x40000000u, 0x40000000u},
|
|
{O::V_MOV_B32, O::V_MED3_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorFloatConversionOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xfffffffdu);
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(7)));
|
|
AppendVMovLiteral(&code, 2, 0x40e00000u);
|
|
AppendVMovLiteral(&code, 3, 0xc0400000u);
|
|
AppendVMovLiteral(&code, 4, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 5, 0x00003c00u);
|
|
AppendVMovLiteral(&code, 6, 0x40700000u);
|
|
AppendVMovLiteral(&code, 7, 0x44332211u);
|
|
AppendVMovLiteral(&code, 8, 0x40000000u);
|
|
AppendVMovLiteral(&code, 9, 0x40800000u);
|
|
AppendVMovLiteral(&code, 10, 0x40100000u);
|
|
AppendVMovLiteral(&code, 11, 0x40300000u);
|
|
code.push_back(EncodeVop1(0x01, 12, InlineU32(0)));
|
|
code.push_back(EncodeVop1(0x01, 37, InlineU32(15)));
|
|
|
|
code.push_back(EncodeVop1(0x05, 13, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x06, 14, Vgpr(1)));
|
|
code.push_back(EncodeVop1(0x07, 15, Vgpr(2)));
|
|
code.push_back(EncodeVop1(0x08, 16, Vgpr(3)));
|
|
code.push_back(EncodeVop1(0x0a, 17, Vgpr(4)));
|
|
code.push_back(EncodeVop1(0x0b, 18, Vgpr(5)));
|
|
code.push_back(EncodeVop1(0x0c, 42, Vgpr(11)));
|
|
code.push_back(EncodeVop1(0x0d, 19, Vgpr(6)));
|
|
code.push_back(EncodeVop1(0x11, 20, Vgpr(7)));
|
|
code.push_back(EncodeVop1(0x12, 21, Vgpr(7)));
|
|
code.push_back(EncodeVop1(0x13, 22, Vgpr(7)));
|
|
code.push_back(EncodeVop1(0x14, 23, Vgpr(7)));
|
|
code.push_back(EncodeVop1(0x2a, 24, Vgpr(8)));
|
|
code.push_back(EncodeVop1(0x20, 25, Vgpr(10)));
|
|
code.push_back(EncodeVop1(0x21, 26, Vgpr(11)));
|
|
code.push_back(EncodeVop1(0x22, 27, Vgpr(10)));
|
|
code.push_back(EncodeVop1(0x23, 28, Vgpr(10)));
|
|
code.push_back(EncodeVop1(0x24, 29, Vgpr(11)));
|
|
code.push_back(EncodeVop1(0x25, 30, Vgpr(4)));
|
|
code.push_back(EncodeVop1(0x27, 36, Vgpr(9)));
|
|
code.push_back(EncodeVop1(0x2e, 32, Vgpr(9)));
|
|
code.push_back(EncodeVop1(0x33, 33, Vgpr(9)));
|
|
code.push_back(EncodeVop1(0x35, 34, Vgpr(12)));
|
|
code.push_back(EncodeVop1(0x36, 35, Vgpr(12)));
|
|
code.push_back(EncodeVop1(0x0e, 38, Vgpr(37)));
|
|
code.push_back(EncodeVop1(0x2a, 39, 249));
|
|
code.push_back(EncodeVop1Sdwa(8, 6, 0, 6, 0, 0, 0, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 40, 0xc0000000u);
|
|
AppendVop3(&code, 0x1aa, 41, Vgpr(40), 0, 0, 1, 0, false, 1);
|
|
AppendVMovLiteral(&code, 2, 0xc0003c00u); // low=1.0h, high=-2.0h
|
|
code.push_back(0x7e1016f9u);
|
|
code.push_back(0x00250602u);
|
|
|
|
const u32 results[] = {13, 14, 15, 16, 17, 18, 42, 19, 20, 21, 22, 23, 24, 25,
|
|
26, 27, 28, 29, 30, 36, 32, 33, 34, 35, 38, 39, 41, 8};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorFloatConversionOps",
|
|
code,
|
|
{},
|
|
{0xc0400000u, 0x40e00000u, 7, 0xfffffffdu, 0x00003c00u,
|
|
0x3f800000u, 3, 3, 0x41880000u, 0x42080000u,
|
|
0x424c0000u, 0x42880000u, 0x3f000000u, 0x3e800000u, 0x40000000u,
|
|
0x40400000u, 0x40000000u, 0x40000000u, 0x40000000u, 0x40000000u,
|
|
0x3f000000u, 0x40000000u, 0, 0x3f800000u, 0xbd800000u,
|
|
0x3f800000u, 0x3f800000u, 0x40000000u},
|
|
{O::V_MOV_B32, O::V_CVT_F32_I32, O::V_CVT_F32_U32,
|
|
O::V_CVT_U32_F32, O::V_CVT_I32_F32, O::V_CVT_F16_F32,
|
|
O::V_CVT_F32_F16, O::V_CVT_RPI_I32_F32, O::V_CVT_FLR_I32_F32,
|
|
O::V_CVT_OFF_F32_I4, O::V_CVT_F32_UBYTE0, O::V_CVT_F32_UBYTE1,
|
|
O::V_CVT_F32_UBYTE2, O::V_CVT_F32_UBYTE3, O::V_RCP_F32,
|
|
O::V_FRACT_F32, O::V_TRUNC_F32, O::V_CEIL_F32,
|
|
O::V_RNDNE_F32, O::V_FLOOR_F32, O::V_EXP_F32,
|
|
O::V_LOG_F32, O::V_RSQ_F32, O::V_SQRT_F32,
|
|
O::V_SIN_F32, O::V_COS_F32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorFrexpF32Edges() {
|
|
using O = ShaderOpcode;
|
|
|
|
const std::vector<u32> inputs = {
|
|
0x00000000u, 0x80000000u, 0x3f000000u, 0xbf800000u, 0x40800000u,
|
|
0xc1000000u, 0x00800000u, 0x007fffffu, 0x00000001u, 0x7f800000u,
|
|
0x7fc00000u, 0xff800000u, 0x7fa00001u,
|
|
};
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < static_cast<u32>(inputs.size()); i++) {
|
|
AppendVMovU32(&code, 31, i * sizeof(u32));
|
|
AppendBufferLoadDword(&code, i == 3u ? 23u : i, 31);
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(inputs.size()); i++) {
|
|
const auto exponent_dst = i == 3u ? 19u : 32u + i;
|
|
const auto mantissa_dst = i == 3u ? 21u : 48u + i;
|
|
if (i == 3u) {
|
|
code.push_back(0x7e267ef9u); // captured v_frexp_exp_i32_f32 v19, abs(v23)
|
|
code.push_back(0x00260617u);
|
|
} else {
|
|
code.push_back(EncodeVop1(0x3f, exponent_dst, Vgpr(i)));
|
|
}
|
|
AppendStoreVgpr(&code, exponent_dst, i);
|
|
if (i == 3u) {
|
|
code.push_back(0x7e2a80f9u); // captured v_frexp_mant_f32 v21, abs(v23)
|
|
code.push_back(0x00260617u);
|
|
} else {
|
|
code.push_back(EncodeVop1(0x40, mantissa_dst, Vgpr(i)));
|
|
}
|
|
AppendStoreVgpr(&code, mantissa_dst, inputs.size() + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorFrexpF32Edges";
|
|
test.code = std::move(code);
|
|
test.initial = inputs;
|
|
test.initial.resize(inputs.size() * 2u);
|
|
test.expected = {
|
|
0u, 0u, 0u, 1u, 3u,
|
|
4u, 0xffffff83u, 0xffffff82u, 0xffffff6cu, 0u,
|
|
0u, 0u, 0u, 0x00000000u, 0x80000000u,
|
|
0x3f000000u, 0x3f000000u, 0x3f000000u, 0xbf000000u, 0x3f000000u,
|
|
0x3f7ffffeu, 0x3f000000u, 0x7f800000u, 0x7fc00000u, 0xff800000u,
|
|
0x7fa00001u,
|
|
};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD,
|
|
O::V_FREXP_EXP_I32_F32, O::V_FREXP_MANT_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_FREXP_EXP_I32_F32", inputs.size()},
|
|
{"V_FREXP_MANT_F32", inputs.size()}};
|
|
test.required_spirv = {"FindUMsb", "OpBitFieldUExtract",
|
|
"OpShiftLeftLogical"};
|
|
return test;
|
|
}
|
|
|
|
TestCase CvtF32ToIntSaturatesNaNAndOutOfRange() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x7fc00000u);
|
|
AppendVMovLiteral(&code, 1, 0xbf800000u);
|
|
AppendVMovLiteral(&code, 2, 0x7f800000u);
|
|
AppendVMovLiteral(&code, 3, 0x4f800000u);
|
|
AppendVMovLiteral(&code, 4, 0xff800000u);
|
|
AppendVMovLiteral(&code, 5, 0x4f000000u);
|
|
|
|
code.push_back(EncodeVop1(0x07, 10, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x07, 11, Vgpr(1)));
|
|
code.push_back(EncodeVop1(0x07, 12, Vgpr(2)));
|
|
code.push_back(EncodeVop1(0x07, 13, Vgpr(3)));
|
|
code.push_back(EncodeVop1(0x08, 14, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x08, 15, Vgpr(2)));
|
|
code.push_back(EncodeVop1(0x08, 16, Vgpr(4)));
|
|
code.push_back(EncodeVop1(0x08, 17, Vgpr(5)));
|
|
|
|
for (u32 i = 0; i < 8; i++) {
|
|
AppendStoreVgpr(&code, 10 + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"CvtF32ToIntSaturatesNaNAndOutOfRange",
|
|
code,
|
|
{},
|
|
{0, 0, 0xffffffffu, 0xffffffffu, 0, 0x7fffffffu, 0x80000000u,
|
|
0x7fffffffu},
|
|
{O::V_MOV_B32, O::V_CVT_U32_F32, O::V_CVT_I32_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorSpecialF32FlushesDenormalInputs() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x00000001u);
|
|
code.push_back(EncodeVop1(0x27, 1, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x2a, 2, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x2e, 3, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x33, 4, Vgpr(0)));
|
|
AppendStoreVgpr(&code, 1, 0);
|
|
AppendStoreVgpr(&code, 2, 1);
|
|
AppendStoreVgpr(&code, 3, 2);
|
|
AppendStoreVgpr(&code, 4, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorSpecialF32FlushesDenormalInputs",
|
|
code,
|
|
{},
|
|
{0xff800000u, 0x7f800000u, 0x7f800000u, 0x00000000u},
|
|
{O::V_MOV_B32, O::V_LOG_F32, O::V_RCP_F32, O::V_RSQ_F32,
|
|
O::V_SQRT_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorRcpIflagF32IntegerReciprocal() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 0);
|
|
AppendVMovU32(&code, 1, 1);
|
|
AppendVMovU32(&code, 2, 2);
|
|
AppendVMovLiteral(&code, 3, 0xffffffffu);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
code.push_back(EncodeVop1(0x06, 4 + i, Vgpr(i)));
|
|
code.push_back(EncodeVop1(0x2b, 8 + i, Vgpr(4 + i)));
|
|
}
|
|
AppendVMovLiteral(&code, 12, 0xc0000000u);
|
|
AppendVop3(&code, 0x1ab, 13, Vgpr(12), 0, 0, 1, 0, false, 1);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 8 + i, i);
|
|
}
|
|
AppendStoreVgpr(&code, 13, 4);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "VectorRcpIflagF32IntegerReciprocal";
|
|
test.code = std::move(code);
|
|
test.expected = {0x7f800000u, 0x3f800000u, 0x3f000000u, 0x2f800000u,
|
|
0x3f800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::V_CVT_F32_U32, O::V_RCP_IFLAG_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_RCP_IFLAG_F32", 5}};
|
|
test.ir_counts = {{"FPRecipIFlag32", 5}};
|
|
test.required_spirv = {"OpConvertUToF", "OpFDiv"};
|
|
test.forbidden_spirv = {"8388608"};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorSinCosMaxFiniteSpecialCases() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xff7fffffu);
|
|
code.push_back(EncodeVop1(0x35, 1, Vgpr(0)));
|
|
code.push_back(EncodeVop1(0x36, 2, Vgpr(0)));
|
|
AppendStoreVgpr(&code, 1, 0);
|
|
AppendStoreVgpr(&code, 2, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorSinCosMaxFiniteSpecialCases",
|
|
code,
|
|
{},
|
|
{0x00000000u, 0x3f800000u},
|
|
{O::V_MOV_B32, O::V_SIN_F32, O::V_COS_F32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCompareOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
std::vector<u32> results;
|
|
std::vector<u32> expected;
|
|
u32 dst = 10;
|
|
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
AppendVMovLiteral(&code, 2, 0x40000000u);
|
|
AppendVMovLiteral(&code, 3, 0x40400000u);
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(1)));
|
|
AppendVMovLiteral(&code, 5, 0xffffffffu);
|
|
|
|
auto append_compare = [&](u32 opcode, u32 src0, u32 src1, bool value) {
|
|
if (dst == 31u) {
|
|
dst++;
|
|
}
|
|
code.push_back(EncodeVopc(opcode, src0, src1));
|
|
code.push_back(EncodeVop2(0x01, dst, InlineU32(0), 1));
|
|
results.push_back(dst);
|
|
expected.push_back(value ? 1u : 0u);
|
|
dst++;
|
|
};
|
|
|
|
append_compare(0x00, Vgpr(2), 3, false);
|
|
append_compare(0x01, Vgpr(2), 3, true);
|
|
append_compare(0x02, Vgpr(2), 3, false);
|
|
append_compare(0x03, Vgpr(2), 3, true);
|
|
append_compare(0x04, Vgpr(2), 3, false);
|
|
append_compare(0x05, Vgpr(2), 3, true);
|
|
append_compare(0x06, Vgpr(2), 3, false);
|
|
append_compare(0x07, Vgpr(2), 3, true);
|
|
append_compare(0x08, Vgpr(2), 3, false);
|
|
append_compare(0x09, Vgpr(2), 3, true);
|
|
append_compare(0x0a, Vgpr(2), 3, false);
|
|
append_compare(0x0b, Vgpr(2), 3, true);
|
|
append_compare(0x0c, Vgpr(2), 3, false);
|
|
append_compare(0x0d, Vgpr(2), 3, true);
|
|
append_compare(0x0e, Vgpr(2), 3, false);
|
|
append_compare(0x0f, Vgpr(2), 3, true);
|
|
|
|
append_compare(0x80, Vgpr(5), 4, false);
|
|
append_compare(0x81, Vgpr(5), 4, true);
|
|
append_compare(0x82, Vgpr(5), 4, false);
|
|
append_compare(0x83, Vgpr(5), 4, true);
|
|
append_compare(0x84, Vgpr(5), 4, false);
|
|
append_compare(0x85, Vgpr(5), 4, true);
|
|
append_compare(0x86, Vgpr(5), 4, false);
|
|
append_compare(0x87, Vgpr(5), 4, true);
|
|
|
|
append_compare(0xc0, Vgpr(4), 5, false);
|
|
append_compare(0xc1, Vgpr(4), 5, true);
|
|
append_compare(0xc2, Vgpr(4), 5, false);
|
|
append_compare(0xc3, Vgpr(4), 5, true);
|
|
append_compare(0xc4, Vgpr(4), 5, false);
|
|
append_compare(0xc5, Vgpr(4), 5, true);
|
|
append_compare(0xc6, Vgpr(4), 5, false);
|
|
append_compare(0xc7, Vgpr(4), 5, true);
|
|
|
|
code.push_back(0x7c1d00f9u);
|
|
code.push_back(0x86069201u);
|
|
code.push_back(EncodeVopc(0x84, 249, 4));
|
|
code.push_back(EncodeVopcSdwa(5, 19, 1));
|
|
code.push_back(EncodeVopc(0x01, 250, 3));
|
|
code.push_back(EncodeVop2Dpp(2));
|
|
AppendVMovLiteral(&code, 6, 0xc0000000u);
|
|
AppendVMovLiteral(&code, 7, 0x3f800000u);
|
|
AppendVop3(&code, 0x04, 20, Vgpr(6), Vgpr(7), 0, 1);
|
|
for (u32 i = 0; i < static_cast<u32>(results.size()); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendStoreSgpr(&code, 18, static_cast<u32>(results.size()));
|
|
expected.push_back(1u);
|
|
AppendStoreSgpr(&code, 19, static_cast<u32>(results.size() + 1u));
|
|
expected.push_back(0u);
|
|
AppendStoreSgpr(&code, 106, static_cast<u32>(results.size() + 2u));
|
|
expected.push_back(1u);
|
|
AppendStoreSgpr(&code, 20, static_cast<u32>(results.size() + 3u));
|
|
expected.push_back(1u);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCompareOps",
|
|
code,
|
|
{},
|
|
expected,
|
|
{O::V_MOV_B32, O::V_CNDMASK_B32, O::V_CMP_F_F32,
|
|
O::V_CMP_LT_F32, O::V_CMP_EQ_F32, O::V_CMP_LE_F32,
|
|
O::V_CMP_GT_F32, O::V_CMP_LG_F32, O::V_CMP_GE_F32,
|
|
O::V_CMP_O_F32, O::V_CMP_U_F32, O::V_CMP_NGE_F32,
|
|
O::V_CMP_NLG_F32, O::V_CMP_NGT_F32, O::V_CMP_NLE_F32,
|
|
O::V_CMP_NEQ_F32, O::V_CMP_NLT_F32, O::V_CMP_TRU_F32,
|
|
O::V_CMP_F_I32, O::V_CMP_LT_I32, O::V_CMP_EQ_I32,
|
|
O::V_CMP_LE_I32, O::V_CMP_GT_I32, O::V_CMP_NE_I32,
|
|
O::V_CMP_GE_I32, O::V_CMP_T_I32, O::V_CMP_F_U32,
|
|
O::V_CMP_LT_U32, O::V_CMP_EQ_U32, O::V_CMP_LE_U32,
|
|
O::V_CMP_GT_U32, O::V_CMP_NE_U32, O::V_CMP_GE_U32,
|
|
O::V_CMP_T_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3CompareEqU64OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
|
|
code.push_back(EncodeSop1(0x04, 106, 126)); // s_mov_b64 vcc, exec
|
|
code.push_back(0xd4e2006au);
|
|
code.push_back(0x0000d47eu); // v_cmp_eq_u64 vcc, exec, vcc
|
|
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
|
|
AppendSMovLiteral(&code, 107, 1); // high-dword mismatch
|
|
code.push_back(0xd4e2006au);
|
|
code.push_back(0x0000d47eu); // v_cmp_eq_u64 vcc, exec, vcc
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVop3CompareEqU64OnGpu",
|
|
code,
|
|
{},
|
|
{1, 0},
|
|
{O::V_MOV_B32, O::S_MOV_B64, O::V_CMP_EQ_U64,
|
|
O::V_CNDMASK_B32, O::S_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMP_EQ_U64 vcc_lo, exec_lo, vcc_lo", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3CompareNeU64OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
|
|
code.push_back(EncodeSop1(0x04, 106, 126)); // s_mov_b64 vcc, exec
|
|
code.push_back(0xd4e5006au);
|
|
code.push_back(0x0000d47eu); // v_cmp_ne_u64 vcc, exec, vcc
|
|
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
|
|
AppendSMovLiteral(&code, 106, 0);
|
|
AppendSMovLiteral(&code, 107, 0);
|
|
code.push_back(0xd4e5006au);
|
|
code.push_back(0x0000d47eu); // v_cmp_ne_u64 vcc, exec, vcc
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3CompareNeU64OnGpu",
|
|
code,
|
|
{},
|
|
{0, 1},
|
|
{O::V_MOV_B32, O::S_MOV_B64, O::V_CMP_NE_U64, O::V_CNDMASK_B32,
|
|
O::S_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVopcCmpxNeU64CapturedExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 7, 0);
|
|
AppendVMovU32(&code, 8, 0);
|
|
AppendVMovU32(&code, 31, 0);
|
|
code.push_back(0x7dea0e80u); // v_cmpx_ne_u64 exec, 0, v[7:8]
|
|
AppendBufferStoreDword(&code, 8, 31);
|
|
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
AppendVMovU32(&code, 8, 1);
|
|
AppendVMovU32(&code, 31, 4);
|
|
code.push_back(0x7dea0e80u); // v_cmpx_ne_u64 exec, 0, v[7:8]
|
|
AppendBufferStoreDword(&code, 8, 31);
|
|
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovU32(&code, 3, 0);
|
|
AppendVMovU32(&code, 31, 8);
|
|
code.push_back(0x7dea0480u); // v_cmpx_ne_u64 exec, 0, v[2:3]
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
AppendVMovU32(&code, 3, 1);
|
|
AppendVMovU32(&code, 31, 12);
|
|
code.push_back(0x7dea0480u); // v_cmpx_ne_u64 exec, 0, v[2:3]
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVopcCmpxNeU64CapturedExecMask",
|
|
code,
|
|
{0x11223344u, 0x55667788u, 0xaabbccddu, 0xeeff0011u},
|
|
{0x11223344u, 1u, 0xaabbccddu, 1u},
|
|
{O::V_MOV_B32, O::V_CMPX_NE_U64, O::BUFFER_STORE_DWORD,
|
|
O::S_MOV_B32, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMPX_NE_U64 exec_lo, 0, v7", 2},
|
|
{"V_CMPX_NE_U64 exec_lo, 0, v2", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3CmpxNeI64CapturedExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 7, 0);
|
|
AppendVMovU32(&code, 8, 0);
|
|
AppendVMovU32(&code, 31, 0);
|
|
code.push_back(0xd4b5007eu);
|
|
code.push_back(0x00020e80u); // v_cmpx_ne_i64 exec, 0, v[7:8]
|
|
AppendBufferStoreDword(&code, 8, 31);
|
|
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
AppendVMovU32(&code, 8, 1);
|
|
AppendVMovU32(&code, 31, 4);
|
|
code.push_back(0xd4b5007eu);
|
|
code.push_back(0x00020e80u); // v_cmpx_ne_i64 exec, 0, v[7:8]
|
|
AppendBufferStoreDword(&code, 8, 31);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVop3CmpxNeI64CapturedExecMask",
|
|
code,
|
|
{0x11223344u, 0x55667788u},
|
|
{0x11223344u, 1u},
|
|
{O::V_MOV_B32, O::V_CMPX_NE_I64, O::BUFFER_STORE_DWORD,
|
|
O::S_MOV_B32, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMPX_NE_I64 exec_lo, 0, v7", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3CompareGtU64OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
|
|
AppendSMovLiteral(&code, 4, 0);
|
|
AppendSMovLiteral(&code, 5, 0x80000000u);
|
|
code.push_back(0xd4e4006au);
|
|
code.push_back(0x00010004u); // v_cmp_gt_u64 vcc, s[4:5], 0
|
|
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
|
|
code.push_back(0xd4e4006au);
|
|
code.push_back(0x0001006au); // v_cmp_gt_u64 vcc, vcc, 0
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, 1);
|
|
|
|
AppendSMovLiteral(&code, 106, 0);
|
|
AppendSMovLiteral(&code, 107, 0);
|
|
code.push_back(0xd4e4006au);
|
|
code.push_back(0x0001006au); // v_cmp_gt_u64 vcc, vcc, 0
|
|
code.push_back(EncodeVop2(0x01, 4, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 4, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVop3CompareGtU64OnGpu",
|
|
code,
|
|
{},
|
|
{1, 1, 0},
|
|
{O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_GT_U64, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVopcCompareLtU64OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 5);
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovU32(&code, 2, 1);
|
|
|
|
AppendSMovLiteral(&code, 4, 4);
|
|
AppendSMovLiteral(&code, 5, 0);
|
|
code.push_back(EncodeVopc(0xe1, 4, 0));
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 2));
|
|
AppendStoreVgpr(&code, 3, 0);
|
|
|
|
AppendSMovLiteral(&code, 4, 6);
|
|
code.push_back(EncodeVopc(0xe1, 4, 0));
|
|
code.push_back(EncodeVop2(0x01, 4, InlineU32(0), 2));
|
|
AppendStoreVgpr(&code, 4, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVopcCompareLtU64OnGpu",
|
|
code,
|
|
{},
|
|
{1, 0},
|
|
{O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_LT_U64,
|
|
O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMP_LT_U64 vcc_lo, s4, v0", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVop3CompareEqI64OnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
code.push_back(EncodeSop1(0x04, 4, 126)); // s_mov_b64 s[4:5], exec
|
|
|
|
code.push_back(0xd4a2006au);
|
|
code.push_back(0x0000087eu); // v_cmp_eq_i64 vcc, exec, s[4:5]
|
|
code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 2, 0);
|
|
|
|
AppendSMovLiteral(&code, 4, 0);
|
|
code.push_back(0xd4a2006au);
|
|
code.push_back(0x0000087eu); // low dword mismatch
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3CompareEqI64OnGpu",
|
|
code,
|
|
{},
|
|
{1, 0},
|
|
{O::V_MOV_B32, O::S_MOV_B64, O::V_CMP_EQ_I64, O::V_CNDMASK_B32,
|
|
O::S_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCompareClassF32() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
std::vector<u32> expected;
|
|
AppendVMovU32(&code, 1, 1);
|
|
|
|
u32 out = 0;
|
|
auto append_class = [&](u32 bits, u32 class_mask, bool match) {
|
|
AppendVMovLiteral(&code, 0, bits);
|
|
AppendVMovU32(&code, 2, class_mask);
|
|
code.push_back(EncodeVopc(0x88, Vgpr(0), 2));
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, 3, out++);
|
|
expected.push_back(match ? 1u : 0u);
|
|
};
|
|
|
|
append_class(0x7fc00000u, 1u << 1u, true);
|
|
append_class(0xff800000u, 1u << 2u, true);
|
|
append_class(0xbf800000u, 1u << 3u, true);
|
|
append_class(0x80000000u, 1u << 5u, true);
|
|
append_class(0x00000000u, 1u << 6u, true);
|
|
append_class(0x40000000u, 1u << 8u, true);
|
|
append_class(0x7f800000u, 1u << 9u, true);
|
|
append_class(0x40000000u, 1u << 3u, false);
|
|
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovU32(&code, 2, 1u << 3u);
|
|
AppendVop3(&code, 0x88, 20, Vgpr(0), Vgpr(2), 0, 0, 0, false, 0, 1);
|
|
AppendStoreSgprPair(&code, 20, out);
|
|
expected.push_back(1u);
|
|
expected.push_back(0u);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCompareClassF32",
|
|
code,
|
|
{},
|
|
expected,
|
|
{O::V_MOV_B32, O::V_CMP_CLASS_F32, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVopcSdwaCmpxClassF32CapturedExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 13, 0x7fc00000u); // Quiet NaN.
|
|
AppendVMovU32(&code, 3, 7);
|
|
AppendVMovU32(&code, 31, 0);
|
|
code.push_back(EncodeSMovB32(106, InlineU32(2))); // qNaN class mask.
|
|
code.insert(code.end(), {0x7d30d4f9u, 0x8606000du});
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1))); // Restore lane-zero EXEC.
|
|
AppendVMovLiteral(&code, 13, 0x3f800000u); // Positive normal.
|
|
AppendVMovU32(&code, 3, 9);
|
|
AppendVMovU32(&code, 31, 4);
|
|
code.push_back(EncodeSMovB32(106, InlineU32(2))); // qNaN class mask.
|
|
code.insert(code.end(), {0x7d30d4f9u, 0x8606000du});
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{
|
|
"VectorVopcSdwaCmpxClassF32CapturedExecMask",
|
|
code,
|
|
{0x11111111u, 0x22222222u},
|
|
{7u, 0x22222222u},
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::V_CMPX_CLASS_F32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.decoded_counts = {
|
|
{"V_CMPX_CLASS_F32 exec_lo, v13, vcc_lo", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase Wave32VccMasksPreserveOtherHalf() {
|
|
using O = ShaderOpcode;
|
|
TestCase test;
|
|
test.name = "Wave32VccMasksPreserveOtherHalf";
|
|
auto &code = test.code;
|
|
AppendSMovLiteral(&code, 106, 0x12345678u);
|
|
AppendSMovLiteral(&code, 107, 0x2468ace0u);
|
|
AppendVMovU32(&code, 1, 20);
|
|
u32 offset = 0;
|
|
const auto check_masks = [&](u32 low, u32 high) {
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 106, 0, offset);
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 107, 0, offset + 4);
|
|
AppendVop3(&code, 0x101, 2, InlineU32(10), Vgpr(1), 107);
|
|
code.push_back(EncodeVop2(0x01, 3, InlineU32(10), 1));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, offset + 8);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0, offset + 12);
|
|
test.expected.insert(test.expected.end(), 4, low);
|
|
test.expected.insert(test.expected.end(), 4, high);
|
|
for (u32 mask : {high, low}) {
|
|
for (u32 lane = 0; lane < 4; ++lane) {
|
|
test.expected.push_back((mask & (1u << lane)) != 0 ? 20u : 10u);
|
|
}
|
|
}
|
|
offset += 16;
|
|
};
|
|
// PPSA10737 keeps a table offset in VCC_LO while comparing into VCC_HI.
|
|
AppendVop3(&code, 0xc2, 107, InlineU32(1), Vgpr(0));
|
|
check_masks(0x12345678u, 2u);
|
|
AppendVop3(&code, 0xc2, 106, InlineU32(2), Vgpr(0));
|
|
check_masks(4u, 2u);
|
|
code.push_back(EncodeSop2(0x10, 106, 106, 107)); // s_or_b32
|
|
check_masks(6u, 2u);
|
|
code.push_back(EncodeSop1(0x04, 106, 193u)); // Explicit s_mov_b64 writes both.
|
|
check_masks(0xffffffffu, 0xffffffffu);
|
|
AppendEnd(&code);
|
|
test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::S_OR_B32, O::V_MOV_B32,
|
|
O::V_CMP_EQ_U32, O::V_CNDMASK_B32, O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = 32;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorCompareF16Ops() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
std::vector<u32> expected;
|
|
AppendVMovU32(&code, 1, 1);
|
|
AppendVMovLiteral(&code, 2, 0x40003c00u); // low=1.0h, high=2.0h
|
|
AppendVMovLiteral(&code, 3, 0x44004200u); // low=3.0h, high=4.0h
|
|
AppendVMovLiteral(&code, 4, 0x00007e00u); // low=qNaN
|
|
|
|
u32 dst = 10;
|
|
u32 out = 0;
|
|
auto append_compare = [&](u32 opcode, u32 src0, u32 src1, bool value) {
|
|
code.push_back(EncodeVopc(opcode, src0, src1));
|
|
code.push_back(EncodeVop2(0x01, dst, InlineU32(0), 1));
|
|
AppendStoreVgpr(&code, dst, out++);
|
|
expected.push_back(value ? 1u : 0u);
|
|
dst++;
|
|
};
|
|
|
|
append_compare(0xc9, Vgpr(2), 3, true);
|
|
append_compare(0xca, Vgpr(2), 2, true);
|
|
append_compare(0xcb, Vgpr(2), 3, true);
|
|
append_compare(0xcc, Vgpr(3), 2, true);
|
|
append_compare(0xcd, Vgpr(2), 3, true);
|
|
append_compare(0xce, Vgpr(3), 2, true);
|
|
append_compare(0xed, Vgpr(4), 2, true);
|
|
|
|
auto append_cmpx = [&](u32 opcode, u32 src0, u32 src1) {
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
code.push_back(EncodeVopc(opcode, src0, src1));
|
|
AppendStoreVgpr(&code, 1, out++);
|
|
expected.push_back(1u);
|
|
};
|
|
|
|
append_cmpx(0xd9, Vgpr(2), 3);
|
|
append_cmpx(0xda, Vgpr(2), 2);
|
|
append_cmpx(0xdb, Vgpr(2), 3);
|
|
append_cmpx(0xdc, Vgpr(3), 2);
|
|
append_cmpx(0xde, Vgpr(3), 2);
|
|
append_cmpx(0xfd, Vgpr(4), 2);
|
|
append_cmpx(0xfe, Vgpr(2), 2);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCompareF16Ops",
|
|
code,
|
|
{},
|
|
expected,
|
|
{O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_LT_F16, O::V_CMP_EQ_F16,
|
|
O::V_CMP_LE_F16, O::V_CMP_GT_F16, O::V_CMP_LG_F16, O::V_CMP_GE_F16,
|
|
O::V_CMP_NEQ_F16, O::V_CMPX_LT_F16, O::V_CMPX_EQ_F16,
|
|
O::V_CMPX_LE_F16, O::V_CMPX_GT_F16, O::V_CMPX_GE_F16,
|
|
O::V_CMPX_NEQ_F16, O::V_CMPX_NLT_F16, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop2SdwaCndmaskSourceModifier() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 6, 0x00000000u);
|
|
AppendVMovLiteral(&code, 47, 0x12345678u);
|
|
AppendVMovLiteral(&code, 53, 0x3f800000u);
|
|
code.push_back(EncodeVopc(0xc7, Vgpr(6), 6)); // v_cmp_t_u32
|
|
code.push_back(0x025e6af9u);
|
|
code.push_back(0x16060635u);
|
|
AppendStoreVgpr(&code, 47, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop2SdwaCndmaskSourceModifier",
|
|
code,
|
|
{},
|
|
{0xbf800000u},
|
|
{O::V_MOV_B32, O::V_CMP_T_U32, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase Vop2SdwaCndmaskCapturedByte3Source() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x55667788u);
|
|
AppendBufferLoadDword(&code, 3, 30);
|
|
code.push_back(EncodeVopc(0xc0, Vgpr(0), 0)); // v_cmp_f_u32
|
|
code.push_back(0x023a00f9u);
|
|
code.push_back(0x06030603u);
|
|
AppendStoreVgpr(&code, 29, 1);
|
|
code.push_back(EncodeVopc(0xc7, Vgpr(0), 0)); // v_cmp_t_u32
|
|
code.push_back(0x023a00f9u);
|
|
code.push_back(0x06030603u);
|
|
AppendStoreVgpr(&code, 29, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop2SdwaCndmaskCapturedByte3Source";
|
|
test.code = code;
|
|
test.initial = {0xabcd1234u, 0, 0};
|
|
test.expected = {0xabcd1234u, 0x000000abu, 0x55667788u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_CMP_F_U32,
|
|
O::V_CMP_T_U32, O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.decoded_counts = {{"V_CNDMASK_B32 v29, v3.sdwa(sel=3,sext=0), v0", 2}};
|
|
test.required_spirv = {"OpBitFieldUExtract", "OpSelect"};
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop3CndmaskUsesSgprMaskLaneBits() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0), EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1), EncodeVopc(0xc2, InlineU32(0), 0),
|
|
EncodeSop1(0x04, 4, 106),
|
|
};
|
|
AppendVop3(&code, 0x101, 2, InlineU32(10), InlineU32(20), 4);
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 1));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Vop3CndmaskUsesSgprMaskLaneBits";
|
|
test.code = code;
|
|
test.expected = {20, 10, 10, 10, 20, 10, 10, 10};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase Vop3CndmaskAllowsDataSourceModifier() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 7, 0x3f800000u);
|
|
AppendSMovLiteral(&code, 34, 1);
|
|
AppendSMovLiteral(&code, 35, 0);
|
|
code.push_back(0xd5010004u);
|
|
code.push_back(0x408a0f07u);
|
|
AppendStoreVgpr(&code, 4, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"Vop3CndmaskAllowsDataSourceModifier",
|
|
code,
|
|
{},
|
|
{0xbf800000u},
|
|
{O::V_MOV_B32, O::S_MOV_B32, O::V_CNDMASK_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorCompareExecOps() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, InlineU32(1)));
|
|
AppendVMovLiteral(&code, 2, 0x40000000u);
|
|
AppendVMovLiteral(&code, 3, 0x40400000u);
|
|
code.push_back(EncodeVop1(0x01, 4, InlineU32(1)));
|
|
AppendVMovLiteral(&code, 5, 0xffffffffu);
|
|
|
|
u32 out = 0;
|
|
auto append_cmpx = [&](u32 opcode, u32 src0, u32 src1) {
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
code.push_back(EncodeVopc(opcode, src0, src1));
|
|
AppendStoreVgpr(&code, 1, out++);
|
|
};
|
|
|
|
append_cmpx(0x11, Vgpr(2), 3);
|
|
append_cmpx(0x12, Vgpr(3), 3);
|
|
append_cmpx(0x13, Vgpr(2), 3);
|
|
append_cmpx(0x14, Vgpr(3), 2);
|
|
append_cmpx(0x15, Vgpr(2), 3);
|
|
append_cmpx(0x16, Vgpr(3), 3);
|
|
append_cmpx(0x19, Vgpr(2), 3);
|
|
append_cmpx(0x1a, Vgpr(3), 3);
|
|
append_cmpx(0x1b, Vgpr(2), 3);
|
|
append_cmpx(0x1c, Vgpr(3), 2);
|
|
append_cmpx(0x1d, Vgpr(2), 3);
|
|
append_cmpx(0x1e, Vgpr(3), 3);
|
|
append_cmpx(0x91, Vgpr(5), 4);
|
|
append_cmpx(0x92, Vgpr(4), 4);
|
|
append_cmpx(0x93, Vgpr(5), 4);
|
|
append_cmpx(0x94, Vgpr(4), 5);
|
|
append_cmpx(0x95, Vgpr(5), 4);
|
|
append_cmpx(0x96, Vgpr(4), 4);
|
|
append_cmpx(0xd1, Vgpr(4), 5);
|
|
append_cmpx(0xd2, Vgpr(4), 4);
|
|
append_cmpx(0xd3, Vgpr(4), 5);
|
|
append_cmpx(0xd4, Vgpr(5), 4);
|
|
append_cmpx(0xd5, Vgpr(4), 5);
|
|
append_cmpx(0xd6, Vgpr(4), 4);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCompareExecOps",
|
|
code,
|
|
{},
|
|
std::vector<u32>(out, 1),
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::V_CMPX_LT_F32,
|
|
O::V_CMPX_EQ_F32, O::V_CMPX_LE_F32, O::V_CMPX_GT_F32,
|
|
O::V_CMPX_LG_F32, O::V_CMPX_GE_F32, O::V_CMPX_NGE_F32,
|
|
O::V_CMPX_NLG_F32, O::V_CMPX_NGT_F32, O::V_CMPX_NLE_F32,
|
|
O::V_CMPX_NEQ_F32, O::V_CMPX_NLT_F32, O::V_CMPX_LT_I32,
|
|
O::V_CMPX_EQ_I32, O::V_CMPX_LE_I32, O::V_CMPX_GT_I32,
|
|
O::V_CMPX_NE_I32, O::V_CMPX_GE_I32, O::V_CMPX_LT_U32,
|
|
O::V_CMPX_EQ_U32, O::V_CMPX_LE_U32, O::V_CMPX_GT_U32,
|
|
O::V_CMPX_NE_U32, O::V_CMPX_GE_U32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3FloatCompareNegSourceModifier() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVop3(&code, 0x01, 20, Vgpr(0), Vgpr(1), 0, 0, 0, false, 0,
|
|
0x1); // -1.0 < 0.0
|
|
AppendStoreSgprPair(&code, 20, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3FloatCompareNegSourceModifier",
|
|
code,
|
|
{},
|
|
{1, 0},
|
|
{O::V_MOV_B32, O::V_CMP_LT_F32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVop3CmpxWritesExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 2);
|
|
AppendVMovU32(&code, 1, 1);
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovU32(&code, 30, 0);
|
|
AppendVop3(&code, 0xd1, 5, Vgpr(0), Vgpr(1)); // v_cmpx_lt_u32, false
|
|
AppendVMovU32(&code, 2, 7);
|
|
AppendBufferStoreDword(&code, 2, 30);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVop3CmpxWritesExecMask",
|
|
code,
|
|
{0},
|
|
{0},
|
|
{O::V_MOV_B32, O::V_CMPX_LT_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVopcSdwaCmpxWritesExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 2);
|
|
AppendVMovU32(&code, 1, 1);
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovU32(&code, 30, 0);
|
|
code.push_back(EncodeVopc(0xd1, 249, 1)); // v_cmpx_lt_u32.sdwa, false
|
|
code.push_back(EncodeVopcSdwa(0));
|
|
AppendVMovU32(&code, 2, 7);
|
|
AppendBufferStoreDword(&code, 2, 30);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorVopcSdwaCmpxWritesExecMask",
|
|
code,
|
|
{0},
|
|
{0},
|
|
{O::V_MOV_B32, O::V_CMPX_LT_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase VectorVopcCmpxGtU16CapturedSdwaExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 2, 0x00008000u);
|
|
AppendVMovU32(&code, 3, 7);
|
|
AppendVMovU32(&code, 31, 0);
|
|
code.insert(code.end(), {0x7d7900f9u, 0x86060002u});
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
AppendVMovU32(&code, 2, 0x00010000u);
|
|
AppendVMovU32(&code, 3, 9);
|
|
AppendVMovU32(&code, 31, 4);
|
|
code.insert(code.end(), {0x7d7900f9u, 0x86060002u});
|
|
AppendBufferStoreDword(&code, 3, 31);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVopcCmpxGtU16CapturedSdwaExecMask",
|
|
code,
|
|
{0x11111111u, 0x22222222u},
|
|
{7, 0x22222222u},
|
|
{O::V_MOV_B32, O::V_CMPX_GT_U16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMPX_GT_U16", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorVopcCmpxNgtF16CapturedSdwaExecMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 31, 0x7e003c00u);
|
|
AppendVMovU32(&code, 3, 7);
|
|
AppendVMovU32(&code, 30, 0);
|
|
code.insert(code.end(), {0x7df700f9u, 0x8605001fu});
|
|
AppendBufferStoreDword(&code, 3, 30);
|
|
AppendVMovLiteral(&code, 31, 0x3c007e00u);
|
|
AppendVMovU32(&code, 3, 9);
|
|
AppendVMovU32(&code, 30, 4);
|
|
code.insert(code.end(), {0x7df700f9u, 0x8605001fu});
|
|
AppendBufferStoreDword(&code, 3, 30);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"VectorVopcCmpxNgtF16CapturedSdwaExecMask",
|
|
code,
|
|
{0x11111111u, 0x22222222u},
|
|
{7, 0x22222222u},
|
|
{O::V_MOV_B32, O::V_CMPX_NGT_F16, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
test.decoded_counts = {{"V_CMPX_NGT_F16", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase VectorCompareInvertedMaskSelect() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeVopc(0xc7, Vgpr(0), 0)); // v_cmp_t_u32 v0, v0
|
|
code.push_back(EncodeSop1(0x08, 106, 106)); // s_not_b64 vcc, vcc
|
|
code.push_back(EncodeVop2(0x01, 1, Vgpr(0), 128)); // v_cndmask_b32 v1, v0, 0
|
|
AppendBufferStoreDword(&code, 1, 30);
|
|
AppendEnd(&code);
|
|
|
|
return {"VectorCompareInvertedMaskSelect",
|
|
code,
|
|
{},
|
|
{0x12345678u},
|
|
{O::V_MOV_B32, O::V_CMP_T_U32, O::S_NOT_B64, O::V_CNDMASK_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BranchSelect() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeSMovB32(0, InlineU32(0)),
|
|
EncodeSopc(0x06, 0, InlineU32(0)),
|
|
EncodeSopp(0x05, 2),
|
|
EncodeSMovB32(1, InlineU32(1)),
|
|
EncodeSopp(0x02, 1),
|
|
EncodeSMovB32(1, InlineU32(7)),
|
|
EncodeSop2(0x0a, 2, 1, InlineU32(2)),
|
|
EncodeVop1(0x01, 0, 2),
|
|
};
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"BranchSelect",
|
|
code,
|
|
{},
|
|
{7},
|
|
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CBRANCH_SCC1, O::S_BRANCH,
|
|
O::S_CSELECT_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase SimpleLoop() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeSMovB32(0, InlineU32(0)),
|
|
EncodeSopc(0x0a, 0, InlineU32(4)),
|
|
EncodeSopp(0x04, 2),
|
|
EncodeSop2(0x00, 0, 0, InlineU32(1)),
|
|
EncodeSopp(0x02, 0xfffcu),
|
|
EncodeVop1(0x01, 0, 0),
|
|
};
|
|
AppendBufferStoreDword(&code, 0, 30);
|
|
AppendEnd(&code);
|
|
return {"SimpleLoop",
|
|
code,
|
|
{},
|
|
{4},
|
|
{O::S_MOV_B32, O::S_CMP_LT_U32, O::S_CBRANCH_SCC0, O::S_ADD_U32,
|
|
O::S_BRANCH, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase SharedReturnKeepsSelectedValues() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 5, 4),
|
|
EncodeVop2(0x1a, 5, InlineU32(6), 5),
|
|
EncodeVop2(0x25, 5, Vgpr(0), 5),
|
|
EncodeSMovB32(8, InlineU32(10)),
|
|
EncodeSopc(0x06, 4, InlineU32(0)),
|
|
0,
|
|
EncodeSMovB32(8, InlineU32(20)),
|
|
EncodeSopc(0x06, 4, InlineU32(1)),
|
|
0,
|
|
EncodeSMovB32(8, InlineU32(30)),
|
|
};
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 8, 5, 0);
|
|
AppendEnd(&code);
|
|
const auto shared_return = static_cast<u32>(code.size());
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 8, 5, 0);
|
|
AppendEnd(&code);
|
|
code[5] = EncodeSopp(0x05, shared_return - 6u);
|
|
code[8] = EncodeSopp(0x05, shared_return - 9u);
|
|
|
|
TestCase test;
|
|
test.name = "SharedReturnKeepsSelectedValues";
|
|
test.code = code;
|
|
for (u32 group = 0; group < 3; group++) {
|
|
test.expected.insert(test.expected.end(), 64, (group + 1u) * 10u);
|
|
}
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CBRANCH_SCC1,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpSelectionMerge"};
|
|
test.forbidden_spirv = {"OpSwitch"};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 4;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 3;
|
|
return test;
|
|
}
|
|
|
|
TestCase BranchVccnzUsesWaveMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0),
|
|
EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1),
|
|
EncodeVop2(0x1a, 3, InlineU32(2), 1),
|
|
EncodeVopc(0xc2, InlineU32(0), 0),
|
|
EncodeSopp(0x07, 2),
|
|
EncodeVop1(0x01, 2, InlineU32(11)),
|
|
EncodeSopp(0x02, 1),
|
|
};
|
|
AppendVMovU32(&code, 2, 42);
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BranchVccnzUsesWaveMask";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(8, 42);
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::V_CMP_EQ_U32, O::S_CBRANCH_VCCNZ, O::S_BRANCH,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarMemoryLoadVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
struct Load {
|
|
u32 opcode;
|
|
u32 dst;
|
|
u32 byte_offset;
|
|
u32 dwords;
|
|
};
|
|
|
|
const Load s_loads[] = {
|
|
{0x00, 20, 0, 1}, {0x01, 22, 4, 2}, {0x02, 24, 12, 4},
|
|
{0x03, 28, 28, 8}, {0x04, 36, 60, 16},
|
|
};
|
|
const Load s_buffer_loads[] = {
|
|
{0x08, 52, 0, 1}, {0x09, 54, 4, 2}, {0x0a, 56, 12, 4},
|
|
{0x0b, 60, 28, 8}, {0x0c, 68, 60, 16},
|
|
};
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
for (const auto &load : s_loads) {
|
|
AppendSmemLoadOpcode(&code, load.opcode, load.dst, load.byte_offset);
|
|
}
|
|
for (const auto &load : s_buffer_loads) {
|
|
AppendSmemLoadOpcode(&code, load.opcode, load.dst, load.byte_offset);
|
|
}
|
|
|
|
u32 out = 0;
|
|
for (const auto &load : s_loads) {
|
|
for (u32 i = 0; i < load.dwords; i++) {
|
|
AppendStoreSgpr(&code, load.dst + i, out++);
|
|
}
|
|
}
|
|
for (const auto &load : s_buffer_loads) {
|
|
for (u32 i = 0; i < load.dwords; i++) {
|
|
AppendStoreSgpr(&code, load.dst + i, out++);
|
|
}
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> initial;
|
|
for (u32 i = 0; i < 31u; i++) {
|
|
initial.push_back(0x10000000u + i);
|
|
}
|
|
std::vector<u32> expected = initial;
|
|
expected.insert(expected.end(), initial.begin(), initial.end());
|
|
|
|
return {"ScalarMemoryLoadVariants",
|
|
code,
|
|
initial,
|
|
expected,
|
|
{O::S_MOV_B32, O::S_LOAD_DWORD, O::S_LOAD_DWORDX2, O::S_LOAD_DWORDX4,
|
|
O::S_LOAD_DWORDX8, O::S_LOAD_DWORDX16, O::S_BUFFER_LOAD_DWORD,
|
|
O::S_BUFFER_LOAD_DWORDX2, O::S_BUFFER_LOAD_DWORDX4,
|
|
O::S_BUFFER_LOAD_DWORDX8, O::S_BUFFER_LOAD_DWORDX16, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase ScalarLoadSignedImmediateOffsetAddsSoffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(8)));
|
|
code.push_back(EncodeSMovB32(2, InlineU32(0)));
|
|
code.push_back(EncodeSMovB32(3, InlineU32(0)));
|
|
code.push_back(EncodeSmem0(0x00, 1, 1));
|
|
code.push_back(EncodeSmem1(0x1ffffcu, 0));
|
|
AppendStoreSgpr(&code, 1, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarLoadSignedImmediateOffsetAddsSoffset",
|
|
code,
|
|
{0x11111111u, 0x22222222u},
|
|
{0x22222222u, 0x22222222u},
|
|
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadStore() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendBufferLoadDword(&code, 0, 30);
|
|
code.push_back(EncodeVop1(0x01, 31, InlineU32(4)));
|
|
AppendBufferStoreDword(&code, 0, 31);
|
|
AppendEnd(&code);
|
|
return {
|
|
"BufferLoadStore",
|
|
code,
|
|
{0x11223344u, 0},
|
|
{0x11223344u, 0x11223344u},
|
|
{O::BUFFER_LOAD_DWORD, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadDwordOffenIdxenUsesVaddrPlusOneOffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 8);
|
|
code.push_back(EncodeMubuf0(0x0cu, 0, true, true));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"BufferLoadDwordOffenIdxenUsesVaddrPlusOneOffset",
|
|
code,
|
|
{0x11111111u, 0x22222222u, 0x33333333u},
|
|
{0x33333333u, 0x22222222u, 0x33333333u},
|
|
{O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferStoreDwordOffenIdxenUsesVaddrPlusOneOffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 12);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
code.push_back(EncodeMubuf0(0x1cu, 0, true, true));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferStoreDwordOffenIdxenUsesVaddrPlusOneOffset",
|
|
code,
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u},
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0xabcdef01u},
|
|
{O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadDwordNoAddressFlagsIgnoresVaddr() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 8);
|
|
code.push_back(EncodeMubuf0(0x0cu, 0, false, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"BufferLoadDwordNoAddressFlagsIgnoresVaddr",
|
|
code,
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0},
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0x11111111u},
|
|
{O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadDwordIdxenUsesDescriptorStride() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
code.push_back(EncodeMubuf0(0x0cu, 0, true, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadDwordIdxenUsesDescriptorStride";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u, 0x66666666u,
|
|
0x77777777u, 0x88888888u, 0x99aabbccu};
|
|
test.expected = {0x99aabbccu, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u, 0x66666666u,
|
|
0x77777777u, 0x88888888u, 0x99aabbccu};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(16, 3);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreDwordIdxenUsesDescriptorStride() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
code.push_back(EncodeMubuf0(0x1cu, 0, true, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreDwordIdxenUsesDescriptorStride";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u, 0x66666666u,
|
|
0x77777777u, 0x88888888u, 0x99aabbccu};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u, 0x66666666u,
|
|
0x77777777u, 0x88888888u, 0xabcdef01u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(16, 3);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreDwordAppliesHostOffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
code.push_back(EncodeMubuf0(0x1cu, 0, false, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreDwordAppliesHostOffset";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x33333333u, 0xabcdef01u};
|
|
test.storage_buffer_range_dwords = 1;
|
|
test.storage_buffer_offsets = {12};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(4, 1);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferOffsetsUsePackedLaneAndStorageFallback() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
for (u32 base : {0u, 4u}) {
|
|
AppendSMovLiteral(&code, base, 0x1000u + base * 0x1000u);
|
|
AppendSMovLiteral(&code, base + 1u, 4u << 16u);
|
|
AppendSMovLiteral(&code, base + 2u, 1u);
|
|
AppendSMovLiteral(&code, base + 3u, 1u << 24u);
|
|
}
|
|
AppendVMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeMubuf0(0x1cu, 0, false, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendVMovLiteral(&code, 1, 0xabcdef01u);
|
|
code.push_back(EncodeMubuf0(0x1cu, 0, false, false));
|
|
code.push_back(EncodeMubuf1(1, 1, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferOffsetsUsePackedLaneAndStorageFallback";
|
|
test.code = std::move(code);
|
|
test.initial = {0, 0, 0, 0};
|
|
test.expected = {0x12345678u, 0, 0, 0xabcdef01u};
|
|
test.storage_buffer_range_dwords = 1;
|
|
test.storage_buffer_offsets = {0, 12};
|
|
test.expand_shader_data_storage = true;
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x08, 0, 20);
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendBufferLoadOpcode(&code, 0x09, 1, 20);
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x0a, 2, 20);
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendBufferLoadOpcode(&code, 0x0b, 3, 20);
|
|
AppendVMovU32(&code, 20, 8);
|
|
AppendBufferLoadOpcode(&code, 0x0d, 4, 20);
|
|
AppendBufferLoadOpcode(&code, 0x0f, 6, 20);
|
|
AppendBufferLoadOpcode(&code, 0x0e, 9, 20);
|
|
|
|
for (u32 i = 0; i < 13u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferLoadVariants",
|
|
code,
|
|
{0x80ff7f01u, 0x00008001u, 0x11223344u, 0x55667788u, 0x99aabbccu,
|
|
0xddeeff00u},
|
|
{0x01u, 0xffffffffu, 0x7f01u, 0xffff80ffu, 0x11223344u, 0x55667788u,
|
|
0x11223344u, 0x55667788u, 0x99aabbccu, 0x11223344u, 0x55667788u,
|
|
0x99aabbccu, 0xddeeff00u},
|
|
{O::BUFFER_LOAD_UBYTE, O::BUFFER_LOAD_SBYTE, O::BUFFER_LOAD_USHORT,
|
|
O::BUFFER_LOAD_SSHORT, O::BUFFER_LOAD_DWORDX2,
|
|
O::BUFFER_LOAD_DWORDX3, O::BUFFER_LOAD_DWORDX4, O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadDwordx4SnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMubuf0(0x0eu, 0, true, true));
|
|
code.push_back(EncodeMubuf1(21, 0, 21));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 4 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadDwordx4SnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
|
|
0, 0, 0, 0};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
|
|
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX4, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(16, 2);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadDwordx2SnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMubuf0(0x0du, 0, true, true));
|
|
code.push_back(EncodeMubuf1(21, 0, 21));
|
|
for (u32 i = 0; i < 2; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 2 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadDwordx2SnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0, 0};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x11111111u, 0x22222222u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX2, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(8, 2);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadDwordx3SnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMubuf0(0x0fu, 0, true, true));
|
|
code.push_back(EncodeMubuf1(21, 0, 21));
|
|
for (u32 i = 0; i < 3; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 3 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadDwordx3SnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0, 0, 0};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x11111111u, 0x22222222u, 0x33333333u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX3, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(12, 2);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 8);
|
|
AppendBufferLoadOpcode(&code, 0x0e, 0, 20);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x33333333u, 0x44444444u, 0u, 0u};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORDX4, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreDwordx4DropsOnlyOutOfBoundsTail() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 1, 0xbbbbbbbbu);
|
|
AppendVMovLiteral(&code, 2, 0xccccccccu);
|
|
AppendVMovLiteral(&code, 3, 0xddddddddu);
|
|
AppendVMovU32(&code, 20, 8);
|
|
code.push_back(EncodeMubuf0(0x1eu));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreDwordx4DropsOnlyOutOfBoundsTail";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x11111111u, 0x22222222u, 0xaaaaaaaau, 0xbbbbbbbbu};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORDX4, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwRejectsPartialRecord() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 8);
|
|
AppendBufferLoadOpcode(&code, 0x03, 0, 20);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwRejectsPartialRecord";
|
|
test.code = std::move(code);
|
|
test.initial = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.expected = {0u, 0u, 0u, 0u};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyzwDropsPartialRecord() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 1, 0xbbbbbbbbu);
|
|
AppendVMovLiteral(&code, 2, 0xccccccccu);
|
|
AppendVMovLiteral(&code, 3, 0xddddddddu);
|
|
AppendVMovU32(&code, 20, 8);
|
|
code.push_back(EncodeMubuf0(0x07u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyzwDropsPartialRecord";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = test.initial;
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXChecksOnlyTransferredComponent() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 12);
|
|
AppendBufferLoadOpcode(&code, 0x00, 0, 20);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXChecksOnlyTransferredComponent";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x44444444u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXChecksOnlyTransferredComponent() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xaaaaaaaau);
|
|
AppendVMovU32(&code, 20, 12);
|
|
code.push_back(EncodeMubuf0(0x04u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXChecksOnlyTransferredComponent";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x11111111u, 0x22222222u, 0x33333333u, 0xaaaaaaaau};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyChecksOnlyTransferredComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 8);
|
|
AppendBufferLoadOpcode(&code, 0x01, 0, 20);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyChecksOnlyTransferredComponents";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x33333333u, 0x44444444u, 0x33333333u, 0x44444444u};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyChecksOnlyTransferredComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 1, 0xbbbbbbbbu);
|
|
AppendVMovU32(&code, 20, 8);
|
|
code.push_back(EncodeMubuf0(0x05u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyChecksOnlyTransferredComponents";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0x11111111u, 0x22222222u, 0xaaaaaaaau, 0xbbbbbbbbu};
|
|
test.storage_buffer_range_dwords = 4;
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XY, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 0, 0xaa);
|
|
AppendBufferStoreOpcode(&code, 0x18, 0, 20);
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovLiteral(&code, 1, 0x0000bbccu);
|
|
AppendBufferStoreOpcode(&code, 0x1a, 1, 20);
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
AppendBufferStoreOpcode(&code, 0x1d, 2, 20);
|
|
AppendVMovU32(&code, 20, 12);
|
|
AppendVMovLiteral(&code, 4, 0x33333333u);
|
|
AppendVMovLiteral(&code, 5, 0x44444444u);
|
|
AppendVMovLiteral(&code, 6, 0x55555555u);
|
|
AppendBufferStoreOpcode(&code, 0x1f, 4, 20);
|
|
AppendVMovU32(&code, 20, 24);
|
|
AppendVMovLiteral(&code, 7, 0x66666666u);
|
|
AppendVMovLiteral(&code, 8, 0x77777777u);
|
|
AppendVMovLiteral(&code, 9, 0x88888888u);
|
|
AppendVMovLiteral(&code, 10, 0x99999999u);
|
|
AppendBufferStoreOpcode(&code, 0x1e, 7, 20);
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferStoreVariants",
|
|
code,
|
|
std::vector<u32>(10, 0),
|
|
{0xbbccaa00u, 0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
|
|
0x55555555u, 0x66666666u, 0x77777777u, 0x88888888u, 0x99999999u},
|
|
{O::V_MOV_B32, O::BUFFER_STORE_BYTE, O::BUFFER_STORE_SHORT,
|
|
O::BUFFER_STORE_DWORDX2, O::BUFFER_STORE_DWORDX3,
|
|
O::BUFFER_STORE_DWORDX4, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferFormatVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x00, 0, 20);
|
|
AppendBufferLoadOpcode(&code, 0x01, 1, 20);
|
|
AppendBufferLoadOpcode(&code, 0x02, 3, 20);
|
|
AppendBufferLoadOpcode(&code, 0x03, 6, 20);
|
|
for (u32 i = 0; i < 10u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase load;
|
|
load.name = "BufferFormatLoadVariants";
|
|
load.code = code;
|
|
load.initial = {0x01020304u, 0x11121314u, 0x21222324u, 0x31323334u};
|
|
load.expected = {0x01020304u, 0x01020304u, 0x11121314u, 0x01020304u,
|
|
0x11121314u, 0x21222324u, 0x01020304u, 0x11121314u,
|
|
0x21222324u, 0x31323334u};
|
|
load.opcodes = {O::BUFFER_LOAD_FORMAT_X,
|
|
O::BUFFER_LOAD_FORMAT_XY,
|
|
O::BUFFER_LOAD_FORMAT_XYZ,
|
|
O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::V_MOV_B32,
|
|
O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return load;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwSnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMubuf0(0x03u, 0, true, true));
|
|
code.push_back(EncodeMubuf1(21, 0, 21));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 4 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwSnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
|
|
0, 0, 0, 0};
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
|
|
0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
16, 2, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwResource32_32FloatAppliesXy01() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x03, 0, 20);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwResource32_32FloatAppliesXy01";
|
|
test.code = std::move(code);
|
|
test.initial = {0x3f800000u, 0x40000000u, 0, 0};
|
|
test.expected = {0x3f800000u, 0x40000000u, 0, 0x3f800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
16, 1, false, BufferFormat(Prospero::BufferFormat::k32_32Float));
|
|
test.user_data[3] = (test.user_data[3] & ~0xfffu) | DstSel(4, 5, 0, 1);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwResource323232FloatRepeatsSource() {
|
|
using O = ShaderOpcode;
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x03, 0, 20);
|
|
for (u32 i = 0; i < 4; ++i) AppendStoreVgpr(&code, i, i + 4);
|
|
AppendEnd(&code);
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwResource323232FloatRepeatsSource";
|
|
test.code = std::move(code);
|
|
test.initial = {0x3e800000u, 0x3f000000u, 0x3f400000u, 0xdeadbeefu, 0, 0, 0, 0};
|
|
test.expected = {0x3e800000u, 0x3f000000u, 0x3f400000u, 0xdeadbeefu,
|
|
0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3e800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
0, 32, false, BufferFormat(Prospero::BufferFormat::k32_32_32Float));
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferLoadOpcode(&code, 0x03, 0, 20);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1";
|
|
test.code = std::move(code);
|
|
test.initial = {0x11223344u, 0x55667788u, 0x99aabbccu, 0xddeeff00u};
|
|
test.expected = {0x11u, 0x22u, 0x33u, 1u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
16, 1, false, BufferFormat(Prospero::BufferFormat::k8_8_8_8UInt));
|
|
test.user_data[3] = (test.user_data[3] & ~0xfffu) | DstSel(7, 6, 5, 1);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 21, 0x11111111u);
|
|
AppendVMovLiteral(&code, 22, 0x22222222u);
|
|
AppendVMovLiteral(&code, 23, 0x33333333u);
|
|
AppendVMovLiteral(&code, 24, 0x44444444u);
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeMubuf0(0x03u, 0, true, true));
|
|
code.push_back(EncodeMubuf1(21, 0, 21));
|
|
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
|
code.push_back(EncodeSMovB32(127, InlineU32(0)));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 4 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd, 0, 0, 0, 0};
|
|
test.expected = {0xaaaaaaaau, 0xbbbbbbbbu, 0xccccccccu, 0xddddddddu,
|
|
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.opcodes = {
|
|
O::V_MOV_B32, O::S_MOV_B64, O::BUFFER_LOAD_FORMAT_XYZW,
|
|
O::S_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(
|
|
16, 2, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferFormatStoreVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x01020304u);
|
|
AppendBufferStoreOpcode(&code, 0x04, 0, 20);
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 1, 0x11121314u);
|
|
AppendVMovLiteral(&code, 2, 0x21222324u);
|
|
AppendBufferStoreOpcode(&code, 0x05, 1, 20);
|
|
AppendVMovU32(&code, 20, 12);
|
|
AppendVMovLiteral(&code, 3, 0x31323334u);
|
|
AppendVMovLiteral(&code, 4, 0x41424344u);
|
|
AppendVMovLiteral(&code, 5, 0x51525354u);
|
|
AppendBufferStoreOpcode(&code, 0x06, 3, 20);
|
|
AppendVMovU32(&code, 20, 24);
|
|
AppendVMovLiteral(&code, 6, 0x61626364u);
|
|
AppendVMovLiteral(&code, 7, 0x71727374u);
|
|
AppendVMovLiteral(&code, 8, 0x81828384u);
|
|
AppendVMovLiteral(&code, 9, 0x91929394u);
|
|
AppendBufferStoreOpcode(&code, 0x07, 6, 20);
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferFormatStoreVariants",
|
|
code,
|
|
std::vector<u32>(10, 0),
|
|
{0x01020304u, 0x11121314u, 0x21222324u, 0x31323334u, 0x41424344u,
|
|
0x51525354u, 0x61626364u, 0x71727374u, 0x81828384u, 0x91929394u},
|
|
{O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::BUFFER_STORE_FORMAT_XY,
|
|
O::BUFFER_STORE_FORMAT_XYZ, O::BUFFER_STORE_FORMAT_XYZW,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferLoadFormatXResource8UintZeroExtendsByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMubuf0(0x00u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXResource8UintZeroExtendsByte";
|
|
test.code = code;
|
|
test.initial = {0x11223344u, 0};
|
|
test.expected = {0x11223344u, 0x00000044u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 5);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXResource16UintWritesHalfword() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x0000aaaau);
|
|
code.push_back(EncodeMubuf0(0x04u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXResource16UintWritesHalfword";
|
|
test.code = code;
|
|
test.initial = {0x11223344u};
|
|
test.expected = {0x1122aaaau};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 4, false, 11);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXResource16UintPreservesAdjacentLanes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop2(0x25, 1, InlineU32(1), 0));
|
|
code.push_back(EncodeMubuf0(0x04u, 0, true, false));
|
|
code.push_back(EncodeMubuf1(1, 0, 0));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXResource16UintPreservesAdjacentLanes";
|
|
test.code = code;
|
|
test.initial = std::vector<u32>(32, 0);
|
|
for (u32 index = 0; index < 32; index++) {
|
|
test.expected.push_back(((index * 2 + 2) << 16) | (index * 2 + 1));
|
|
}
|
|
test.opcodes = {O::V_ADD_NC_U32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = 64;
|
|
test.has_compute_info = true;
|
|
test.user_data = MakeStructuredStorageBufferData(2, 64, false, 11);
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"OpAtomicLoad", "OpAtomicCompareExchange"};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyResource88UintExtractsBytes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMubuf0(0x01u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 2);
|
|
AppendStoreVgpr(&code, 1, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferLoadFormatXyResource8_8UintExtractsBytes";
|
|
test.code = code;
|
|
test.initial = {0x0000807fu, 0x55667788u, 0, 0};
|
|
test.expected = {0x0000807fu, 0x55667788u, 0x0000007fu, 0x00000080u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 18);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferLoadFormatXyResource8888UnormConvertsFirstTwoComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMubuf0(0x01u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name =
|
|
"BufferLoadFormatXyResource8_8_8_8UnormConvertsFirstTwoComponents";
|
|
test.code = code;
|
|
test.initial = {0x44332211u, 0xdeadbeefu};
|
|
test.expected = {0x3d888889u, 0x3e088889u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 56);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyResource88UintWritesBytes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x000000aau);
|
|
AppendVMovLiteral(&code, 1, 0x000000bbu);
|
|
code.push_back(EncodeMubuf0(0x05u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyResource8_8UintWritesBytes";
|
|
test.code = code;
|
|
test.initial = {0x11223344u, 0x55667788u};
|
|
test.expected = {0x1122bbaau, 0x55667788u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XY, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 18);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyResource32UintWritesOneDword() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
AppendVMovLiteral(&code, 1, 0x12345678u);
|
|
code.push_back(EncodeMubuf0(0x05u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyResource32UintWritesOneDword";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u};
|
|
test.expected = {0xabcdef01u, 0x22222222u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XY, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 20);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyzResource3232UintWritesTwoDwords() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
AppendVMovLiteral(&code, 1, 0x12345678u);
|
|
AppendVMovLiteral(&code, 2, 0x0badc0deu);
|
|
code.push_back(EncodeMubuf0(0x06u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyzResource32_32UintWritesTwoDwords";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u};
|
|
test.expected = {0xabcdef01u, 0x12345678u, 0x33333333u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZ, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 62);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyzwResource323232UintWritesThreeDwords() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
AppendVMovLiteral(&code, 1, 0x12345678u);
|
|
AppendVMovLiteral(&code, 2, 0x0badc0deu);
|
|
AppendVMovLiteral(&code, 3, 0xfeedfaceu);
|
|
code.push_back(EncodeMubuf0(0x07u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyzwResource32_32_32UintWritesThreeDwords";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
|
test.expected = {0xabcdef01u, 0x12345678u, 0x0badc0deu, 0x44444444u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 72);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXyzResource8UintWritesOneByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x000000aau);
|
|
AppendVMovLiteral(&code, 1, 0x000000bbu);
|
|
AppendVMovLiteral(&code, 2, 0x000000ccu);
|
|
code.push_back(EncodeMubuf0(0x06u));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXyzResource8UintWritesOneByte";
|
|
test.code = code;
|
|
test.initial = {0x11223344u, 0x55667788u, 0x99aabbccu};
|
|
test.expected = {0x112233aau, 0x55667788u, 0x99aabbccu};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZ, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(0, 8, false, 5);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXAddTidUsesLaneIndex() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeMubuf0(0x04u, 0, false, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXAddTidUsesLaneIndex";
|
|
test.code = code;
|
|
test.initial = std::vector<u32>(64, 0);
|
|
test.expected = std::vector<u32>(64, 0);
|
|
std::fill_n(test.expected.begin(), 32, 0x12345678u);
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 0;
|
|
test.compute_info.wave_size = 32;
|
|
test.has_compute_info = true;
|
|
test.user_data = MakeStructuredStorageBufferData(4, 64, true);
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"BuiltIn SubgroupLocalInvocationId"};
|
|
test.forbidden_spirv = {"BuiltIn LocalInvocationId",
|
|
"OpGroupNonUniformBallot"};
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferStoreFormatXDropsOutOfRangeRecord() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovLiteral(&code, 0, 0xabcdef01u);
|
|
code.push_back(EncodeMubuf0(0x04u, 0, true, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferStoreFormatXDropsOutOfRangeRecord";
|
|
test.code = code;
|
|
test.initial = {0x11111111u, 0x22222222u};
|
|
test.expected = {0x11111111u, 0x22222222u};
|
|
test.storage_buffer_range_dwords = 1;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(4, 1, false, 20);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase TBufferLoadVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadOpcode(&code, 0x00, 0, 20);
|
|
AppendTBufferLoadOpcode(&code, 0x01, 1, 20);
|
|
AppendTBufferLoadOpcode(&code, 0x02, 3, 20);
|
|
AppendTBufferLoadOpcode(&code, 0x03, 6, 20);
|
|
for (u32 i = 0; i < 10u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadVariants",
|
|
code,
|
|
{0x01020304u, 0x11121314u, 0x21222324u, 0x31323334u},
|
|
{0x01020304u, 0x01020304u, 0x11121314u, 0x01020304u, 0x11121314u,
|
|
0x21222324u, 0x01020304u, 0x11121314u, 0x21222324u, 0x31323334u},
|
|
{O::TBUFFER_LOAD_FORMAT_X, O::TBUFFER_LOAD_FORMAT_XY,
|
|
O::TBUFFER_LOAD_FORMAT_XYZ, O::TBUFFER_LOAD_FORMAT_XYZW,
|
|
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyzwSnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
constexpr auto format =
|
|
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float);
|
|
code.push_back(
|
|
EncodeMtbuf0(0x03u, format & 0xfu, (format >> 4u) & 0x7u, 0, true, true));
|
|
code.push_back(EncodeMtbuf1(0x03u, 21, 0, 21));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 4 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "TBufferLoadFormatXyzwSnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
|
|
0, 0, 0, 0};
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u,
|
|
0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(16, 2);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
constexpr auto format = BufferFormat(Prospero::BufferFormat::k8_8_8_8UInt);
|
|
code.push_back(
|
|
EncodeMtbuf0(0x03u, format & 0xfu, (format >> 4u) & 0x7u, 0, true, true));
|
|
code.push_back(EncodeMtbuf1(0x03u, 21, 0, 21));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 21 + i, 4 + i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress";
|
|
test.code = std::move(code);
|
|
test.initial = {0x44332211u, 0, 0, 0, 0, 0, 0, 0};
|
|
test.expected = {0x44332211u, 0, 0, 0, 0x11u, 0x22u, 0x33u, 0x44u};
|
|
test.opcodes = {O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZW,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(4, 8);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase TBufferStoreFormatX8UintWritesOneByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 0, 0xaa);
|
|
code.push_back(EncodeMtbuf0(0x04, 5, 0));
|
|
code.push_back(EncodeMtbuf1(0x04, 0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreFormatX8UintWritesOneByte",
|
|
code,
|
|
{0x11223344u},
|
|
{0x112233aau},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_X, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatX8UintZeroExtendsByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x00, 5, 0));
|
|
code.push_back(EncodeMtbuf1(0x00, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatX8UintZeroExtendsByte",
|
|
code,
|
|
{0x11223344u, 0},
|
|
{0x00000044u, 0},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatX8888UintExtractsFirstByte() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x00, 0, 20,
|
|
Prospero::BufferFormat::k8_8_8_8UInt);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatX8_8_8_8UintExtractsFirstByte",
|
|
code,
|
|
{0x44332211u, 0},
|
|
{0x00000011u, 0},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXIdxenUsesDescriptorStride() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
code.push_back(EncodeMtbuf0(0x00, 5, 0, 0, true, false));
|
|
code.push_back(EncodeMtbuf1(0x00, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "TBufferLoadFormatXIdxenUsesDescriptorStride";
|
|
test.code = code;
|
|
test.initial = {0x01020304u, 0, 0, 0, 0, 0, 0, 0, 0x0000007eu};
|
|
test.expected = {0x0000007eu, 0, 0, 0, 0, 0, 0, 0, 0x0000007eu};
|
|
test.opcodes = {O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.user_data = MakeStructuredStorageBufferData(16, 3);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase TBufferLoadFormatX16FloatConvertsToFloat() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x00, 0, 20,
|
|
Prospero::BufferFormat::k16Float);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatX16FloatConvertsToFloat",
|
|
code,
|
|
{0x00003c00u, 0},
|
|
{0x3f800000u, 0},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXSintSignExtendsSubDword() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x00, 6, 0));
|
|
code.push_back(EncodeMtbuf1(0x00, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
code.push_back(EncodeMtbuf0(0x00, 12, 0));
|
|
code.push_back(EncodeMtbuf1(0x00, 1, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXSintSignExtendsSubDword",
|
|
code,
|
|
{0x00000080u, 0x00008001u},
|
|
{0xffffff80u, 0xffff8001u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_X, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferStoreFormatXSintWritesSubDword() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0xffffff80u);
|
|
code.push_back(EncodeMtbuf0(0x04, 6, 0));
|
|
code.push_back(EncodeMtbuf1(0x04, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 1, 0xffff8001u);
|
|
code.push_back(EncodeMtbuf0(0x04, 12, 0));
|
|
code.push_back(EncodeMtbuf1(0x04, 1, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreFormatXSintWritesSubDword",
|
|
code,
|
|
{0x11223344u, 0x55667788u},
|
|
{0x11223380u, 0x55668001u},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_X, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy88IntegerComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x01, 2, 1));
|
|
code.push_back(EncodeMtbuf1(0x01, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
code.push_back(EncodeMtbuf0(0x01, 3, 1));
|
|
code.push_back(EncodeMtbuf1(0x01, 2, 0, 20));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy8_8IntegerComponents",
|
|
code,
|
|
{0x0000807fu, 0x00007f80u, 0, 0},
|
|
{0x0000007fu, 0x00000080u, 0xffffff80u, 0x0000007fu},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferStoreFormatXy88IntegerComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 0, 0xaa);
|
|
AppendVMovU32(&code, 1, 0xbb);
|
|
code.push_back(EncodeMtbuf0(0x05, 2, 1));
|
|
code.push_back(EncodeMtbuf1(0x05, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 2, 0xffffff80u);
|
|
AppendVMovU32(&code, 3, 0x7f);
|
|
code.push_back(EncodeMtbuf0(0x05, 3, 1));
|
|
code.push_back(EncodeMtbuf1(0x05, 2, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreFormatXy8_8IntegerComponents",
|
|
code,
|
|
{0x11223344u, 0x55667788u},
|
|
{0x1122bbaau, 0x55667f80u},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_XY, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy1616IntegerComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x01, 11, 1));
|
|
code.push_back(EncodeMtbuf1(0x01, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
code.push_back(EncodeMtbuf0(0x01, 12, 1));
|
|
code.push_back(EncodeMtbuf1(0x01, 2, 0, 20));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy16_16IntegerComponents",
|
|
code,
|
|
{0x80017fffu, 0x7fff8000u, 0, 0},
|
|
{0x00007fffu, 0x00008001u, 0xffff8000u, 0x00007fffu},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferStoreFormatXy1616IntegerComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 0, 0xaaaa);
|
|
AppendVMovU32(&code, 1, 0xbbbb);
|
|
code.push_back(EncodeMtbuf0(0x05, 11, 1));
|
|
code.push_back(EncodeMtbuf1(0x05, 0, 0, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 2, 0xffff8000u);
|
|
AppendVMovU32(&code, 3, 0x7fff);
|
|
code.push_back(EncodeMtbuf0(0x05, 12, 1));
|
|
code.push_back(EncodeMtbuf1(0x05, 2, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreFormatXy16_16IntegerComponents",
|
|
code,
|
|
{0x11223344u, 0x55667788u},
|
|
{0xbbbbaaaau, 0x7fff8000u},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_XY, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyz16161616UintLoadsHalfwords() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x02, 0, 20,
|
|
Prospero::BufferFormat::k16_16_16_16UInt);
|
|
for (u32 i = 0; i < 3; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXyz16_16_16_16UintLoadsHalfwords",
|
|
code,
|
|
{0x22221111u, 0x44443333u, 0},
|
|
{0x00001111u, 0x00002222u, 0x00003333u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZ, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy88UnormConvertsToFloat() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x01, 14, 0));
|
|
code.push_back(EncodeMtbuf1(0x01, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy8_8UnormConvertsToFloat",
|
|
code,
|
|
{0x0000ff80u, 0},
|
|
{0x3f008081u, 0x3f800000u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy88SnormConvertsToFloat() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x01, 0, 20,
|
|
Prospero::BufferFormat::k8_8SNorm);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy8_8SnormConvertsToFloat",
|
|
code,
|
|
{0x00007f80u, 0},
|
|
{0xbf800000u, 0x3f800000u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy1616UnormConvertsToFloat() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x01, 7, 1));
|
|
code.push_back(EncodeMtbuf1(0x01, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy16_16UnormConvertsToFloat",
|
|
code,
|
|
{0xffff8000u, 0},
|
|
{0x3f000080u, 0x3f800000u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXy8888UnormConvertsFirstTwoComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x01, 8, 3));
|
|
code.push_back(EncodeMtbuf1(0x01, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXy8_8_8_8UnormConvertsFirstTwoComponents",
|
|
code,
|
|
{0x44332211u, 0xdeadbeefu},
|
|
{0x3d888889u, 0x3e088889u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XY, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyzw8888UintExtractsBytes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x03, 0, 20,
|
|
Prospero::BufferFormat::k8_8_8_8UInt);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXyzw8_8_8_8UintExtractsBytes",
|
|
code,
|
|
{0x44332211u, 0xaaaaaaaau, 0xbbbbbbbbu, 0xccccccccu},
|
|
{0x00000011u, 0x00000022u, 0x00000033u, 0x00000044u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZW, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyzw1010102SnormConvertsToFloat() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x03, 0, 20,
|
|
Prospero::BufferFormat::k10_10_10_2SNorm);
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXyzw10_10_10_2SnormConvertsToFloat",
|
|
code,
|
|
{0x800801ffu, 0, 0, 0},
|
|
{0x3f800000u, 0xbf800000u, 0, 0xbf800000u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZW, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyz111110FloatUnpacks() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendTBufferLoadFormatOpcode(&code, 0x02, 0, 20,
|
|
Prospero::BufferFormat::k11_11_10Float);
|
|
for (u32 i = 0; i < 3; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXyz11_11_10FloatUnpacks",
|
|
code,
|
|
{0x781e03c0u, 0, 0},
|
|
{0x3f800000u, 0x3f800000u, 0x3f800000u},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZ, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferLoadFormatXyzw3232FloatZerosMissingComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeMtbuf0(0x03, 0, 4));
|
|
code.push_back(EncodeMtbuf1(0x03, 0, 0, 20));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferLoadFormatXyzw32_32FloatZerosMissingComponents",
|
|
code,
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u},
|
|
{0x11111111u, 0x22222222u, 0, 0},
|
|
{O::V_MOV_B32, O::TBUFFER_LOAD_FORMAT_XYZW, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferStoreFormatXyzw3232FloatWritesOnlyPresentComponents() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x40000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40400000u);
|
|
AppendVMovLiteral(&code, 3, 0x40800000u);
|
|
code.push_back(EncodeMtbuf0(0x07, 0, 4));
|
|
code.push_back(EncodeMtbuf1(0x07, 0, 0, 20));
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreFormatXyzw32_32FloatWritesOnlyPresentComponents",
|
|
code,
|
|
{0xaaaaaaaau, 0xbbbbbbbbu, 0xccccccccu, 0xddddddddu},
|
|
{0x3f800000u, 0x40000000u, 0xccccccccu, 0xddddddddu},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase TBufferStoreVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 0, 0x01020304u);
|
|
AppendTBufferStoreOpcode(&code, 0x04, 0, 20);
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 1, 0x11121314u);
|
|
AppendVMovLiteral(&code, 2, 0x21222324u);
|
|
AppendTBufferStoreOpcode(&code, 0x05, 1, 20);
|
|
AppendVMovU32(&code, 20, 12);
|
|
AppendVMovLiteral(&code, 3, 0x31323334u);
|
|
AppendVMovLiteral(&code, 4, 0x41424344u);
|
|
AppendVMovLiteral(&code, 5, 0x51525354u);
|
|
AppendTBufferStoreOpcode(&code, 0x06, 3, 20);
|
|
AppendVMovU32(&code, 20, 24);
|
|
AppendVMovLiteral(&code, 6, 0x61626364u);
|
|
AppendVMovLiteral(&code, 7, 0x71727374u);
|
|
AppendVMovLiteral(&code, 8, 0x81828384u);
|
|
AppendVMovLiteral(&code, 9, 0x91929394u);
|
|
AppendTBufferStoreOpcode(&code, 0x07, 6, 20);
|
|
AppendEnd(&code);
|
|
|
|
return {"TBufferStoreVariants",
|
|
code,
|
|
std::vector<u32>(10, 0),
|
|
{0x01020304u, 0x11121314u, 0x21222324u, 0x31323334u, 0x41424344u,
|
|
0x51525354u, 0x61626364u, 0x71727374u, 0x81828384u, 0x91929394u},
|
|
{O::V_MOV_B32, O::TBUFFER_STORE_FORMAT_X, O::TBUFFER_STORE_FORMAT_XY,
|
|
O::TBUFFER_STORE_FORMAT_XYZ, O::TBUFFER_STORE_FORMAT_XYZW,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase FlatLoadVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeFlat0(0x08, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x09, 0, 2));
|
|
code.push_back(EncodeFlat1(1, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0a, 0, 0));
|
|
code.push_back(EncodeFlat1(2, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0b, 0, 2));
|
|
code.push_back(EncodeFlat1(3, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0c, 0, 8));
|
|
code.push_back(EncodeFlat1(13, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0d, 0, 8));
|
|
code.push_back(EncodeFlat1(4, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0f, 0, 8));
|
|
code.push_back(EncodeFlat1(6, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0e, 0, 8));
|
|
code.push_back(EncodeFlat1(9, 0x7d, 0, 20));
|
|
|
|
for (u32 i = 0; i < 14u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
const std::vector<u32> guest_memory{0x80ff7f01u, 0x00008001u, 0x11223344u,
|
|
0x55667788u, 0x99aabbccu, 0xddeeff00u};
|
|
constexpr size_t byte_offset = 8;
|
|
std::vector<u32> host_memory(
|
|
(byte_offset + guest_memory.size() * sizeof(u32) + sizeof(u32) - 1u) /
|
|
sizeof(u32));
|
|
std::memcpy(reinterpret_cast<uint8_t *>(host_memory.data()) + byte_offset,
|
|
guest_memory.data(), guest_memory.size() * sizeof(u32));
|
|
|
|
TestCase test{"FlatLoadVariants",
|
|
code,
|
|
std::move(host_memory),
|
|
{0x01u, 0xffffffffu, 0x7f01u, 0xffff80ffu, 0x11223344u,
|
|
0x55667788u, 0x11223344u, 0x55667788u, 0x99aabbccu,
|
|
0x11223344u, 0x55667788u, 0x99aabbccu, 0xddeeff00u,
|
|
0x11223344u},
|
|
{O::V_MOV_B32, O::FLAT_LOAD_UBYTE, O::FLAT_LOAD_SBYTE,
|
|
O::FLAT_LOAD_USHORT, O::FLAT_LOAD_SSHORT, O::FLAT_LOAD_DWORD,
|
|
O::FLAT_LOAD_DWORDX2, O::FLAT_LOAD_DWORDX3,
|
|
O::FLAT_LOAD_DWORDX4, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.bda_mappings = {{0, static_cast<u32>(byte_offset)}};
|
|
return test;
|
|
}
|
|
|
|
TestCase FlatSubdwordLoadsApplyByteOffset() {
|
|
using O = ShaderOpcode;
|
|
|
|
constexpr size_t byte_offset = 3;
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, static_cast<u32>(byte_offset));
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeFlat0(0x08, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0a, 0, 1));
|
|
code.push_back(EncodeFlat1(1, 0x7d, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
constexpr u32 guest_word = 0x80ff7f01u;
|
|
std::vector<u32> host_memory(
|
|
(byte_offset + sizeof(guest_word) + sizeof(u32) - 1u) / sizeof(u32));
|
|
std::memcpy(reinterpret_cast<uint8_t *>(host_memory.data()) + byte_offset,
|
|
&guest_word, sizeof(guest_word));
|
|
|
|
TestCase test;
|
|
test.name = "FlatSubdwordLoadsApplyByteOffset";
|
|
test.code = std::move(code);
|
|
test.initial = std::move(host_memory);
|
|
test.expected = {0x01u, 0x0000ff7fu};
|
|
test.bda_mappings = {{0, 0}};
|
|
test.opcodes = {O::V_MOV_B32, O::FLAT_LOAD_UBYTE, O::FLAT_LOAD_USHORT,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase BranchVccnzUsesCarryProducedWaveMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0), EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1), EncodeVop2(0x1a, 3, InlineU32(2), 1),
|
|
EncodeVopc(0xc0, Vgpr(0), 0),
|
|
};
|
|
AppendVMovLiteral(&code, 2, 0xffffffffu);
|
|
code.push_back(EncodeVop2(0x28, 5, Vgpr(2), 1));
|
|
code.push_back(EncodeSopp(0x07, 2));
|
|
code.push_back(EncodeVop1(0x01, 2, InlineU32(11)));
|
|
code.push_back(EncodeSopp(0x02, 1));
|
|
AppendVMovU32(&code, 2, 42);
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BranchVccnzUsesCarryProducedWaveMask";
|
|
test.code = code;
|
|
test.expected = std::vector<u32>(8, 42);
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::V_CMP_F_U32, O::V_ADDC_U32, O::S_CBRANCH_VCCNZ,
|
|
O::S_BRANCH, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase ScalarLoadAlignsComponentsAndMasksAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, 1u);
|
|
AppendSMovLiteral(&code, 2, 3u);
|
|
AppendSMovLiteral(&code, 3, 0xffff0000u);
|
|
code.push_back(EncodeSmem0(0x00, 1, 1));
|
|
code.push_back(EncodeSmem1(3, 0));
|
|
AppendStoreSgpr(&code, 1, 0);
|
|
AppendEnd(&code);
|
|
|
|
return {"ScalarLoadAlignsComponentsAndMasksAddress",
|
|
code,
|
|
{0x11111111u, 0x22222222u},
|
|
{0x11111111u, 0x22222222u},
|
|
{O::S_MOV_B32, O::S_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase FlatVirtualAddressRebasesGuestAllocation() {
|
|
using O = ShaderOpcode;
|
|
|
|
constexpr uint64_t GuestBase = 0x0000000110000000ull;
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, static_cast<u32>(GuestBase + 4u));
|
|
AppendVMovLiteral(&code, 21, static_cast<u32>(GuestBase >> 32u));
|
|
code.push_back(EncodeFlat0(0x0c, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 0, 20));
|
|
code.push_back(EncodeFlat0(0x0c, 2, 0));
|
|
code.push_back(EncodeFlat1(1, 0x7d, 0, 20));
|
|
AppendVMovLiteral(&code, 21, static_cast<u32>((GuestBase >> 32u) + 1u));
|
|
code.push_back(EncodeFlat0(0x0c, 0, 0));
|
|
code.push_back(EncodeFlat1(2, 0x7d, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendStoreVgpr(&code, 2, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "FlatVirtualAddressRebasesGuestAllocation";
|
|
test.code = std::move(code);
|
|
test.initial = {0xfeedfaceu, 0xcafebabeu, 0, 0x12345678u};
|
|
test.expected = {0x12345678u, 0x12345678u, 0};
|
|
test.bda_mappings = {{GuestBase, 8}};
|
|
test.opcodes = {O::V_MOV_B32, O::FLAT_LOAD_DWORD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase GlobalSignedImmediateRebasesBeforeSaddr() {
|
|
using O = ShaderOpcode;
|
|
|
|
constexpr uint64_t GuestBase = 0x0000000110000000ull;
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 0, static_cast<u32>(GuestBase));
|
|
AppendSMovLiteral(&code, 1, static_cast<u32>(GuestBase >> 32u));
|
|
AppendVMovU32(&code, 20, 8);
|
|
code.push_back(EncodeFlat0(0x0c, 2, 0xffcu));
|
|
code.push_back(EncodeFlat1(0, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "GlobalSignedImmediateRebasesBeforeSaddr";
|
|
test.code = std::move(code);
|
|
test.initial = {0xfeedfaceu, 0xcafebabeu, 0x11111111u, 0x12345678u};
|
|
test.expected = {0x12345678u};
|
|
test.bda_mappings = {{GuestBase, 8}};
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::FLAT_LOAD_DWORD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
TestCase FlatSegmentIgnoresSaddrAndMasksOffsetMsb() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(4)));
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeFlat0(0x0c, 0, 4));
|
|
code.push_back(EncodeFlat1(0, 0, 0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeFlat0(0x0c, 0, 0x804));
|
|
code.push_back(EncodeFlat1(1, 0x7d, 0, 20));
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> initial(514, 0);
|
|
initial[1] = 0x11111111u;
|
|
initial[2] = 0x22222222u;
|
|
initial[513] = 0x33333333u;
|
|
|
|
TestCase test;
|
|
test.name = "FlatSegmentIgnoresSaddrAndMasksOffsetMsb";
|
|
test.code = code;
|
|
test.initial = initial;
|
|
test.expected = {0x11111111u, 0x11111111u};
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32, O::FLAT_LOAD_DWORD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.bda_mappings = {{0, 0}};
|
|
return test;
|
|
}
|
|
|
|
TestCase ScratchIsPrivatePerInvocation() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(20, InlineU32(0)));
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeFlat0(0x1c, 1, 4));
|
|
code.push_back(EncodeFlat1(0, 20, 0, 21));
|
|
code.push_back(EncodeFlat0(0x0c, 1, 4));
|
|
code.push_back(EncodeFlat1(5, 20, 0, 21));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 5, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ScratchIsPrivatePerInvocation";
|
|
test.code = std::move(code);
|
|
for (u32 lane = 0; lane < 64; lane++) {
|
|
test.expected.push_back(lane);
|
|
}
|
|
test.opcodes = {O::S_MOV_B32, O::V_MOV_B32,
|
|
O::FLAT_STORE_DWORD, O::FLAT_LOAD_DWORD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.compute_info.scratch_size_dwords = 2;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
TestCase FlatStoreVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 0, 0xaa);
|
|
code.push_back(EncodeFlat0(0x18, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 0, 20));
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovLiteral(&code, 1, 0x0000bbccu);
|
|
code.push_back(EncodeFlat0(0x1a, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 1, 20));
|
|
AppendVMovU32(&code, 20, 4);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
code.push_back(EncodeFlat0(0x1d, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 2, 20));
|
|
AppendVMovU32(&code, 20, 12);
|
|
AppendVMovLiteral(&code, 4, 0x33333333u);
|
|
AppendVMovLiteral(&code, 5, 0x44444444u);
|
|
AppendVMovLiteral(&code, 6, 0x55555555u);
|
|
code.push_back(EncodeFlat0(0x1f, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 4, 20));
|
|
AppendVMovU32(&code, 20, 24);
|
|
AppendVMovLiteral(&code, 7, 0x66666666u);
|
|
AppendVMovLiteral(&code, 8, 0x77777777u);
|
|
AppendVMovLiteral(&code, 9, 0x88888888u);
|
|
AppendVMovLiteral(&code, 10, 0x99999999u);
|
|
code.push_back(EncodeFlat0(0x1e, 0, 0));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 7, 20));
|
|
AppendVMovU32(&code, 20, 0);
|
|
code.push_back(EncodeFlat0(0x1c, 0, 40));
|
|
code.push_back(EncodeFlat1(0, 0x7d, 2, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"FlatStoreVariants",
|
|
code,
|
|
std::vector<u32>(12, 0),
|
|
{0xbbccaa00u, 0x11111111u, 0x22222222u, 0x33333333u,
|
|
0x44444444u, 0x55555555u, 0x66666666u, 0x77777777u,
|
|
0x88888888u, 0x99999999u, 0x11111111u, 0},
|
|
{O::V_MOV_B32, O::FLAT_STORE_BYTE, O::FLAT_STORE_SHORT,
|
|
O::FLAT_STORE_DWORD, O::FLAT_STORE_DWORDX2,
|
|
O::FLAT_STORE_DWORDX3, O::FLAT_STORE_DWORDX4, O::S_ENDPGM}};
|
|
return test;
|
|
}
|
|
#endif
|
|
|
|
TestCase DsReadWriteVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x11223344u);
|
|
code.push_back(EncodeDs0(0x0d, 0));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
code.push_back(EncodeDs0(0x36, 0));
|
|
code.push_back(EncodeDs1(3, 0, 1));
|
|
AppendVMovU32(&code, 4, 0xaa);
|
|
code.push_back(EncodeDs0(0x1e, 4));
|
|
code.push_back(EncodeDs1(0, 4, 1));
|
|
code.push_back(EncodeDs0(0x3a, 4));
|
|
code.push_back(EncodeDs1(5, 0, 1));
|
|
code.push_back(EncodeDs0(0x39, 4));
|
|
code.push_back(EncodeDs1(6, 0, 1));
|
|
AppendVMovLiteral(&code, 7, 0x000080ffu);
|
|
code.push_back(EncodeDs0(0x1f, 8));
|
|
code.push_back(EncodeDs1(0, 7, 1));
|
|
code.push_back(EncodeDs0(0x3c, 8));
|
|
code.push_back(EncodeDs1(8, 0, 1));
|
|
code.push_back(EncodeDs0(0x3b, 8));
|
|
code.push_back(EncodeDs1(9, 0, 1));
|
|
AppendVMovLiteral(&code, 10, 0x10101010u);
|
|
AppendVMovLiteral(&code, 11, 0x11111111u);
|
|
code.push_back(EncodeDs0(0x4d, 12));
|
|
code.push_back(EncodeDs1(0, 10, 1));
|
|
code.push_back(EncodeDs0(0x76, 12));
|
|
code.push_back(EncodeDs1(14, 0, 1));
|
|
AppendVMovLiteral(&code, 16, 0x20202020u);
|
|
AppendVMovLiteral(&code, 17, 0x21212121u);
|
|
AppendVMovLiteral(&code, 18, 0x22222222u);
|
|
code.push_back(EncodeDs0(0xde, 20));
|
|
code.push_back(EncodeDs1(0, 16, 1));
|
|
code.push_back(EncodeDs0(0xfe, 20));
|
|
code.push_back(EncodeDs1(19, 0, 1));
|
|
AppendVMovLiteral(&code, 22, 0x30303030u);
|
|
AppendVMovLiteral(&code, 23, 0x31313131u);
|
|
AppendVMovLiteral(&code, 24, 0x32323232u);
|
|
AppendVMovLiteral(&code, 25, 0x33333333u);
|
|
code.push_back(EncodeDs0(0xdf, 32));
|
|
code.push_back(EncodeDs1(0, 22, 1));
|
|
code.push_back(EncodeDs0(0xff, 32));
|
|
code.push_back(EncodeDs1(26, 0, 1));
|
|
|
|
const u32 results[] = {3, 5, 6, 8, 9, 14, 15, 19, 20, 21, 26, 27, 28, 29};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"DsReadWriteVariants",
|
|
code,
|
|
std::vector<u32>(14, 0),
|
|
{0x11223344u, 0xaau, 0xffffffaau, 0x80ffu, 0xffff80ffu, 0x10101010u,
|
|
0x11111111u, 0x20202020u, 0x21212121u, 0x22222222u, 0x30303030u,
|
|
0x31313131u, 0x32323232u, 0x33333333u},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_READ_B32, O::DS_WRITE_B8,
|
|
O::DS_READ_U8, O::DS_READ_I8, O::DS_WRITE_B16, O::DS_READ_U16,
|
|
O::DS_READ_I16, O::DS_WRITE_B64, O::DS_READ_B64, O::DS_WRITE_B96,
|
|
O::DS_READ_B96, O::DS_WRITE_B128, O::DS_READ_B128,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsReadU16D16CapturedPreservesHighHalf() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x89abcdefu);
|
|
code.push_back(EncodeDs0(0x0d));
|
|
code.push_back(EncodeDs1(0, 2, 1)); // ds_write_b32 v2, v1
|
|
|
|
AppendVMovLiteral(&code, 3, 0x12345678u);
|
|
code.push_back(EncodeDs0(0xa6));
|
|
code.push_back(EncodeDs1(3, 0, 1)); // ds_read_u16_d16 v3, v1
|
|
|
|
AppendVMovU32(&code, 7, 0);
|
|
code.push_back(0xda980000u);
|
|
code.push_back(0x07000007u); // captured ds_read_u16_d16 v7, v7
|
|
|
|
AppendStoreVgpr(&code, 3, 0);
|
|
AppendStoreVgpr(&code, 7, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsReadU16D16CapturedPreservesHighHalf",
|
|
code,
|
|
std::vector<u32>(2, 0),
|
|
{0x1234cdefu, 0x0000cdefu},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_READ_U16_D16,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"DS_READ_U16_D16", 2}};
|
|
return test;
|
|
}
|
|
|
|
TestCase DsWriteB16D16HiCapturedUsesHighHalf() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 3, 0xdeadbeefu);
|
|
code.push_back(EncodeDs0(0x0d));
|
|
code.push_back(EncodeDs1(0, 3, 1)); // ds_write_b32 v1, v3
|
|
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovLiteral(&code, 2, 0x1234abcdu);
|
|
AppendVMovLiteral(&code, 0, 0x87654321u);
|
|
code.push_back(0xda840000u);
|
|
code.push_back(0x00000214u); // captured ds_write_b16_d16_hi v20, v2
|
|
|
|
code.push_back(EncodeDs0(0x36));
|
|
code.push_back(EncodeDs1(4, 0, 1)); // ds_read_b32 v4, v1
|
|
AppendStoreVgpr(&code, 4, 0);
|
|
AppendStoreVgpr(&code, 0, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsWriteB16D16HiCapturedUsesHighHalf",
|
|
code,
|
|
std::vector<u32>(2, 0),
|
|
{0x1234beefu, 0x87654321u},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_WRITE_B16_D16_HI,
|
|
O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"DS_WRITE_B16_D16_HI", 1}};
|
|
return test;
|
|
}
|
|
|
|
TestCase DsReadU16D16HiCapturedPreservesLowHalf() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x89abcdefu);
|
|
code.push_back(EncodeDs0(0x0d));
|
|
code.push_back(EncodeDs1(0, 2, 1)); // ds_write_b32 v2, v1
|
|
|
|
AppendVMovLiteral(&code, 5, 0x12345678u);
|
|
AppendVMovU32(&code, 6, 0);
|
|
code.push_back(0xda9c0000u);
|
|
code.push_back(0x05000006u); // captured ds_read_u16_d16_hi v5, v6
|
|
|
|
AppendStoreVgpr(&code, 5, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsReadU16D16HiCapturedPreservesLowHalf",
|
|
code,
|
|
std::vector<u32>(1, 0),
|
|
{0xcdef5678u},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_READ_U16_D16_HI,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.decoded_counts = {{"DS_READ_U16_D16_HI", 1}};
|
|
return test;
|
|
}
|
|
|
|
TestCase DsReadWrite2Variants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
code.push_back(EncodeDs0(0x0e, (3u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(0, 3, 2, 1));
|
|
code.push_back(EncodeDs0(0x37, (3u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(4, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 6, 0x33333333u);
|
|
AppendVMovLiteral(&code, 7, 0x44444444u);
|
|
AppendVMovLiteral(&code, 8, 0x55555555u);
|
|
AppendVMovLiteral(&code, 9, 0x66666666u);
|
|
code.push_back(EncodeDs0(0x4d, 32));
|
|
code.push_back(EncodeDs1(0, 6, 1));
|
|
code.push_back(EncodeDs0(0x4d, 48));
|
|
code.push_back(EncodeDs1(0, 8, 1));
|
|
code.push_back(EncodeDs0(0x77, (6u << 8u) | 4u));
|
|
code.push_back(EncodeDs1Ex(10, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 14, 0x77777777u);
|
|
AppendVMovLiteral(&code, 15, 0x88888888u);
|
|
code.push_back(EncodeDs0(0x0f, (2u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(0, 15, 14, 1));
|
|
code.push_back(EncodeDs0(0x38, (2u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(16, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 18, 0x99999999u);
|
|
AppendVMovLiteral(&code, 19, 0xaaaaaaaau);
|
|
AppendVMovLiteral(&code, 20, 0xbbbbbbbbu);
|
|
AppendVMovLiteral(&code, 21, 0xccccccccu);
|
|
code.push_back(EncodeDs0(0x4e, (10u << 8u) | 8u));
|
|
code.push_back(EncodeDs1Ex(0, 20, 18, 1));
|
|
code.push_back(EncodeDs0(0x77, (10u << 8u) | 8u));
|
|
code.push_back(EncodeDs1Ex(22, 0, 0, 1));
|
|
AppendVMovLiteral(&code, 26, 0xddddddddu);
|
|
AppendVMovLiteral(&code, 27, 0xeeeeeeeeu);
|
|
AppendVMovLiteral(&code, 28, 0xf0f0f0f0u);
|
|
AppendVMovLiteral(&code, 29, 0x12345678u);
|
|
code.push_back(EncodeDs0(0x4f, (2u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(0, 28, 26, 1));
|
|
code.push_back(EncodeDs0(0x78, (2u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(34, 0, 0, 1));
|
|
|
|
const u32 results[] = {4, 5, 10, 11, 12, 13, 22, 23,
|
|
24, 25, 16, 17, 34, 35, 36, 37};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"DsReadWrite2Variants",
|
|
code,
|
|
std::vector<u32>(16, 0),
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u, 0x55555555u,
|
|
0x66666666u, 0x99999999u, 0xaaaaaaaau, 0xbbbbbbbbu, 0xccccccccu,
|
|
0x77777777u, 0x88888888u, 0xddddddddu, 0xeeeeeeeeu, 0xf0f0f0f0u,
|
|
0x12345678u},
|
|
{O::V_MOV_B32, O::DS_WRITE2_B32, O::DS_READ2_B32, O::DS_WRITE_B64,
|
|
O::DS_READ2_B64, O::DS_WRITE2_B64, O::DS_WRITE2ST64_B32,
|
|
O::DS_READ2ST64_B32, O::DS_WRITE2ST64_B64, O::DS_READ2ST64_B64,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsWideReadSnapshotsOverlappingAddress() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 6, 0x11111111u);
|
|
AppendVMovLiteral(&code, 7, 0x22222222u);
|
|
code.push_back(EncodeDs0(0x4d, 0));
|
|
code.push_back(EncodeDs1(0, 6, 1));
|
|
AppendVMovLiteral(&code, 8, 0x33333333u);
|
|
AppendVMovLiteral(&code, 9, 0x44444444u);
|
|
code.push_back(EncodeDs0(0x4d, 16));
|
|
code.push_back(EncodeDs1(0, 8, 1));
|
|
code.push_back(EncodeDs0(0x77, 2u << 8u));
|
|
code.push_back(EncodeDs1Ex(1, 0, 0, 1));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 1u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"DsWideReadSnapshotsOverlappingAddress",
|
|
code,
|
|
std::vector<u32>(4, 0),
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u},
|
|
{O::V_MOV_B32, O::DS_WRITE_B64, O::DS_READ2_B64,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsReadWrite2EqualOffsetsUseData0() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
code.push_back(EncodeDs0(0x0e, (1u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(0, 3, 2, 1));
|
|
AppendVMovLiteral(&code, 4, 0x33333333u);
|
|
AppendVMovLiteral(&code, 5, 0x44444444u);
|
|
AppendVMovLiteral(&code, 6, 0x55555555u);
|
|
AppendVMovLiteral(&code, 7, 0x66666666u);
|
|
code.push_back(EncodeDs0(0x4e, (2u << 8u) | 2u));
|
|
code.push_back(EncodeDs1Ex(0, 6, 4, 1));
|
|
code.push_back(EncodeDs0(0x37, (1u << 8u) | 1u));
|
|
code.push_back(EncodeDs1Ex(8, 0, 0, 1));
|
|
code.push_back(EncodeDs0(0x77, (2u << 8u) | 2u));
|
|
code.push_back(EncodeDs1Ex(10, 0, 0, 1));
|
|
for (u32 i = 0; i < 6; i++) {
|
|
AppendStoreVgpr(&code, 8u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"DsReadWrite2EqualOffsetsUseData0",
|
|
code,
|
|
std::vector<u32>(6, 0),
|
|
{0x11111111u, 0x11111111u, 0x33333333u, 0x44444444u, 0x33333333u,
|
|
0x44444444u},
|
|
{O::V_MOV_B32, O::DS_WRITE2_B32, O::DS_WRITE2_B64, O::DS_READ2_B32,
|
|
O::DS_READ2_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsWideLdsPartialBounds() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
AppendVMovLiteral(&code, 4, 0x33333333u);
|
|
AppendVMovLiteral(&code, 5, 0x44444444u);
|
|
code.push_back(EncodeDs0(0xdf));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
code.push_back(EncodeDs0(0xff));
|
|
code.push_back(EncodeDs1(6, 0, 1));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 6u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsWideLdsPartialBounds",
|
|
code,
|
|
std::vector<u32>(4, 0),
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0},
|
|
{O::V_MOV_B32, O::DS_WRITE_B128, O::DS_READ_B128,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.compute_info.lds_size_dwords = 3;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsWideGdsPartialBounds() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
AppendVMovLiteral(&code, 3, 0x22222222u);
|
|
AppendVMovLiteral(&code, 4, 0x33333333u);
|
|
AppendVMovLiteral(&code, 5, 0x44444444u);
|
|
code.push_back(EncodeDs0(0xdf, 0, true));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
code.push_back(EncodeDs0(0xff, 0, true));
|
|
code.push_back(EncodeDs1(6, 0, 1));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
AppendStoreVgpr(&code, 6u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsWideGdsPartialBounds",
|
|
code,
|
|
std::vector<u32>(4, 0),
|
|
{0x11111111u, 0x22222222u, 0x33333333u, 0},
|
|
{O::V_MOV_B32, O::DS_WRITE_B128, O::DS_READ_B128,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.gds_initial = {0, 0, 0};
|
|
test.expected_gds = {0x11111111u, 0x22222222u, 0x33333333u};
|
|
return test;
|
|
}
|
|
|
|
TestCase DsAtomicNoReturnVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
const u32 initial[] = {10, 10, 0xfffffff0u, 0xfffffff0u, 10,
|
|
10, 0xf0f0u, 0xf000u, 0xf00fu};
|
|
const u32 values[] = {5, 3, 5, 5, 5, 20, 0x0ff0u, 0x0f00u, 0x00ffu};
|
|
const u32 ops[] = {0x00, 0x01, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovLiteral(&code, 2, initial[i]);
|
|
code.push_back(EncodeDs0(0x0d, i * 4u));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
AppendVMovLiteral(&code, 3, values[i]);
|
|
code.push_back(EncodeDs0(ops[i], i * 4u));
|
|
code.push_back(EncodeDs1(0, 3, 1));
|
|
code.push_back(EncodeDs0(0x36, i * 4u));
|
|
code.push_back(EncodeDs1(10u + i, 0, 1));
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, 10u + i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"DsAtomicNoReturnVariants",
|
|
code,
|
|
std::vector<u32>(9, 0),
|
|
{15, 7, 0xfffffff0u, 5, 5, 20, 0x00f0u, 0xff00u, 0xf0f0u},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_ADD_U32, O::DS_SUB_U32,
|
|
O::DS_MIN_I32, O::DS_MAX_I32, O::DS_MIN_U32, O::DS_MAX_U32,
|
|
O::DS_AND_B32, O::DS_OR_B32, O::DS_XOR_B32, O::DS_READ_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsAtomicReturnVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
const u32 initial[] = {10, 10, 0xfffffff0u, 0xfffffff0u, 10,
|
|
10, 0xf0f0u, 0xf000u, 0xf00fu, 10};
|
|
const u32 values[] = {5, 3, 5, 5, 5, 20, 0x0ff0u, 0x0f00u, 0x00ffu, 99};
|
|
const u32 ops[] = {0x20, 0x21, 0x25, 0x26, 0x27,
|
|
0x28, 0x29, 0x2a, 0x2b, 0x2d};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovLiteral(&code, 2, initial[i]);
|
|
code.push_back(EncodeDs0(0x0d, i * 4u));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
AppendVMovLiteral(&code, 3, values[i]);
|
|
code.push_back(EncodeDs0(ops[i], i * 4u));
|
|
code.push_back(EncodeDs1(10u + i, 3, 1));
|
|
code.push_back(EncodeDs0(0x36, i * 4u));
|
|
code.push_back(EncodeDs1(20u + i, 0, 1));
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, 10u + i, i);
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, 20u + i, i + 10u);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"DsAtomicReturnVariants",
|
|
code,
|
|
std::vector<u32>(20, 0),
|
|
{10, 10, 0xfffffff0u, 0xfffffff0u, 10, 10, 0xf0f0u, 0xf000u, 0xf00fu, 10,
|
|
15, 7, 0xfffffff0u, 5, 5, 20, 0x00f0u, 0xff00u, 0xf0f0u, 99},
|
|
{O::V_MOV_B32, O::DS_WRITE_B32, O::DS_ADD_RTN_U32, O::DS_SUB_RTN_U32,
|
|
O::DS_MIN_RTN_I32, O::DS_MAX_RTN_I32, O::DS_MIN_RTN_U32,
|
|
O::DS_MAX_RTN_U32, O::DS_AND_RTN_B32, O::DS_OR_RTN_B32,
|
|
O::DS_XOR_RTN_B32, O::DS_WRXCHG_RTN_B32, O::DS_READ_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsBoundedAtomicBoundaries(bool decrement, bool gds) {
|
|
using O = ShaderOpcode;
|
|
constexpr u32 guard = 0x13579bdfu;
|
|
struct Boundary {
|
|
u32 before, limit, incremented, decremented;
|
|
};
|
|
constexpr Boundary boundaries[] = {
|
|
{4, 5, 5, 3},
|
|
{5, 5, 0, 4},
|
|
{6, 5, 0, 5},
|
|
{0, 5, 1, 5},
|
|
{0, 0, 0, 0},
|
|
{1, 0, 0, 0},
|
|
{0xffffffffu, 0xffffffffu, 0, 0xfffffffeu},
|
|
{0xfffffffeu, 0xffffffffu, 0xffffffffu, 0xfffffffdu},
|
|
{0, 0xffffffffu, 1, 0xffffffffu},
|
|
{0x80000000u, 0x7fffffffu, 0, 0x7fffffffu},
|
|
{0x7fffffffu, 0x80000000u, 0x80000000u, 0x7ffffffeu},
|
|
{0xffffffffu, 0x80000000u, 0, 0x80000000u}};
|
|
constexpr u32 count = static_cast<u32>(std::size(boundaries));
|
|
const u32 opcode = decrement ? 0x24u : 0x23u;
|
|
TestCase test;
|
|
test.name =
|
|
decrement
|
|
? (gds ? "DsDecReturnGdsBoundaries" : "DsDecReturnLdsBoundaries")
|
|
: (gds ? "DsIncReturnGdsBoundaries" : "DsIncReturnLdsBoundaries");
|
|
auto &code = test.code;
|
|
AppendVMovU32(&code, 2, 0);
|
|
std::vector<u32> memory(68, guard);
|
|
for (u32 i = 0; i < count; ++i)
|
|
memory[i + 1] = boundaries[i].before;
|
|
if (!gds) {
|
|
for (u32 i = 0; i < memory.size(); ++i) {
|
|
AppendVMovLiteral(&code, 3, memory[i]);
|
|
code.push_back(EncodeDs0(0x0d, i * 4));
|
|
code.push_back(EncodeDs1(0, 3, 2));
|
|
}
|
|
}
|
|
auto StoreResult = [&](u32 vgpr, u32 expected) {
|
|
AppendStoreVgpr(&code, vgpr, static_cast<u32>(test.expected.size()));
|
|
test.expected.push_back(expected);
|
|
};
|
|
for (u32 i = 0; i < count; ++i) {
|
|
AppendVMovU32(&code, 2, i * 4);
|
|
AppendVMovLiteral(&code, 3, boundaries[i].limit);
|
|
// Preserve the captured DATA0/VDST alias and exact increment words in GDS.
|
|
code.push_back(decrement ? EncodeDs0(opcode, 4, gds)
|
|
: (gds ? 0xd88e0004u : 0xd88c0004u));
|
|
code.push_back(0x03000302u);
|
|
StoreResult(3, boundaries[i].before);
|
|
code.push_back(EncodeDs0(0x36, 4, gds));
|
|
code.push_back(EncodeDs1(4, 0, 2));
|
|
StoreResult(4, decrement ? boundaries[i].decremented
|
|
: boundaries[i].incremented);
|
|
}
|
|
|
|
// EXEC=0 preserves memory and the aliased destination register.
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovLiteral(&code, 3, 0x12345678u);
|
|
code.push_back(EncodeSop1(0x04, 10, 126));
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeDs0(opcode, 4, gds));
|
|
code.push_back(0x03000302u);
|
|
code.push_back(EncodeSop1(0x04, 126, 10));
|
|
StoreResult(3, 0x12345678u);
|
|
|
|
// Use both offset bytes and alias VDST with ADDR instead of DATA0.
|
|
AppendVMovU32(&code, 2, 4);
|
|
AppendVMovLiteral(&code, 3, 0xffffffffu);
|
|
code.push_back(EncodeDs0(opcode, 0x104, gds));
|
|
code.push_back(EncodeDs1(2, 3, 2));
|
|
StoreResult(2, guard);
|
|
|
|
// Overwriting an unused return must not let DCE discard the atomic update.
|
|
AppendVMovU32(&code, 2, 0);
|
|
code.push_back(EncodeDs0(opcode, 0x108, gds));
|
|
code.push_back(EncodeDs1(4, 3, 2));
|
|
AppendVMovU32(&code, 4, 0);
|
|
|
|
// An active access just outside the resource returns zero.
|
|
AppendVMovU32(&code, 2, static_cast<u32>(memory.size() * 4 - 4));
|
|
code.push_back(EncodeDs0(opcode, 4, gds));
|
|
code.push_back(0x03000302u);
|
|
StoreResult(3, 0);
|
|
|
|
// Read back all words, including guards around both address ranges.
|
|
AppendVMovU32(&code, 2, 0);
|
|
for (u32 i = 0; i < memory.size(); ++i) {
|
|
u32 expected = memory[i];
|
|
if (i >= 1 && i <= count) {
|
|
expected = decrement ? boundaries[i - 1].decremented
|
|
: boundaries[i - 1].incremented;
|
|
} else if (i == 66) {
|
|
expected = decrement ? guard - 2 : guard + 2;
|
|
}
|
|
code.push_back(EncodeDs0(0x36, i * 4, gds));
|
|
code.push_back(EncodeDs1(4, 0, 2));
|
|
StoreResult(4, expected);
|
|
if (gds)
|
|
test.expected_gds.push_back(expected);
|
|
}
|
|
AppendEnd(&code);
|
|
test.initial.resize(test.expected.size());
|
|
test.compute_info.lds_size_dwords = static_cast<u32>(memory.size());
|
|
test.opcodes = {
|
|
O::S_MOV_B64, O::V_MOV_B32,
|
|
O::DS_READ_B32, decrement ? O::DS_DEC_RTN_U32 : O::DS_INC_RTN_U32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpAtomicLoad", "OpAtomicCompareExchange",
|
|
"OpLoopMerge"};
|
|
if (gds)
|
|
test.gds_initial = memory;
|
|
else
|
|
test.opcodes.push_back(O::DS_WRITE_B32);
|
|
return test;
|
|
}
|
|
|
|
TestCase DsBoundedAtomicContention(bool decrement, bool gds, u32 wave_size) {
|
|
using O = ShaderOpcode;
|
|
constexpr u32 guard = 0x13579bdfu;
|
|
constexpr u32 limit = 15;
|
|
TestCase test;
|
|
if (wave_size == 32) {
|
|
test.name = decrement ? (gds ? "DsDecReturnGdsContentionWave32"
|
|
: "DsDecReturnLdsContentionWave32")
|
|
: (gds ? "DsIncReturnGdsContentionWave32"
|
|
: "DsIncReturnLdsContentionWave32");
|
|
} else {
|
|
test.name = decrement ? (gds ? "DsDecReturnGdsContentionWave64"
|
|
: "DsDecReturnLdsContentionWave64")
|
|
: (gds ? "DsIncReturnGdsContentionWave64"
|
|
: "DsIncReturnLdsContentionWave64");
|
|
}
|
|
auto &code = test.code;
|
|
AppendVMovU32(&code, 2, 0);
|
|
AppendVMovU32(&code, 3, limit);
|
|
AppendVMovU32(&code, 5, 1);
|
|
AppendVMovLiteral(&code, 6, guard);
|
|
if (!gds) {
|
|
// One lane initializes LDS before every wave in the workgroup uses it.
|
|
code.push_back(EncodeSop1(0x04, 10, 126));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 126, 106));
|
|
code.push_back(EncodeDs0(0x0d, 4));
|
|
code.push_back(EncodeDs1(0, 2, 2));
|
|
code.push_back(EncodeSop1(0x04, 126, 10));
|
|
code.push_back(EncodeSopp(0x0a, 0));
|
|
}
|
|
// Distinct masks in the wave64 halves also check inactive return
|
|
// preservation.
|
|
code.push_back(EncodeSop1(0x04, 10, 126));
|
|
AppendSMovLiteral(&code, 126, 0x55555555u);
|
|
AppendSMovLiteral(&code, 127, 0xaaaaaaaau);
|
|
code.push_back(EncodeDs0(decrement ? 0x24 : 0x23, 4, gds));
|
|
code.push_back(EncodeDs1(6, 3, 2));
|
|
// Returned old values form a histogram; no lane ordering is assumed.
|
|
code.push_back(EncodeVop2(0x1a, 7, InlineU32(2), 6));
|
|
AppendBufferStoreOpcode(&code, 0x32, 5, 7);
|
|
code.push_back(EncodeSop1(0x04, 126, 10));
|
|
AppendVMovLiteral(&code, 7, guard);
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(7), 6));
|
|
code.push_back(EncodeVop2(0x01, 8, InlineU32(0), 5));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 8, 0, 16);
|
|
code.push_back(EncodeSopp(0x0a, 0));
|
|
// S_BARRIER orders LDS; use atomic add-zero to read GDS consistently.
|
|
code.push_back(EncodeDs0(gds ? 0x20 : 0x36, 4, gds));
|
|
code.push_back(EncodeDs1(9, 2, 2));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 9, 0, 144);
|
|
AppendEnd(&code);
|
|
test.expected.assign(16, 4); // 64 active lanes, four complete wraps.
|
|
for (u32 lane = 0; lane < 128; ++lane) {
|
|
const uint64_t mask = 0xaaaaaaaa55555555ull;
|
|
test.expected.push_back(
|
|
(mask & (uint64_t{1} << (lane % wave_size))) == 0 ? 1 : 0);
|
|
}
|
|
test.expected.resize(272, 0);
|
|
test.initial.resize(test.expected.size());
|
|
test.compute_info.threads_num[0] = 128;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.wave_size = wave_size;
|
|
test.compute_info.lds_size_dwords = 3;
|
|
test.has_compute_info = true;
|
|
test.opcodes = {O::S_MOV_B32,
|
|
O::S_MOV_B64,
|
|
O::S_BARRIER,
|
|
O::V_MOV_B32,
|
|
O::V_LSHLREV_B32,
|
|
O::V_ADD_NC_U32,
|
|
O::V_CMP_EQ_U32,
|
|
O::V_CNDMASK_B32,
|
|
decrement ? O::DS_DEC_RTN_U32 : O::DS_INC_RTN_U32,
|
|
gds ? O::DS_ADD_RTN_U32 : O::DS_READ_B32,
|
|
O::BUFFER_ATOMIC_ADD,
|
|
O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpAtomicLoad", "OpAtomicCompareExchange",
|
|
"OpLoopMerge"};
|
|
if (gds) {
|
|
test.gds_initial = {guard, 0, guard};
|
|
test.expected_gds = test.gds_initial;
|
|
} else
|
|
test.opcodes.push_back(O::DS_WRITE_B32);
|
|
return test;
|
|
}
|
|
|
|
TestCase DsMiscVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 0, 0);
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x40800000u);
|
|
code.push_back(EncodeDs0(0x0d, 0));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
AppendVMovLiteral(&code, 3, 0x3f800000u);
|
|
code.push_back(EncodeDs0(0x0d, 4));
|
|
code.push_back(EncodeDs1(0, 3, 1));
|
|
AppendVMovLiteral(&code, 4, 0x40000000u);
|
|
code.push_back(EncodeDs0(0x12, 0));
|
|
code.push_back(EncodeDs1(0, 4, 1));
|
|
AppendVMovLiteral(&code, 5, 0x40400000u);
|
|
code.push_back(EncodeDs0(0x13, 4));
|
|
code.push_back(EncodeDs1(0, 5, 1));
|
|
code.push_back(EncodeDs0(0x36, 0));
|
|
code.push_back(EncodeDs1(6, 0, 1));
|
|
code.push_back(EncodeDs0(0x36, 4));
|
|
code.push_back(EncodeDs1(7, 0, 1));
|
|
AppendVMovLiteral(&code, 8, 0x12345678u);
|
|
code.push_back(EncodeDs0(0x35, 0x001f));
|
|
code.push_back(EncodeDs1(9, 0, 8));
|
|
code.push_back(EncodeSMovB32(124, InlineU32(0)));
|
|
AppendVMovLiteral(&code, 10, 0xabcdef01u);
|
|
code.push_back(EncodeDs0(0xb0, 8));
|
|
code.push_back(EncodeDs1(0, 10, 0));
|
|
code.push_back(EncodeDs0(0xb1, 8));
|
|
code.push_back(EncodeDs1(11, 0, 0));
|
|
|
|
const u32 results[] = {6, 7, 9, 11};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(results)); i++) {
|
|
AppendStoreVgpr(&code, results[i], i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsMiscVariants";
|
|
test.code = code;
|
|
test.initial = std::vector<u32>(4, 0);
|
|
test.expected = {0x40000000u, 0x40400000u, 0x12345678u, 0xabcdef01u};
|
|
test.opcodes = {O::V_MOV_B32,
|
|
O::S_MOV_B32,
|
|
O::DS_WRITE_B32,
|
|
O::DS_MIN_F32,
|
|
O::DS_MAX_F32,
|
|
O::DS_READ_B32,
|
|
O::DS_SWIZZLE_B32,
|
|
O::DS_WRITE_ADDTID_B32,
|
|
O::DS_READ_ADDTID_B32,
|
|
O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 1;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsFloatMinMaxUsesSeparateCompareOperand() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovLiteral(&code, 2, 0x40800000u);
|
|
code.push_back(EncodeDs0(0x0d, 0));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
AppendVMovLiteral(&code, 3, 0x40800000u);
|
|
code.push_back(EncodeDs0(0x0d, 4));
|
|
code.push_back(EncodeDs1(0, 3, 1));
|
|
AppendVMovLiteral(&code, 4, 0x41100000u);
|
|
AppendVMovLiteral(&code, 5, 0x40000000u);
|
|
code.push_back(EncodeDs0(0x12, 0));
|
|
code.push_back(EncodeDs1Ex(0, 5, 4, 1));
|
|
AppendVMovLiteral(&code, 6, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 7, 0x40400000u);
|
|
code.push_back(EncodeDs0(0x13, 4));
|
|
code.push_back(EncodeDs1Ex(0, 7, 6, 1));
|
|
code.push_back(EncodeDs0(0x36, 0));
|
|
code.push_back(EncodeDs1(8, 0, 1));
|
|
code.push_back(EncodeDs0(0x36, 4));
|
|
code.push_back(EncodeDs1(9, 0, 1));
|
|
AppendStoreVgpr(&code, 8, 0);
|
|
AppendStoreVgpr(&code, 9, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsFloatMinMaxUsesSeparateCompareOperand";
|
|
test.code = code;
|
|
test.initial = std::vector<u32>(2, 0);
|
|
test.expected = {0x41100000u, 0x3f800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::DS_WRITE_B32, O::DS_MIN_F32,
|
|
O::DS_MAX_F32, O::DS_READ_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 1;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsSwizzleInvalidSourceLaneZero() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeDs0(0x35, 0x00e0));
|
|
code.push_back(EncodeDs1(2, 0, 1));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsSwizzleInvalidSourceLaneZero";
|
|
test.code = code;
|
|
test.expected = {0, 0, 0, 0};
|
|
test.opcodes = {O::V_MOV_B32, O::DS_SWIZZLE_B32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsBpermuteCapturedExecOffsetAndWrap() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0));
|
|
AppendBufferLoadDword(&code, 17, 30);
|
|
AppendVMovLiteral(&code, 1, 0xdeadbeefu);
|
|
AppendVMovU32(&code, 3, 100);
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(0), 3));
|
|
code.push_back(EncodeSop1(0x04, 4, 126));
|
|
code.push_back(EncodeVop2(0x1b, 18, InlineU32(1), 0));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(1), 18));
|
|
code.push_back(EncodeSop1(0x04, 126, 106));
|
|
code.push_back(0xdacc0000u);
|
|
code.push_back(0x01000311u); // ds_bpermute_b32 v1, v17, v3
|
|
code.push_back(EncodeSop1(0x04, 126, 4));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 4);
|
|
|
|
AppendVMovU32(&code, 17, 124);
|
|
code.push_back(EncodeDs0(0xb3, 4));
|
|
code.push_back(EncodeDs1(2, 3, 17));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 8);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsBpermuteCapturedExecOffsetAndWrap";
|
|
test.code = code;
|
|
test.initial = {0, 0, 12, 4, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
test.expected = {0, 0, 12, 4,
|
|
0xdeadbeefu, 0, 0xdeadbeefu, 101,
|
|
100, 100, 100, 100};
|
|
test.opcodes = {O::V_LSHLREV_B32, O::BUFFER_LOAD_DWORD,
|
|
O::V_MOV_B32, O::V_ADD_NC_U32,
|
|
O::V_AND_B32, O::V_CMP_EQ_U32,
|
|
O::S_MOV_B64,
|
|
O::DS_BPERMUTE_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpGroupNonUniformShuffle",
|
|
"OpGroupNonUniformBallot"};
|
|
test.forbidden_spirv = {" Workgroup", "OpControlBarrier",
|
|
"OpMemoryBarrier"};
|
|
test.decoded_counts = {{"DS_BPERMUTE_B32", 2}};
|
|
test.ir_counts = {{"BpermuteU32", 2}};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsBpermuteWave64UsesIndependentHalves() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 3, 100);
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(0), 3));
|
|
AppendVMovU32(&code, 17, 128);
|
|
code.push_back(0xdacc0000u);
|
|
code.push_back(0x01000311u); // ds_bpermute_b32 v1, v17, v3
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsBpermuteWave64UsesIndependentHalves";
|
|
test.code = code;
|
|
test.initial = std::vector<u32>(64, 0);
|
|
test.expected = std::vector<u32>(32, 100);
|
|
test.expected.insert(test.expected.end(), 32, 132);
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32,
|
|
O::DS_BPERMUTE_B32, O::V_LSHLREV_B32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpGroupNonUniformShuffle"};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.wave_size = 64;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase Wave64CrossHalfLaneAndLds() {
|
|
using O = ShaderOpcode;
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 100);
|
|
code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1));
|
|
AppendVop3(&code, 0x360, 8, Vgpr(1), InlineU32(47));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 8, 0, 0);
|
|
code.push_back(EncodeSop1(0x04, 10, 126));
|
|
code.push_back(EncodeVop2(0x1b, 2, InlineU32(63), 0));
|
|
code.push_back(EncodeVopc(0xc2, InlineU32(40), 2));
|
|
code.push_back(EncodeSop1(0x04, 126, 106));
|
|
code.push_back(EncodeVop1(0x02, 9, Vgpr(1)));
|
|
code.push_back(EncodeSop1(0x04, 126, 10));
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 9, 0, 128);
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 106, 0, 256);
|
|
AppendStoreSgprAtLaneDwordOffset(&code, 107, 0, 384);
|
|
code.push_back(EncodeVop2(0x1a, 2, InlineU32(2), 0));
|
|
code.push_back(EncodeDs0(0x0d));
|
|
code.push_back(EncodeDs1(0, 1, 2));
|
|
code.push_back(EncodeSopp(0x0a, 0));
|
|
code.push_back(EncodeVop2(0x1d, 3, InlineU32(64), 0));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 3));
|
|
code.push_back(EncodeDs0(0x36));
|
|
code.push_back(EncodeDs1(4, 0, 3));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 4, 0, 512);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Wave64CrossHalfLaneAndLds";
|
|
test.code = std::move(code);
|
|
test.initial.resize(640);
|
|
test.expected.insert(test.expected.end(), 64, 147);
|
|
test.expected.insert(test.expected.end(), 64, 211);
|
|
test.expected.insert(test.expected.end(), 64, 140);
|
|
test.expected.insert(test.expected.end(), 64, 204);
|
|
test.expected.insert(test.expected.end(), 128, 0);
|
|
test.expected.insert(test.expected.end(), 128, 256);
|
|
for (u32 lane = 0; lane < 128; lane++) {
|
|
test.expected.push_back(100 + (lane ^ 64));
|
|
}
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_READLANE_B32,
|
|
O::V_READFIRSTLANE_B32, O::V_CMP_EQ_U32, O::S_MOV_B64,
|
|
O::V_LSHLREV_B32, O::V_AND_B32, O::V_XOR_B32, O::DS_WRITE_B32,
|
|
O::S_BARRIER, O::DS_READ_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpGroupNonUniformBallot", "OpGroupNonUniformShuffle",
|
|
"OpControlBarrier"};
|
|
test.ir_counts = {{"Barrier", 1}};
|
|
test.compute_info.threads_num[0] = 128;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase Wave64RawMasksAndScalarBranch() {
|
|
using O = ShaderOpcode;
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 1, 7);
|
|
AppendSMovLiteral(&code, 8, 33u << 16u);
|
|
code.push_back(EncodeSop2(0x29, 126, 193u, 8)); // captured ES BFE EXEC,-1,width
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 0);
|
|
code.push_back(EncodeSop2(0x1f, 126, InlineU32(1), InlineU32(32)));
|
|
const auto exec_branch = code.size();
|
|
code.push_back(0);
|
|
code.push_back(EncodeSopp(0x0a, 0));
|
|
code.push_back(EncodeSop1(0x24, 10, 193u)); // s_and_saveexec_b64 s[10:11],-1
|
|
const auto scc_branch = code.size();
|
|
code.push_back(0);
|
|
AppendVMovU32(&code, 1, 9);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 64);
|
|
const auto restore = code.size();
|
|
code[exec_branch] = EncodeSopp(0x08, restore - exec_branch - 1);
|
|
code[scc_branch] = EncodeSopp(0x04, restore - scc_branch - 1);
|
|
code.push_back(EncodeSop1(0x04, 126, 193u));
|
|
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
|
|
code.push_back(EncodeSop1(0x04, 12, 106));
|
|
code.push_back(EncodeSMovB32(13, InlineU32(1)));
|
|
AppendVop3(&code, 0x101, 2, InlineU32(0), InlineU32(11), 12);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 128);
|
|
code.push_back(EncodeSMovB32(127, InlineU32(1)));
|
|
AppendVMovU32(&code, 3, 13);
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0, 192);
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(1)));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0, 256);
|
|
code.push_back(EncodeSMovB32(107, InlineU32(2)));
|
|
code.push_back(EncodeSMovB32(127, 107));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 320);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "Wave64RawMasksAndScalarBranch";
|
|
test.code = std::move(code);
|
|
test.initial.assign(384, 0xdeadbeef);
|
|
test.expected = test.initial;
|
|
for (u32 lane = 0; lane < 64; lane++) {
|
|
if (lane < 33) {
|
|
test.expected[lane] = 7;
|
|
test.expected[192 + lane] = 13;
|
|
}
|
|
test.expected[128 + lane] = lane < 33 ? 11 : 0;
|
|
}
|
|
test.expected[64 + 32] = 9;
|
|
test.expected[256] = 13;
|
|
test.expected[320] = 7;
|
|
test.expected[320 + 33] = 7;
|
|
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_BFE_U64, O::S_LSHL_B64,
|
|
O::S_CBRANCH_EXECZ, O::S_AND_SAVEEXEC_B64, O::S_CBRANCH_SCC0,
|
|
O::S_BARRIER, O::S_MOV_B64, O::V_CMP_EQ_U32, O::V_CNDMASK_B32,
|
|
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase Wave64PartialMultidimensionalWorkgroup() {
|
|
using O = ShaderOpcode;
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(2), 2));
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(2), 3));
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(1), 3));
|
|
code.push_back(EncodeVop2(0x1a, 3, InlineU32(3), 3));
|
|
code.push_back(EncodeVop2(0x25, 3, Vgpr(0), 3));
|
|
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 3));
|
|
AppendVMovU32(&code, 5, 1);
|
|
AppendBufferStoreOpcode(&code, 0x32, 5, 4, false);
|
|
AppendEnd(&code);
|
|
TestCase test;
|
|
test.name = "Wave64PartialMultidimensionalWorkgroup";
|
|
test.code = std::move(code);
|
|
test.initial.resize(128);
|
|
test.expected.assign(96, 1);
|
|
test.expected.resize(128);
|
|
test.opcodes = {O::V_ADD_NC_U32, O::V_LSHLREV_B32, O::V_MOV_B32,
|
|
O::BUFFER_ATOMIC_ADD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 8;
|
|
test.compute_info.threads_num[1] = 3;
|
|
test.compute_info.threads_num[2] = 4;
|
|
test.compute_info.thread_ids_num = 3;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase Wave64AppendConsumeHighHalf() {
|
|
using O = ShaderOpcode;
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 124, 4);
|
|
code.push_back(EncodeSop2(0x1f, 126, 193u, InlineU32(32)));
|
|
code.push_back(EncodeDs0(0x3e, 0, true));
|
|
code.push_back(EncodeDs1(1, 0, 0));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 1, 0, 0);
|
|
code.push_back(EncodeDs0(0x3d, 0, true));
|
|
code.push_back(EncodeDs1(2, 0, 0));
|
|
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 64);
|
|
AppendEnd(&code);
|
|
TestCase test;
|
|
test.name = "Wave64AppendConsumeHighHalf";
|
|
test.code = std::move(code);
|
|
test.initial.assign(128, 0xdeadbeef);
|
|
test.expected = test.initial;
|
|
for (u32 lane = 32; lane < 64; lane++) {
|
|
test.expected[lane] = 10;
|
|
test.expected[64 + lane] = 42;
|
|
}
|
|
test.gds_initial = {10};
|
|
test.expected_gds = {10};
|
|
test.opcodes = {O::S_MOV_B32, O::S_LSHL_B64, O::DS_APPEND, O::DS_CONSUME,
|
|
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferAtomicVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
const u32 values[] = {100, 5, 3, 5, 5, 5, 20, 0x0ff0u, 0x0f00u, 0x00ffu};
|
|
const u32 ops[] = {0x30, 0x32, 0x33, 0x35, 0x36,
|
|
0x37, 0x38, 0x39, 0x3a, 0x3b};
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovU32(&code, 20, i * 4u);
|
|
AppendVMovU32(&code, i, values[i]);
|
|
AppendBufferStoreOpcode(&code, ops[i], i, 20, true);
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, i, i + 10u);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"BufferAtomicVariants",
|
|
code,
|
|
{10, 10, 10, 0xfffffff0u, 10, 0xfffffff0u, 10, 0xf0f0u, 0xf000u, 0xf00fu,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
{100, 15, 7, 0xfffffff0u, 5, 5, 20,
|
|
0x00f0u, 0xff00u, 0xf0f0u, 10, 10, 10, 0xfffffff0u,
|
|
10, 0xfffffff0u, 10, 0xf0f0u, 0xf000u, 0xf00fu},
|
|
{O::V_MOV_B32, O::BUFFER_ATOMIC_SWAP, O::BUFFER_ATOMIC_ADD,
|
|
O::BUFFER_ATOMIC_SUB, O::BUFFER_ATOMIC_SMIN, O::BUFFER_ATOMIC_UMIN,
|
|
O::BUFFER_ATOMIC_SMAX, O::BUFFER_ATOMIC_UMAX, O::BUFFER_ATOMIC_AND,
|
|
O::BUFFER_ATOMIC_OR, O::BUFFER_ATOMIC_XOR, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferAtomicCmpSwapExactRaw() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
const auto Store = [&](u32 value, u32 index) {
|
|
AppendVMovU32(&code, 31, index * sizeof(u32));
|
|
code.push_back(EncodeMubuf0(0x1cu));
|
|
code.push_back(EncodeMubuf1(value, 8, 31));
|
|
};
|
|
AppendVMovU32(&code, 4, 1);
|
|
AppendVMovLiteral(&code, 1, 0x22222222u);
|
|
AppendVMovLiteral(&code, 2, 0x11111111u);
|
|
code.push_back(0xe0c46000u);
|
|
code.push_back(0x80080104u);
|
|
Store(1, 2);
|
|
|
|
AppendVMovLiteral(&code, 1, 0x33333333u);
|
|
AppendVMovLiteral(&code, 2, 0x44444444u);
|
|
code.push_back(0xe0c46000u);
|
|
code.push_back(0x80080104u);
|
|
Store(1, 3);
|
|
Store(2, 4);
|
|
|
|
AppendVMovLiteral(&code, 1, 0x55555555u);
|
|
AppendVMovLiteral(&code, 2, 0x22222222u);
|
|
code.push_back(EncodeMubuf0(0x31u, 0, true, false));
|
|
code.push_back(0x80080104u);
|
|
Store(1, 5);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferAtomicCmpSwapExactRaw";
|
|
test.code = std::move(code);
|
|
test.initial = {0xaaaaaaaau, 0x11111111u, 0, 0, 0, 0};
|
|
test.expected = {0xaaaaaaaau, 0x55555555u, 0x11111111u,
|
|
0x22222222u, 0x44444444u, 0x55555555u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_ATOMIC_CMPSWAP,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpAtomicCompareExchange"};
|
|
const auto descriptor = MakeStructuredStorageBufferData(
|
|
sizeof(u32), static_cast<u32>(test.initial.size()));
|
|
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 32);
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferAtomicGlc0DoesNotReturnOldValue() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 0, 5);
|
|
AppendBufferStoreOpcode(&code, 0x32, 0, 20);
|
|
AppendStoreVgpr(&code, 0, 1);
|
|
AppendEnd(&code);
|
|
|
|
return {
|
|
"BufferAtomicGlc0DoesNotReturnOldValue",
|
|
code,
|
|
{10, 0},
|
|
{15, 5},
|
|
{O::V_MOV_B32, O::BUFFER_ATOMIC_ADD, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferAtomicFMinExactRawGlcModes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x40000000u); // 2.0
|
|
code.push_back(0xe0fc0000u);
|
|
code.push_back(0x80010000u); // exact failing buffer_atomic_fmin, GLC=0
|
|
AppendStoreVgpr(&code, 0, 1);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u); // 1.0
|
|
code.push_back(0xe0fc4000u);
|
|
code.push_back(0x80010000u); // same instruction with GLC=1
|
|
AppendStoreVgpr(&code, 0, 2);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferAtomicFMinExactRawGlcModes";
|
|
test.code = code;
|
|
test.initial = {0x40800000u, 0, 0}; // 4.0
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40000000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_ATOMIC_FMIN, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
const auto descriptor = MakeStructuredStorageBufferData(
|
|
0, static_cast<u32>(test.initial.size() * sizeof(u32)));
|
|
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4);
|
|
test.user_data[50] = 1u << 20u;
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferAtomicFMinSpecialValues() {
|
|
using O = ShaderOpcode;
|
|
|
|
const u32 values[] = {
|
|
0x40000000u, // 2.0
|
|
0x7f800000u, // +infinity
|
|
0xff800000u, // -infinity
|
|
0x3f800000u, // 1.0
|
|
0x7fc00000u, // quiet NaN
|
|
0x00000000u, // +0.0
|
|
0x80000000u, // -0.0
|
|
0x00000000u, // +0.0
|
|
0x80000001u, // smallest negative denorm
|
|
};
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovU32(&code, 20, i * 4u);
|
|
AppendVMovLiteral(&code, i, values[i]);
|
|
AppendBufferStoreOpcode(&code, 0x3f, i, 20, true);
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, i, i + static_cast<u32>(std::size(values)));
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferAtomicFMinSpecialValues",
|
|
code,
|
|
{0x40800000u, 0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u,
|
|
0x80000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0},
|
|
{0x40000000u, 0xbf800000u, 0xff800000u, 0x7fc00000u, 0x3f800000u,
|
|
0x80000000u, 0x00000000u, 0x00000000u, 0x80000001u, 0x40800000u,
|
|
0xbf800000u, 0x7f800000u, 0x7fc00000u, 0x3f800000u, 0x80000000u,
|
|
0x00000000u, 0x00000001u, 0x00000000u},
|
|
{O::V_MOV_B32, O::BUFFER_ATOMIC_FMIN, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferAtomicFMinContendedWorkgroup() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferStoreOpcode(&code, 0x3f, 1, 20);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferAtomicFMinContendedWorkgroup";
|
|
test.code = code;
|
|
test.initial = {0x42c80000u}; // 100.0
|
|
test.expected = {0x00000000u}; // min(100.0, 0.0 .. 63.0)
|
|
test.opcodes = {O::V_CVT_F32_U32, O::V_MOV_B32, O::BUFFER_ATOMIC_FMIN,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferAtomicFMaxExactRawGlcModes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 3, 0x40000000u); // 2.0
|
|
code.push_back(0xe100000cu);
|
|
code.push_back(
|
|
0x80010300u); // exact failing buffer_atomic_fmax v3, offset 12, GLC=0
|
|
AppendStoreVgpr(&code, 3, 4);
|
|
AppendVMovLiteral(&code, 0, 0x40800000u); // 4.0
|
|
code.push_back(0xe100400cu);
|
|
code.push_back(0x80010000u); // same address with GLC=1
|
|
AppendStoreVgpr(&code, 0, 5);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferAtomicFMaxExactRawGlcModes";
|
|
test.code = code;
|
|
test.initial = {0, 0, 0, 0x3f800000u, 0, 0}; // memory[3] = 1.0
|
|
test.expected = {0, 0, 0, 0x40800000u, 0x40000000u, 0x40000000u};
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_ATOMIC_FMAX, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
const auto descriptor = MakeStructuredStorageBufferData(
|
|
0, static_cast<u32>(test.initial.size() * sizeof(u32)));
|
|
std::copy_n(descriptor.begin(), 4, test.user_data.begin() + 4);
|
|
test.user_data[50] = 1u << 20u;
|
|
test.has_user_data = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase BufferAtomicFMaxSpecialValues() {
|
|
using O = ShaderOpcode;
|
|
|
|
const u32 values[] = {
|
|
0x40000000u, // 2.0, finite update
|
|
0x40000000u, // 2.0, finite no-update
|
|
0x7f800000u, // incoming +infinity
|
|
0xff800000u, // incoming -infinity
|
|
0x3f800000u, // finite against old +infinity
|
|
0x3f800000u, // finite against old -infinity
|
|
0x3f800000u, // finite against old quiet NaN
|
|
0x7fcabcdeu, // incoming quiet NaN
|
|
0x3f800000u, // finite against old signaling NaN
|
|
0x7faabcdeu, // incoming signaling NaN
|
|
0x00000000u, // +0.0 against old -0.0
|
|
0x80000000u, // -0.0 against old +0.0
|
|
0x80000000u, // -0.0 against a negative denorm
|
|
0x80000001u, // negative denorm against -0.0
|
|
0x00000001u, // positive denorm against +0.0
|
|
0x00000000u, // +0.0 against a positive denorm
|
|
};
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovU32(&code, 20, i * 4u);
|
|
AppendVMovLiteral(&code, i, values[i]);
|
|
AppendBufferStoreOpcode(&code, 0x40, i, 20, true);
|
|
}
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendStoreVgpr(&code, i, i + static_cast<u32>(std::size(values)));
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
return {"BufferAtomicFMaxSpecialValues",
|
|
code,
|
|
{0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u,
|
|
0x7f800000u, 0xff800000u, 0x7fc12345u, 0x3f800000u,
|
|
0x7fa54321u, 0x3f800000u, 0x80000000u, 0x00000000u,
|
|
0x80000001u, 0x80000000u, 0x00000000u, 0x00000001u,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0,
|
|
0, 0, 0, 0},
|
|
{0x40000000u, 0x40800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u,
|
|
0x3f800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u, 0x3f800000u,
|
|
0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000001u,
|
|
0x00000001u, 0x3f800000u, 0x40800000u, 0xbf800000u, 0x3f800000u,
|
|
0x7f800000u, 0xff800000u, 0x7fc12345u, 0x3f800000u, 0x7fa54321u,
|
|
0x3f800000u, 0x80000000u, 0x00000000u, 0x80000001u, 0x80000000u,
|
|
0x00000000u, 0x00000001u},
|
|
{O::V_MOV_B32, O::BUFFER_ATOMIC_FMAX, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase BufferAtomicFMaxContendedWorkgroup() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x06, 1, Vgpr(0))); // v_cvt_f32_u32 v1, thread_id.x
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendBufferStoreOpcode(&code, 0x40, 1, 20);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "BufferAtomicFMaxContendedWorkgroup";
|
|
test.code = code;
|
|
test.initial = {0xc2c80000u}; // -100.0
|
|
test.expected = {0x427c0000u}; // max(-100.0, 0.0 .. 63.0)
|
|
test.opcodes = {O::V_CVT_F32_U32, O::V_MOV_B32, O::BUFFER_ATOMIC_FMAX,
|
|
O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 64;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
return test;
|
|
}
|
|
|
|
std::vector<u32> MakeRgbaImage(u32 width, u32 height, u32 value = 0) {
|
|
return std::vector<u32>(static_cast<size_t>(width) * height * 4u, value);
|
|
}
|
|
|
|
void SetRgbaPixel(std::vector<u32> *image, u32 width, u32 x, u32 y, u32 r,
|
|
u32 g, u32 b, u32 a) {
|
|
const auto base = static_cast<size_t>((y * width + x) * 4u);
|
|
(*image)[base + 0] = r;
|
|
(*image)[base + 1] = g;
|
|
(*image)[base + 2] = b;
|
|
(*image)[base + 3] = a;
|
|
}
|
|
|
|
TestCase ImageLoadVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 23, 0);
|
|
code.push_back(EncodeMimg0(0x00, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
code.push_back(EncodeMimg0(0x01, 0xf));
|
|
code.push_back(EncodeMimg1(4, 20));
|
|
AppendVMovU32(&code, 24, 0);
|
|
code.push_back(EncodeMimg0(0x0e, 0x1));
|
|
code.push_back(EncodeMimg1(8, 24));
|
|
for (u32 i = 0; i < 9u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadVariants";
|
|
test.code = code;
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u, 0x3f800000u, 0x40000000u,
|
|
0x40400000u, 0x40800000u, 4};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD,
|
|
O::IMAGE_LOAD_MIP, O::IMAGE_GET_RESINFO,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
return test;
|
|
}
|
|
|
|
TestCase DsAppendConsumeUsesEncodedLdsSelector() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 124, 0x0000ffffu);
|
|
AppendVMovU32(&code, 1, 0);
|
|
AppendVMovU32(&code, 2, 10);
|
|
code.push_back(EncodeDs0(0x0d, 0));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
code.push_back(EncodeDs0(0x3e, 0));
|
|
code.push_back(EncodeDs1(3, 0, 0));
|
|
code.push_back(EncodeDs0(0x3d, 0));
|
|
code.push_back(EncodeDs1(4, 0, 0));
|
|
AppendSMovLiteral(&code, 124, 0);
|
|
code.push_back(EncodeDs0(0x3e, 0));
|
|
code.push_back(EncodeDs1(5, 0, 0));
|
|
code.push_back(EncodeDs0(0x36, 0));
|
|
code.push_back(EncodeDs1(6, 0, 1));
|
|
AppendStoreVgpr(&code, 3, 0);
|
|
AppendStoreVgpr(&code, 4, 1);
|
|
AppendStoreVgpr(&code, 5, 2);
|
|
AppendStoreVgpr(&code, 6, 3);
|
|
AppendEnd(&code);
|
|
|
|
return {"DsAppendConsumeLdsSelector",
|
|
code,
|
|
{},
|
|
{10, 11, 0, 10},
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::DS_WRITE_B32, O::DS_READ_B32,
|
|
O::DS_APPEND, O::DS_CONSUME, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase DsAppendUsesEncodedGdsSelector() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendSMovLiteral(&code, 124, 0x00000008u);
|
|
code.push_back(EncodeDs0(0x3e, 0, true));
|
|
code.push_back(EncodeDs1(0, 0, 0));
|
|
code.push_back(EncodeDs0(0x3d, 0, true));
|
|
code.push_back(EncodeDs1(1, 0, 0));
|
|
code.push_back(EncodeDs0(0x3e, 4, true));
|
|
code.push_back(EncodeDs1(2, 0, 0));
|
|
code.push_back(EncodeDs0(0x3d, 4, true));
|
|
code.push_back(EncodeDs1(3, 0, 0));
|
|
AppendSMovLiteral(&code, 124, 0x00080008u);
|
|
code.push_back(EncodeDs0(0x3e, 4, true));
|
|
code.push_back(EncodeDs1(4, 0, 0));
|
|
code.push_back(EncodeDs0(0x3d, 4, true));
|
|
code.push_back(EncodeDs1(5, 0, 0));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendStoreVgpr(&code, 2, 2);
|
|
AppendStoreVgpr(&code, 3, 3);
|
|
AppendStoreVgpr(&code, 4, 4);
|
|
AppendStoreVgpr(&code, 5, 5);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test{"DsAppendGdsSelector",
|
|
code,
|
|
{},
|
|
{10, 11, 20, 21, 40, 41},
|
|
{O::S_MOV_B32, O::DS_APPEND, O::DS_CONSUME,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
|
test.gds_initial = {10, 20, 30, 40};
|
|
test.expected_gds = {10, 20, 30, 40};
|
|
return test;
|
|
}
|
|
|
|
TestCase DsGdsSubdwordAndAtomicWrites() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 1, Vgpr(0)));
|
|
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 0));
|
|
code.push_back(EncodeDs0(0x1e, 0, true));
|
|
code.push_back(EncodeDs1(0, 2, 1));
|
|
code.push_back(EncodeVop2(0x1a, 8, InlineU32(1), 0));
|
|
code.push_back(EncodeVop2(0x25, 8, InlineU32(12), 8));
|
|
code.push_back(EncodeDs0(0x1f, 0, true));
|
|
code.push_back(EncodeDs1(0, 2, 8));
|
|
AppendVMovU32(&code, 3, 4);
|
|
AppendVMovU32(&code, 4, 1);
|
|
code.push_back(EncodeDs0(0x00, 0, true));
|
|
code.push_back(EncodeDs1(0, 4, 3));
|
|
AppendVMovU32(&code, 5, 8);
|
|
AppendVMovLiteral(&code, 6, 0x40a00000u);
|
|
AppendVMovLiteral(&code, 7, 0x40a00000u);
|
|
code.push_back(EncodeDs0(0x12, 0, true));
|
|
code.push_back(EncodeDs1Ex(0, 7, 6, 5));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DsGdsSubdwordAndAtomics";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
|
|
O::DS_WRITE_B8, O::DS_WRITE_B16, O::DS_ADD_U32,
|
|
O::DS_MIN_F32, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.has_compute_info = true;
|
|
test.gds_initial = {0, 0, 0x42c80000u, 0, 0};
|
|
test.expected_gds = {0x04030201u, 4, 0x40a00000u, 0x00020001u, 0x00040003u};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadR32UintUsesIntegerSampledImage() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
code.push_back(EncodeMimg0(0x00, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadR32UintUsesIntegerSampledImage";
|
|
test.code = code;
|
|
test.expected = {0xdeadbeefu};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba.resize(16);
|
|
test.sampled_image_rgba[6] = 0xdeadbeefu;
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_10", "OpTypeImage %uint",
|
|
"OpImageFetch %v4uint"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadFmaskUsesNativeSampleMapping() {
|
|
auto test = ImageLoadR32UintUsesIntegerSampledImage();
|
|
test.name = "ImageLoadFmaskUsesNativeSampleMapping";
|
|
test.expected = {0x76543210u};
|
|
test.user_data = {0x303ac300u, 0xca100000u, 0x021bc3bfu, 0x91800004u,
|
|
0u, 0x00700000u, 0u, 0u};
|
|
test.image_descriptor_swizzle = DstSel(4, 0, 0, 0);
|
|
test.sampled_image_rgba.clear();
|
|
test.required_spirv = {"OpConstant %uint 1985229328"};
|
|
test.forbidden_spirv = {"OpTypeImage", "OpImageFetch"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadR32SintUsesSignedSampledImage() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
code.push_back(EncodeMimg0(0x00, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadR32SintUsesSignedSampledImage";
|
|
test.code = code;
|
|
test.expected = {0xffffff80u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba.resize(16);
|
|
test.sampled_image_rgba[6] = 0xffffff80u;
|
|
test.sampled_image_format = vk::Format::eR32Sint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32SInt);
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_17", "OpTypeImage %int", "OpImageFetch %v4int",
|
|
"OpBitcast %uint"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadPackedUintUnpacksAndSwizzles() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
code.push_back(EncodeMimg0(0x00, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
for (u32 component = 0; component < 4; component++) {
|
|
AppendStoreVgpr(&code, component, component);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadPackedUintUnpacksAndSwizzles";
|
|
test.code = std::move(code);
|
|
test.expected = {0x2abu, 0x456u, 0x321u, 0x321u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba.resize(16);
|
|
test.sampled_image_rgba[6] = 0xaae2b321u;
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.user_data =
|
|
MakeSampledTextureData(Prospero::BufferFormat::k11_11_10UInt);
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = DstSel(6, 5, 4, 7);
|
|
test.required_spirv = {"OpImageFetch %v4uint", "OpBitFieldUExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSamplePackedUintConvertsSampleAndGather() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 21, 0x3ec00000u);
|
|
code.push_back(EncodeMimg0(0x20, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
code.push_back(EncodeMimg0(0x47, 0x2));
|
|
code.push_back(EncodeMimg1(4, 20));
|
|
for (u32 component = 0; component < 8; component++) {
|
|
AppendStoreVgpr(&code, component, component);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSamplePackedUintConvertsSampleAndGather";
|
|
test.code = std::move(code);
|
|
test.expected = {0x2abu, 0x456u, 0x321u, 0x321u,
|
|
0x456u, 0x456u, 0x456u, 0x456u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GATHER4_LZ,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.sampled_image_rgba = std::vector<u32>(16, 0xaae2b321u);
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.user_data =
|
|
MakeSampledTextureData(Prospero::BufferFormat::k11_11_10UInt);
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = DstSel(6, 5, 4, 7);
|
|
test.expected_force_point_sampler = true;
|
|
test.required_spirv = {"OpImageSampleExplicitLod", "OpImageGather",
|
|
"OpBitFieldUExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadR128IgnoresAdjacentMaskSgprs() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
code.push_back(EncodeSop1(0x04, 4, 126)); // s_mov_b64 s[4:5], exec
|
|
code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 1, true));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadR128IgnoresAdjacentMaskSgprs";
|
|
test.code = code;
|
|
test.expected = {0xdeadbeefu};
|
|
test.opcodes = {O::V_MOV_B32, O::S_MOV_B64, O::IMAGE_LOAD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.sampled_image_rgba.resize(16);
|
|
test.sampled_image_rgba[6] = 0xdeadbeefu;
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.has_user_data = true;
|
|
test.decoded_counts = {{"r128=1", 1}};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoad1DUsesScalarCoordinate() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 0));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoad1DUsesScalarCoordinate";
|
|
test.code = code;
|
|
test.expected = {0xdeadbeefu};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.image_width = 4;
|
|
test.image_height = 1;
|
|
test.sampled_image_rgba = {0, 0, 0xdeadbeefu, 0};
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.sampled_image_type = vk::ImageType::e1D;
|
|
test.sampled_image_view_type = vk::ImageViewType::e1D;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor1D)
|
|
<< 28u;
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_8"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageGather2DInstructionWith1DDescriptor() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 21, 0x3f000000u);
|
|
code.push_back(EncodeMimg0(0x47, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
for (u32 i = 0; i < 4u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 1);
|
|
SetRgbaPixel(&image, 4, 1, 0, 0x40000000u, 0, 0, 0);
|
|
SetRgbaPixel(&image, 4, 2, 0, 0x40400000u, 0, 0, 0);
|
|
|
|
TestCase test;
|
|
test.name = "ImageGather2DInstructionWith1DDescriptor";
|
|
test.code = code;
|
|
test.expected = {0x40000000u, 0x40400000u, 0x40400000u, 0x40000000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.image_width = 4;
|
|
test.image_height = 1;
|
|
test.sampled_image_rgba = image;
|
|
test.sampled_image_type = vk::ImageType::e1D;
|
|
test.sampled_image_view_type = vk::ImageViewType::e1D;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32Float);
|
|
test.user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor1D)
|
|
<< 28u;
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_1", "OpImageQuerySizeLod",
|
|
"OpImageSampleExplicitLod", "Floor"};
|
|
test.forbidden_spirv = {"OpImageGather"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoad1DArrayUsesLayerCoordinate() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 4));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoad1DArrayUsesLayerCoordinate";
|
|
test.code = code;
|
|
test.expected = {0xcafebabeu};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.image_width = 4;
|
|
test.image_height = 1;
|
|
test.sampled_image_rgba = {0, 0, 0, 0, 0, 0, 0xcafebabeu, 0};
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.sampled_image_type = vk::ImageType::e1D;
|
|
test.sampled_image_view_type = vk::ImageViewType::e1DArray;
|
|
test.sampled_image_layers = 2;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor1DArray)
|
|
<< 28u;
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_9"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoad1DArrayDescriptorUsesSelectedLayer() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
code.push_back(EncodeMimg0(0x00, 0x1, 0, false, 0));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoad1DArrayDescriptorUsesSelectedLayer";
|
|
test.code = code;
|
|
test.expected = {0xcafebabeu};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.image_width = 4;
|
|
test.image_height = 1;
|
|
test.sampled_image_rgba = {
|
|
0, 0, 0xdeadbeefu, 0, 0, 0, 0xcafebabeu, 0,
|
|
};
|
|
test.sampled_image_format = vk::Format::eR32Uint;
|
|
test.sampled_image_dwords_per_pixel = 1;
|
|
test.sampled_image_type = vk::ImageType::e1D;
|
|
test.sampled_image_view_type = vk::ImageViewType::e1D;
|
|
test.sampled_image_layers = 2;
|
|
test.sampled_image_view_base_layer = 1;
|
|
test.sampled_image_view_layers = 1;
|
|
test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.user_data[3] = static_cast<uint32_t>(Prospero::ImageType::kColor1DArray)
|
|
<< 28u;
|
|
test.user_data[4] = 1u | (1u << 16u);
|
|
test.has_user_data = true;
|
|
test.required_spirv = {"image_8"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadMipUsesVaddr2Lod2D() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 1);
|
|
AppendVMovU32(&code, 23, 0);
|
|
code.push_back(EncodeMimg0(0x01, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
auto base = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&base, 4, 1, 1, 0x3f800000u, 0, 0, 0);
|
|
auto mip1 = MakeRgbaImage(2, 2);
|
|
SetRgbaPixel(&mip1, 2, 1, 1, 0x40000000u, 0, 0, 0);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadMipUsesVaddr2Lod2D";
|
|
test.code = code;
|
|
test.expected = {0x40000000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD_MIP, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba_mips = {base, mip1};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadMipNsaUsesSelectedAddressVgprs() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 30, 1);
|
|
AppendVMovU32(&code, 31, 1);
|
|
code.push_back(EncodeMimg0(0x01, 0x1, 1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
code.push_back((30u << 0u) | (31u << 8u));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
auto base = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&base, 4, 1, 0, 0x3f800000u, 0, 0, 0);
|
|
auto mip1 = MakeRgbaImage(2, 2);
|
|
SetRgbaPixel(&mip1, 2, 1, 1, 0x40a00000u, 0, 0, 0);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadMipNsaUsesSelectedAddressVgprs";
|
|
test.code = code;
|
|
test.expected = {0x40a00000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD_MIP, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba_mips = {base, mip1};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageLoadA16UintCoordsOnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x00010002u); // x=2, y=1 packed as u16.
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeMimg0(0x00, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, true));
|
|
for (u32 i = 0; i < 4u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageLoadA16UintCoordsOnGpu";
|
|
test.code = code;
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_LOAD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
test.required_spirv = {"OpShiftRightLogical", "OpBitwiseAnd"};
|
|
test.forbidden_spirv = {"UnpackHalf2x16"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageGetResinfoDmaskWidthHeight() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 24, 0);
|
|
code.push_back(EncodeMimg0(0x0e, 0x3));
|
|
code.push_back(EncodeMimg1(0, 24));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageGetResinfoDmaskWidthHeight";
|
|
test.code = code;
|
|
test.expected = {4, 2};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_GET_RESINFO, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.image_width = 4;
|
|
test.image_height = 2;
|
|
test.sampled_image_rgba = MakeRgbaImage(test.image_width, test.image_height);
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageGetResinfoDmaskMipLevels() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 24, 0);
|
|
code.push_back(EncodeMimg0(0x0e, 0x8));
|
|
code.push_back(EncodeMimg1(0, 24));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageGetResinfoDmaskMipLevels";
|
|
test.code = code;
|
|
test.expected = {2};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_GET_RESINFO, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
auto base = MakeRgbaImage(4, 4);
|
|
auto mip1 = MakeRgbaImage(2, 2);
|
|
test.sampled_image_rgba_mips = {base, mip1};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSampleAndGather() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 21, 0x3ec00000u);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMimg0(0x20, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
code.push_back(EncodeMimg0(0x47, 0x1));
|
|
code.push_back(EncodeMimg1(4, 20));
|
|
AppendVMovU32(&code, 24, 0);
|
|
AppendVMovLiteral(&code, 25, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 26, 0x3ec00000u);
|
|
AppendVMovU32(&code, 27, 0);
|
|
code.push_back(EncodeMimg0(0x57, 0x1));
|
|
code.push_back(EncodeMimg1(8, 24));
|
|
code.push_back(EncodeMimg0(0x60, 0x1));
|
|
code.push_back(EncodeMimg1(12, 20));
|
|
for (u32 i = 0; i < 13u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
for (u32 y = 0; y < 4u; y++) {
|
|
for (u32 x = 0; x < 4u; x++) {
|
|
SetRgbaPixel(&image, 4, x, y, 0x3f800000u, 0, 0, 0);
|
|
}
|
|
}
|
|
SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSampleAndGather";
|
|
test.code = code;
|
|
test.expected = {0x3f800000u,
|
|
0x40000000u,
|
|
0x40400000u,
|
|
0x40800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0x3f800000u,
|
|
0};
|
|
test.opcodes = {
|
|
O::V_MOV_B32, O::IMAGE_SAMPLE, O::IMAGE_GET_LOD,
|
|
O::IMAGE_GATHER4_LZ, O::IMAGE_GATHER4_LZ_O, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageD16GatherPacksHalfPairs() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 21, 0x3f000000u);
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMimg0(0x47, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, false, true));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
for (u32 y = 0; y < 4u; y++) {
|
|
for (u32 x = 0; x < 4u; x++) {
|
|
SetRgbaPixel(&image, 4, x, y, 0x3f000000u, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
TestCase test;
|
|
test.name = "ImageD16GatherPacksHalfPairs";
|
|
test.code = code;
|
|
test.expected = {0x38003800u, 0x38003800u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
test.decoded_counts = {{"d16=1", 1}};
|
|
test.required_spirv = {"OpImageGather", "PackHalf2x16"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSampleA16SamplerCoordsOnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x36003900u); // x=0.625, y=0.375 packed as f16.
|
|
AppendVMovU32(&code, 21, 0);
|
|
code.push_back(EncodeMimg0(0x20, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, true));
|
|
for (u32 i = 0; i < 4u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSampleA16SamplerCoordsOnGpu";
|
|
test.code = code;
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
test.required_spirv = {"UnpackHalf2x16"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSampleOpcodeAliasUsesNormalCoords() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 2, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 3, 0x3f000000u);
|
|
code.push_back(0xf0800109u); // observed image_sample_a v6, v2, s0, s24
|
|
code.push_back(0x00c00602u);
|
|
AppendStoreVgpr(&code, 6, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSampleOpcodeAliasUsesNormalCoords";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpImageSampleExplicitLod"};
|
|
test.forbidden_spirv = {"UnpackHalf2x16"};
|
|
test.compile_only = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20,
|
|
1); // Non-constant +1 X offset is not a SPIR-V ConstOffset.
|
|
AppendVMovLiteral(&code, 21, 0x36003900u); // x=0.625, y=0.375 packed as f16.
|
|
AppendVMovU32(&code, 22, 0);
|
|
code.push_back(EncodeMimg0(0x30, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, true));
|
|
for (u32 i = 0; i < 4u; i++) {
|
|
AppendStoreVgpr(&code, i, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
auto image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&image, 4, 2, 1, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu";
|
|
test.code = code;
|
|
test.expected = {0x3f800000u, 0x40000000u, 0x40400000u, 0x40800000u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.sampled_image_rgba = image;
|
|
test.required_spirv = {"UnpackHalf2x16"};
|
|
test.forbidden_spirv = {"OpBitFieldSExtract"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageSampleA16CompareBiasRdna2AddressOrder() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 20, 0x00003800u); // bias=0.5 as low f16.
|
|
AppendVMovLiteral(&code, 21, 0x3f000000u); // PCF reference stays 32-bit.
|
|
AppendVMovLiteral(&code, 22, 0x36003900u); // x=0.625, y=0.375 packed as f16.
|
|
code.push_back(EncodeMimg0(0x2d, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, true));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
// Ordinary and comparison reads of one sharp require distinct SPIR-V image types.
|
|
code.push_back(EncodeMimg0(0x27, 0x1));
|
|
code.push_back(EncodeMimg1(1, 22, 0, 0, true));
|
|
AppendStoreVgpr(&code, 1, 1);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageSampleA16CompareBiasRdna2AddressOrder";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_SAMPLE, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpTypeImage %float 2D 0", "OpTypeImage %float 2D 1",
|
|
"OpImageSampleDrefExplicitLod", "OpImageSampleExplicitLod",
|
|
"UnpackHalf2x16"};
|
|
test.compile_only = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageGatherCompareOpcodes() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovLiteral(&code, 21, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 22, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 23, 0x3ec00000u);
|
|
AppendVMovLiteral(&code, 24, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 25, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 26, 0x3ec00000u);
|
|
AppendVMovLiteral(&code, 28, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 29, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 30, 0x3ec00000u);
|
|
AppendVMovU32(&code, 32, 0);
|
|
AppendVMovLiteral(&code, 33, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 34, 0x3f200000u);
|
|
AppendVMovLiteral(&code, 35, 0x3ec00000u);
|
|
code.push_back(EncodeMimg0(0x48, 0x1));
|
|
code.push_back(EncodeMimg1(4, 24));
|
|
code.push_back(EncodeMimg0(0x4f, 0x1));
|
|
code.push_back(EncodeMimg1(8, 28));
|
|
code.push_back(EncodeMimg0(0x58, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
code.push_back(EncodeMimg0(0x5f, 0x1));
|
|
code.push_back(EncodeMimg1(12, 32));
|
|
AppendStoreVgpr(&code, 4, 0);
|
|
AppendStoreVgpr(&code, 8, 1);
|
|
AppendStoreVgpr(&code, 0, 2);
|
|
AppendStoreVgpr(&code, 12, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageGatherCompareOpcodes";
|
|
test.code = code;
|
|
test.opcodes = {
|
|
O::V_MOV_B32, O::IMAGE_GATHER4_C, O::IMAGE_GATHER4_C_LZ,
|
|
O::IMAGE_GATHER4_C_O, O::IMAGE_GATHER4_C_LZ_O, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.required_spirv = {"OpImageDrefGather", "OpBitFieldSExtract"};
|
|
test.compile_only = true;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 2);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x40000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40400000u);
|
|
AppendVMovLiteral(&code, 3, 0x40800000u);
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendVMovU32(&code, 24, 3);
|
|
AppendVMovU32(&code, 25, 2);
|
|
AppendVMovU32(&code, 26, 0);
|
|
AppendVMovU32(&code, 27, 0);
|
|
AppendVMovLiteral(&code, 4, 0x40a00000u);
|
|
AppendVMovLiteral(&code, 5, 0x40c00000u);
|
|
AppendVMovLiteral(&code, 6, 0x40e00000u);
|
|
AppendVMovLiteral(&code, 7, 0x41000000u);
|
|
code.push_back(EncodeMimg0(0x09, 0xf));
|
|
code.push_back(EncodeMimg1(4, 24));
|
|
AppendVMovLiteral(&code, 8, 0x12345678u);
|
|
AppendStoreVgpr(&code, 8, 0);
|
|
AppendEnd(&code);
|
|
|
|
auto expected_image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&expected_image, 4, 1, 2, 0x3f800000u, 0x40000000u, 0x40400000u,
|
|
0x40800000u);
|
|
SetRgbaPixel(&expected_image, 4, 3, 2, 0x40a00000u, 0x40c00000u, 0x40e00000u,
|
|
0x41000000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreVariants";
|
|
test.code = code;
|
|
test.expected = {0x12345678u};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_STORE_MIP,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba = expected_image;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageD16StoreUnpacksHalfPairs() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 2);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x38003400u);
|
|
AppendVMovLiteral(&code, 1, 0x3c003a00u);
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20, 0, 0, false, true));
|
|
AppendEnd(&code);
|
|
|
|
auto expected_image = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&expected_image, 4, 1, 2, 0x3e800000u, 0x3f000000u, 0x3f400000u,
|
|
0x3f800000u);
|
|
|
|
TestCase test;
|
|
test.name = "ImageD16StoreUnpacksHalfPairs";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.expected_storage_image_rgba = expected_image;
|
|
test.decoded_counts = {{"d16=1", 1}};
|
|
test.required_spirv = {"UnpackHalf2x16", "OpImageWrite"};
|
|
return test;
|
|
}
|
|
|
|
void CheckIndirectImageKeySwitch() {
|
|
constexpr const char *name = "IndirectImageKeySwitch";
|
|
constexpr uint32_t mapping_capacity = 1793u;
|
|
using namespace ShaderRecompiler::IR;
|
|
|
|
Program program{};
|
|
program.stage = ShaderType::Compute;
|
|
program.wave_size = 32;
|
|
program.srt_plan_complete = true;
|
|
program.resource_tracking_complete = true;
|
|
program.shader_info_complete = true;
|
|
program.block_storage.push_back(std::make_unique<Block>());
|
|
auto *block = program.block_storage.back().get();
|
|
program.blocks.push_back(block);
|
|
program.block_info.push_back({.id = 0});
|
|
|
|
auto &key = block->AppendNewInst(ValueOpcode::LaneId);
|
|
auto &image =
|
|
block->AppendNewInst(ValueOpcode::GetImageResource,
|
|
{Value(&key), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
image.SetFlags<uint32_t>(0u);
|
|
auto &sampler =
|
|
block->AppendNewInst(ValueOpcode::GetSamplerResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
sampler.SetFlags<uint32_t>(0u);
|
|
auto &address = block->AppendNewInst(
|
|
ValueOpcode::MakeImageAddress,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u)});
|
|
MemoryInfo memory{};
|
|
memory.kind = ResourceKind::Image;
|
|
memory.resource = 0;
|
|
memory.sampler = 0;
|
|
memory.dmask = 0xf;
|
|
memory.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLod;
|
|
memory.image_dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
memory.image_address_components = 3;
|
|
program.memory_info.push_back(memory);
|
|
const MemoryFlags memory_flags{0u, 0x10f0u};
|
|
uint64_t memory_flag_bits = 0;
|
|
std::memcpy(&memory_flag_bits, &memory_flags, sizeof(memory_flags));
|
|
auto &sample = block->AppendNewInst(
|
|
ValueOpcode::ImageSampleRaw,
|
|
{Value(&image), Value(&sampler), Value(&address)}, memory_flag_bits);
|
|
auto &sample_x = block->AppendNewInst(ValueOpcode::CompositeExtractU32x4,
|
|
{Value(&sample), Value(0u)});
|
|
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(&sample_x)});
|
|
|
|
program.descriptor_sources.resize(2);
|
|
program.descriptor_sources[0].dword_count = 8;
|
|
program.descriptor_sources[0].indirect_image =
|
|
DescriptorSource::IndirectImage{0u, 0u, 224u, 12u, 0u};
|
|
program.descriptor_sources[1].dword_count = 4;
|
|
|
|
ImageResource root{};
|
|
root.source = 0;
|
|
root.first_use_pc = 0x10f0u;
|
|
root.resource_class = ImageResourceClass::Sampled;
|
|
root.numeric_class = Prospero::TextureNumericClass::Float;
|
|
root.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
root.read = true;
|
|
root.indirect_root = 0;
|
|
root.indirect_mapping_offset = 0;
|
|
root.indirect_search_iterations = std::bit_width(mapping_capacity);
|
|
root.indirect_resources = {0u, 1u};
|
|
auto candidate = root;
|
|
candidate.indirect_search_iterations = 0;
|
|
candidate.indirect_resources.clear();
|
|
program.info.images = {root, candidate};
|
|
program.info.samplers.push_back({1u, 0x10f0u});
|
|
program.info.sampled_pairs.push_back({0u, 0u, 0x10f0u});
|
|
|
|
AllocateBindings(program);
|
|
ShaderComputeInputInfo compute{};
|
|
ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program);
|
|
auto spirv = ShaderRecompiler::Spirv::EmitProgram(program,
|
|
{.compute = &compute});
|
|
ValidateSpirv(name, spirv);
|
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
|
std::string text;
|
|
Require(name, "SPIR-V disassembly", tools.Disassemble(spirv, &text),
|
|
"failed to disassemble indirect image shader");
|
|
Require(name, "key switch",
|
|
text.find("OpSwitch") != std::string::npos &&
|
|
text.find("OpPhi") != std::string::npos &&
|
|
CountText(text, "OpImageSampleExplicitLod") == 2 &&
|
|
CountText(text, "OpIEqual") == 11,
|
|
"dynamic image key did not use a compact two-sample switch");
|
|
}
|
|
|
|
TestCase ImageStoreMipSelectsPpsa01340Descriptor() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 2);
|
|
AppendVMovU32(&code, 23, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x40000000u);
|
|
AppendVMovLiteral(&code, 2, 0x40400000u);
|
|
AppendVMovLiteral(&code, 3, 0x40800000u);
|
|
code.push_back(EncodeMimg0(0x09, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreMipSelectsPpsa01340Descriptor";
|
|
test.code = std::move(code);
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE_MIP, O::S_ENDPGM};
|
|
const std::array<u32, 8> descriptor{0x0294dc00u, 0xc4700000u, 0x00b3c13fu,
|
|
0x91b31facu, 0x00000000u, 0x00700030u,
|
|
0xb07b0000u, 0x0002ac3cu};
|
|
std::copy(descriptor.begin(), descriptor.end(), test.user_data.begin());
|
|
test.has_user_data = true;
|
|
test.compile_only = true;
|
|
test.expected_storage_mip_descriptors = 3;
|
|
test.required_spirv = {"OpSwitch", "OpImageWrite"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreRgbOneUsesInverseSwizzle() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 2);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f008081u);
|
|
AppendVMovLiteral(&code, 2, 0x3e808081u);
|
|
AppendVMovLiteral(&code, 3, 0x3f40c0c1u);
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreRgbOneUsesInverseSwizzle";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.has_user_data = true;
|
|
test.user_data[0] = 0x1000u;
|
|
test.user_data[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k8_8_8_8UNorm) << 20u;
|
|
test.image_descriptor_swizzle = DstSel(4, 5, 6, 1);
|
|
test.storage_image_format = vk::Format::eR8G8B8A8Unorm;
|
|
test.storage_image_dwords_per_pixel = 1;
|
|
test.storage_image_rgba = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba[2 * 4 + 1] = 0x004080ffu;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreDuplicateSelectorUsesInverseSwizzle() {
|
|
auto test = ImageStoreRgbOneUsesInverseSwizzle();
|
|
test.name = "ImageStoreDuplicateSelectorUsesInverseSwizzle";
|
|
test.image_descriptor_swizzle = DstSel(4, 4, 6, 7);
|
|
test.expected_storage_image_rgba[2 * 4 + 1] = 0xc04000ffu;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreBgraUsesInverseSwizzle() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 2);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f008081u); // 128/255
|
|
AppendVMovLiteral(&code, 2, 0x3e808081u); // 64/255
|
|
AppendVMovLiteral(&code, 3, 0x3f3fbfc0u); // 191/255
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreBgraUsesInverseSwizzle";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.has_user_data = true;
|
|
test.user_data[0] = 0x1000u;
|
|
test.user_data[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k8_8_8_8UNorm) << 20u;
|
|
test.image_descriptor_swizzle = DstSel(6, 5, 4, 7);
|
|
test.storage_image_format = vk::Format::eR8G8B8A8Unorm;
|
|
test.storage_image_dwords_per_pixel = 1;
|
|
test.storage_image_rgba = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba[2 * 4 + 1] = 0xbfff8040u;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreYzwxUsesInverseSwizzle() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 1);
|
|
AppendVMovU32(&code, 21, 2);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 2, 0x3e800000u);
|
|
AppendVMovLiteral(&code, 3, 0x3f400000u);
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreYzwxUsesInverseSwizzle";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.user_data =
|
|
MakeStorageTextureData(Prospero::BufferFormat::k32_32_32_32Float);
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = DstSel(5, 6, 7, 4);
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.expected_storage_image_rgba = MakeRgbaImage(4, 4);
|
|
SetRgbaPixel(&test.expected_storage_image_rgba, 4, 1, 2, 0x3f400000u,
|
|
0x3f800000u, 0x3f000000u, 0x3e800000u);
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreR32FloatUsesFormatlessStorageImage() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x3f800000u);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 0x3f800000u;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreR32FloatUsesFormatlessStorageImage";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.storage_image_format = vk::Format::eR32Sfloat;
|
|
test.storage_image_dwords_per_pixel = 1;
|
|
test.storage_image_rgba = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_rgba = expected_image;
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat"};
|
|
test.forbidden_spirv = {"OpCapability StorageImageReadWithoutFormat",
|
|
"Rgba32f", "OpTypeSampler", "OpTypeSampledImage"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreR32SintUsesRawUintView() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x80000001u);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 0x80000001u;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreR32SintUsesRawUintView";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32SInt);
|
|
test.has_user_data = true;
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = expected_image;
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false)};
|
|
test.forbidden_spirv = {"R32ui"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreR32UintUsesFormatlessStorageImage() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, 0x12345678u);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 0x12345678u;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreR32UintUsesFormatlessStorageImage";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.has_user_data = true;
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = expected_image;
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false)};
|
|
test.forbidden_spirv = {"R32ui"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStorePackedUintSaturatesChannels() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 0x800u);
|
|
AppendVMovLiteral(&code, 1, UINT32_MAX);
|
|
AppendVMovU32(&code, 2, 0x400u);
|
|
AppendVMovU32(&code, 3, 0x55u);
|
|
code.push_back(EncodeMimg0(0x08, 0xf));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = UINT32_MAX;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStorePackedUintSaturatesChannels";
|
|
test.code = std::move(code);
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.user_data =
|
|
MakeStorageTextureData(Prospero::BufferFormat::k11_11_10UInt);
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = DstSel(4, 5, 6, 0);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = std::move(expected_image);
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false), "OpULessThan",
|
|
"OpSelect"};
|
|
test.forbidden_spirv = {"R32ui"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStorePackedUintHonorsSparseDmask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 0x321u);
|
|
AppendVMovU32(&code, 1, 0x2abu);
|
|
code.push_back(EncodeMimg0(0x08, 0x5));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 0xaac00321u;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStorePackedUintHonorsSparseDmask";
|
|
test.code = std::move(code);
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::S_ENDPGM};
|
|
test.user_data =
|
|
MakeStorageTextureData(Prospero::BufferFormat::k11_11_10UInt);
|
|
test.has_user_data = true;
|
|
test.image_descriptor_swizzle = DstSel(4, 5, 6, 0);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = std::move(expected_image);
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false)};
|
|
test.forbidden_spirv = {"R32ui"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ComputeTgSizeSgprUsesWaveMetadata() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendStoreSgpr(&code, 2, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ComputeTgSizeSgprUsesWaveMetadata";
|
|
test.code = code;
|
|
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 4;
|
|
test.compute_info.threads_num[2] = 16;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.group_id[1] = true;
|
|
test.compute_info.wave_size = 32;
|
|
test.compute_info.thread_ids_num = 3;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.compute_info.tg_size_en = true;
|
|
test.has_compute_info = true;
|
|
test.compile_only = true;
|
|
test.required_spirv = {"OpUDiv", "OpShiftLeftLogical", "2147483648"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreAndAtomicShareTypedBinding() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 5);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendVMovU32(&code, 0, 3);
|
|
code.push_back(EncodeMimg0(0x11, 0x1, 0, true));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 8;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreAndAtomicShareTypedBinding";
|
|
test.code = std::move(code);
|
|
test.expected = {5};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.has_user_data = true;
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = std::move(expected_image);
|
|
test.required_spirv = {"OpImageWrite", "OpImageTexelPointer", "R32ui",
|
|
StorageUint2DImageBindingName(true)};
|
|
test.forbidden_spirv = {"StorageImageReadWithoutFormat",
|
|
"StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false)};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageAtomicSwapReturnsPreviousTexel() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 5);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendVMovU32(&code, 0, 3);
|
|
code.push_back(EncodeMimg0(0x0f, 0x1, 0, true));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 3;
|
|
|
|
TestCase test;
|
|
test.name = "ImageAtomicSwapReturnsPreviousTexel";
|
|
test.code = std::move(code);
|
|
test.expected = {5};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_SWAP,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
|
test.has_user_data = true;
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = std::move(expected_image);
|
|
test.required_spirv = {"OpAtomicExchange", "OpImageTexelPointer"};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageStoreAndAtomicUseSeparateBindings() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 2);
|
|
AppendVMovU32(&code, 21, 1);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 5);
|
|
code.push_back(EncodeMimg0(0x08, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20, 0));
|
|
AppendVMovU32(&code, 0, 3);
|
|
code.push_back(EncodeMimg0(0x11, 0x1, 0, true));
|
|
code.push_back(EncodeMimg1(0, 20, 2));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
auto user_data = MakeStorageTextureData(Prospero::BufferFormat::k32UInt);
|
|
std::copy_n(user_data.begin(), 8, user_data.begin() + 8);
|
|
std::vector<u32> expected_image(16, 0);
|
|
expected_image[1 * 4 + 2] = 8;
|
|
|
|
TestCase test;
|
|
test.name = "ImageStoreAndAtomicUseSeparateBindings";
|
|
test.code = std::move(code);
|
|
test.expected = {5};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_STORE, O::IMAGE_ATOMIC_ADD,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.user_data = user_data;
|
|
test.has_user_data = true;
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui = std::move(expected_image);
|
|
test.required_spirv = {"OpCapability StorageImageWriteWithoutFormat",
|
|
"OpImageWrite",
|
|
"OpImageTexelPointer",
|
|
"R32ui",
|
|
StorageUint2DImageBindingName(false),
|
|
StorageUint2DImageBindingName(true)};
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageAtomicVariants() {
|
|
using O = ShaderOpcode;
|
|
|
|
const u32 initial[] = {10, 10, 0xf0f0u, 0xf000u, 0xf00fu};
|
|
const u32 values[] = {5, 5, 0x0ff0u, 0x0f00u, 0x00ffu};
|
|
const u32 ops[] = {0x11, 0x15, 0x18, 0x19, 0x1a};
|
|
|
|
std::vector<u32> code;
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(values)); i++) {
|
|
AppendVMovU32(&code, 20, i & 3u);
|
|
AppendVMovU32(&code, 21, i >> 2u);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovLiteral(&code, 0, values[i]);
|
|
code.push_back(EncodeMimg0(ops[i], 0x1, 0, true));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, i);
|
|
}
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageAtomicVariants";
|
|
test.code = code;
|
|
test.expected = {10, 10, 0xf0f0u, 0xf000u, 0xf00fu};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_ATOMIC_ADD,
|
|
O::IMAGE_ATOMIC_UMIN, O::IMAGE_ATOMIC_AND,
|
|
O::IMAGE_ATOMIC_OR, O::IMAGE_ATOMIC_XOR,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.required_spirv = {"OpImageTexelPointer", "R32ui",
|
|
StorageUint2DImageBindingName(true)};
|
|
test.forbidden_spirv = {"OpTypeSampler", "OpTypeSampledImage",
|
|
"StorageImageReadWithoutFormat",
|
|
"StorageImageWriteWithoutFormat",
|
|
StorageUint2DImageBindingName(false)};
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
for (u32 i = 0; i < static_cast<u32>(std::size(initial)); i++) {
|
|
test.storage_image_r32ui[i] = initial[i];
|
|
}
|
|
test.expected_storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui[0] = 15;
|
|
test.expected_storage_image_r32ui[1] = 5;
|
|
test.expected_storage_image_r32ui[2] = 0x00f0u;
|
|
test.expected_storage_image_r32ui[3] = 0xff00u;
|
|
test.expected_storage_image_r32ui[4] = 0xf0f0u;
|
|
return test;
|
|
}
|
|
|
|
TestCase ImageAtomicGlc0DoesNotReturnOldValue() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovU32(&code, 20, 0);
|
|
AppendVMovU32(&code, 21, 0);
|
|
AppendVMovU32(&code, 22, 0);
|
|
AppendVMovU32(&code, 0, 5);
|
|
code.push_back(EncodeMimg0(0x11, 0x1));
|
|
code.push_back(EncodeMimg1(0, 20));
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "ImageAtomicGlc0DoesNotReturnOldValue";
|
|
test.code = code;
|
|
test.expected = {5};
|
|
test.opcodes = {O::V_MOV_B32, O::IMAGE_ATOMIC_ADD, O::BUFFER_STORE_DWORD,
|
|
O::S_ENDPGM};
|
|
test.storage_image_rgba = MakeRgbaImage(4, 4);
|
|
test.storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.storage_image_r32ui[0] = 10;
|
|
test.expected_storage_image_r32ui = std::vector<u32>(16, 0);
|
|
test.expected_storage_image_r32ui[0] = 15;
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsInterpolationExport() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVintrp(0x00, 0, 0, 0, 0));
|
|
code.push_back(EncodeVintrp(0x01, 0, 0, 0, 0));
|
|
code.push_back(EncodeVintrp(0x02, 1, 0, 1, 2));
|
|
AppendVMovLiteral(&code, 2, 0x3f400000u);
|
|
AppendVMovLiteral(&code, 3, 0x3f800000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsInterpolationExport",
|
|
code,
|
|
{0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u},
|
|
{O::V_INTERP_P1_F32, O::V_INTERP_P2_F32, O::V_INTERP_MOV_F32,
|
|
O::V_MOV_B32, O::EXP, O::S_ENDPGM}};
|
|
}
|
|
|
|
GraphicsCase GraphicsPositionWExport() {
|
|
GraphicsCase test;
|
|
test.name = "GraphicsPositionWExport";
|
|
test.pixel_position_w = true;
|
|
test.vertex_clip_w = 4.0f;
|
|
// POS_W occupies v2; the rasterizer supplies FragCoord.w=1/4 for this triangle.
|
|
test.fragment_code = {EncodeExp0(0x00, 0xf), EncodeExp1(2, 2, 2, 2)};
|
|
AppendEnd(&test.fragment_code);
|
|
test.expected_pixel = {0x40800000u, 0x40800000u, 0x40800000u, 0x40800000u};
|
|
test.opcodes = {ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsAncillaryLayer(bool front_face) {
|
|
GraphicsCase test;
|
|
test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer";
|
|
test.pixel_ancillary = true;
|
|
test.pixel_front_face = front_face;
|
|
test.layers = 8;
|
|
const auto ancillary = Vgpr(front_face ? 3 : 2);
|
|
test.fragment_code.push_back(EncodeSop1(0x04, 8, 126));
|
|
test.fragment_code.push_back(EncodeSop1(0x0a, 126, 126));
|
|
AppendVop3(&test.fragment_code, 0x148, 4, ancillary, InlineU32(16), InlineU32(11));
|
|
AppendVop3(&test.fragment_code, 0x149, 5, ancillary, InlineU32(16), InlineU32(3));
|
|
test.fragment_code.push_back(EncodeSop1(0x04, 126, 8));
|
|
AppendVMovU32(&test.fragment_code, 6, test.layers);
|
|
test.fragment_code.push_back(EncodeVopc(0xc1, Vgpr(4), 6)); // Dynamic layer < count.
|
|
test.fragment_code.push_back(EncodeSop1(0x04, 126, 106));
|
|
test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4))); // float(unsigned layer)
|
|
test.fragment_code.push_back(EncodeVop1(0x05, 1, Vgpr(5))); // float(signed low 3 bits)
|
|
AppendVMovLiteral(&test.fragment_code, 2, 0);
|
|
AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u);
|
|
test.fragment_code.push_back(EncodeExp0(0x00, 0xf));
|
|
test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
AppendEnd(&test.fragment_code);
|
|
test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_BFE_I32,
|
|
ShaderOpcode::V_CVT_F32_U32, ShaderOpcode::V_CVT_F32_I32,
|
|
ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64,
|
|
ShaderOpcode::S_WQM_B64, ShaderOpcode::V_CMP_LT_U32,
|
|
ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM};
|
|
for (u32 layer = 0; layer < test.layers; ++layer) {
|
|
const auto signed_bits = layer < 4 ? static_cast<int>(layer) : static_cast<int>(layer) - 8;
|
|
test.expected_pixel.insert(test.expected_pixel.end(),
|
|
{std::bit_cast<u32>(static_cast<float>(layer)),
|
|
std::bit_cast<u32>(static_cast<float>(signed_bits)), 0, 0x3f800000u});
|
|
}
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsAncillarySampleId() {
|
|
GraphicsCase test;
|
|
test.name = "GraphicsAncillarySampleId";
|
|
test.pixel_ancillary = true;
|
|
test.samples = vk::SampleCountFlagBits::e4;
|
|
AppendVop3(&test.fragment_code, 0x148, 4, Vgpr(2), InlineU32(8), InlineU32(4));
|
|
AppendVop3(&test.fragment_code, 0x149, 5, Vgpr(2), InlineU32(8), InlineU32(2));
|
|
AppendVop3(&test.fragment_code, 0x148, 6, Vgpr(2), InlineU32(9), InlineU32(1));
|
|
test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4)));
|
|
test.fragment_code.push_back(EncodeVop1(0x05, 1, Vgpr(5)));
|
|
test.fragment_code.push_back(EncodeVop1(0x06, 2, Vgpr(6)));
|
|
AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u);
|
|
test.fragment_code.push_back(EncodeExp0(0x00, 0xf));
|
|
test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
AppendEnd(&test.fragment_code);
|
|
test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_BFE_I32,
|
|
ShaderOpcode::V_CVT_F32_U32, ShaderOpcode::V_CVT_F32_I32,
|
|
ShaderOpcode::V_MOV_B32, ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM};
|
|
// Average samples 0..3, their signed low two bits, and their second bit.
|
|
test.expected_pixel = {0x3fc00000u, 0xbf000000u, 0x3f000000u, 0x3f800000u};
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsFlatInterpolatorExport() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVintrp(0x02, 0, 0, 0, 2));
|
|
AppendVMovLiteral(&code, 1, 0x00000000u);
|
|
AppendVMovLiteral(&code, 2, 0x00000000u);
|
|
AppendVMovLiteral(&code, 3, 0x3f800000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
AppendEnd(&code);
|
|
|
|
GraphicsCase test;
|
|
test.name = "GraphicsFlatInterpolatorExport";
|
|
test.fragment_code = code;
|
|
test.expected_pixel = {0x3e800000u, 0x00000000u, 0x00000000u, 0x3f800000u};
|
|
test.opcodes = {O::V_INTERP_MOV_F32, O::V_MOV_B32, O::EXP, O::S_ENDPGM};
|
|
test.pixel_interpolator_settings = {0x00000400u};
|
|
test.vertices = {
|
|
0xbf800000u, 0xbf800000u, 0x3e800000u, 0x00000000u, 0x00000000u,
|
|
0x3f800000u, 0x40400000u, 0xbf800000u, 0x3f400000u, 0x00000000u,
|
|
0x00000000u, 0x3f800000u, 0xbf800000u, 0x40400000u, 0x3e800000u,
|
|
0x00000000u, 0x00000000u, 0x3f800000u,
|
|
};
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsDsAddtidScratchExport() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(124, InlineU32(0)));
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u);
|
|
code.push_back(EncodeDs0(0xb0, 0));
|
|
code.push_back(EncodeDs1(0, 0, 0));
|
|
code.push_back(EncodeDs0(0xb1, 0));
|
|
code.push_back(EncodeDs1(4, 0, 0));
|
|
code.push_back(EncodeExp0(0x00, 0xf));
|
|
code.push_back(EncodeExp1(4, 4, 4, 4));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsDsAddtidScratchExport",
|
|
code,
|
|
{0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u},
|
|
{O::S_MOV_B32, O::V_MOV_B32, O::DS_WRITE_ADDTID_B32,
|
|
O::DS_READ_ADDTID_B32, O::EXP, O::S_ENDPGM}};
|
|
}
|
|
|
|
GraphicsCase GraphicsDirectSgprPushConstantExport() {
|
|
using O = ShaderOpcode;
|
|
|
|
const std::vector<u32> values = {0x3e800000u, 0x3f000000u, 0x3f400000u,
|
|
0x3f800000u};
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeVop1(0x01, 0, 0));
|
|
code.push_back(EncodeVop1(0x01, 1, 1));
|
|
code.push_back(EncodeVop1(0x01, 2, 2));
|
|
code.push_back(EncodeVop1(0x01, 3, 3));
|
|
code.push_back(EncodeExp0(0x00, 0xf));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
AppendEnd(&code);
|
|
|
|
GraphicsCase test;
|
|
test.name = "GraphicsDirectSgprPushConstantExport";
|
|
test.fragment_code = code;
|
|
test.expected_pixel = values;
|
|
test.opcodes = {O::V_MOV_B32, O::EXP, O::S_ENDPGM};
|
|
|
|
for (size_t i = 0; i < values.size(); i++) {
|
|
test.user_data[i] = values[i];
|
|
}
|
|
test.has_user_data = true;
|
|
test.push_constants = values;
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsInlineSrtScalarPromotionExport() {
|
|
using O = ShaderOpcode;
|
|
|
|
const std::vector<u32> values = {0x00000000u, 0x00000000u, 0x00000000u,
|
|
0x3f800000u};
|
|
|
|
std::vector<u32> code;
|
|
AppendVop3(&code, 0x12fu, 0, 0, 1);
|
|
AppendVop3(&code, 0x12fu, 1, 2, 3);
|
|
code.push_back(EncodeExp0(0x00, 0xf, true, true, true));
|
|
code.push_back(EncodeExp1(0, 1, 0, 0));
|
|
AppendEnd(&code);
|
|
|
|
GraphicsCase test;
|
|
test.name = "GraphicsInlineSrtScalarPromotionExport";
|
|
test.fragment_code = code;
|
|
test.expected_pixel = values;
|
|
test.opcodes = {O::V_CVT_PKRTZ_F16_F32, O::EXP, O::S_ENDPGM};
|
|
|
|
for (size_t i = 0; i < values.size(); i++) {
|
|
test.user_data[i] = values[i];
|
|
}
|
|
test.has_user_data = true;
|
|
test.push_constants = values;
|
|
return test;
|
|
}
|
|
|
|
GraphicsCase GraphicsNullVmExportDiscardsInactiveExec() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f400000u);
|
|
AppendVMovLiteral(&code, 2, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 3, 0x40000000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf, false));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeExp0(0x09, 0x0, true, false, true));
|
|
code.push_back(EncodeExp1(0, 0, 0, 0));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsNullVmExportDiscardsInactiveExec",
|
|
code,
|
|
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
|
{O::V_MOV_B32, O::EXP, O::S_MOV_B64, O::S_ENDPGM}};
|
|
}
|
|
|
|
GraphicsCase GraphicsMrt0OffVmExportDiscardsInactiveExec() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f400000u);
|
|
AppendVMovLiteral(&code, 2, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 3, 0x40000000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf, false));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeExp0(0x00, 0x0, true, true, true));
|
|
code.push_back(EncodeExp1(0, 0, 0, 0));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsMrt0OffVmExportDiscardsInactiveExec",
|
|
code,
|
|
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
|
{O::V_MOV_B32, O::EXP, O::S_MOV_B64, O::S_ENDPGM}};
|
|
}
|
|
|
|
GraphicsCase GraphicsFinalVmExportSupersedesEarlierVmMask() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f400000u);
|
|
AppendVMovLiteral(&code, 2, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 3, 0x40000000u);
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeExp0(0x00, 0xf, false, false, true));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
code.push_back(EncodeSMovB32(126, 193u));
|
|
code.push_back(EncodeSMovB32(127, 193u));
|
|
AppendVMovLiteral(&code, 4, 0x40400000u);
|
|
AppendVMovLiteral(&code, 5, 0x40800000u);
|
|
AppendVMovLiteral(&code, 6, 0x40a00000u);
|
|
AppendVMovLiteral(&code, 7, 0x40c00000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf, true, false, true));
|
|
code.push_back(EncodeExp1(4, 5, 6, 7));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsFinalVmExportSupersedesEarlierVmMask",
|
|
code,
|
|
{0x40400000u, 0x40800000u, 0x40a00000u, 0x40c00000u},
|
|
{O::V_MOV_B32, O::S_MOV_B64, O::EXP, O::S_MOV_B32, O::S_ENDPGM}};
|
|
}
|
|
|
|
GraphicsCase GraphicsBranchPathFinalVmExportDiscardsInactiveExec() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
AppendVMovLiteral(&code, 0, 0x3f000000u);
|
|
AppendVMovLiteral(&code, 1, 0x3f400000u);
|
|
AppendVMovLiteral(&code, 2, 0x3f800000u);
|
|
AppendVMovLiteral(&code, 3, 0x40000000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf, false));
|
|
code.push_back(EncodeExp1(0, 1, 2, 3));
|
|
code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
|
|
code.push_back(EncodeExp0(0x09, 0x0, true, false, true));
|
|
code.push_back(EncodeExp1(0, 0, 0, 0));
|
|
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(1)));
|
|
const auto branch_index = code.size();
|
|
code.push_back(0);
|
|
AppendEnd(&code);
|
|
const auto later_export_index = code.size();
|
|
code[branch_index] = EncodeSopp(
|
|
0x05, static_cast<u32>(later_export_index - branch_index - 1u));
|
|
code.push_back(EncodeSMovB32(126, 193u));
|
|
code.push_back(EncodeSMovB32(127, 193u));
|
|
AppendVMovLiteral(&code, 4, 0x40400000u);
|
|
AppendVMovLiteral(&code, 5, 0x40800000u);
|
|
AppendVMovLiteral(&code, 6, 0x40a00000u);
|
|
AppendVMovLiteral(&code, 7, 0x40c00000u);
|
|
code.push_back(EncodeExp0(0x00, 0xf, true, false, true));
|
|
code.push_back(EncodeExp1(4, 5, 6, 7));
|
|
AppendEnd(&code);
|
|
|
|
return {"GraphicsBranchPathFinalVmExportDiscardsInactiveExec",
|
|
code,
|
|
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
|
|
{O::V_MOV_B32, O::EXP, O::S_MOV_B64, O::S_CMP_EQ_U32,
|
|
O::S_CBRANCH_SCC1, O::S_MOV_B32, O::S_ENDPGM}};
|
|
}
|
|
|
|
TestCase MultipleWorkitemsGlobalId() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code = {
|
|
EncodeVop1(0x01, 1, 0),
|
|
EncodeVop2(0x1a, 1, InlineU32(2), 1),
|
|
EncodeVop2(0x25, 1, Vgpr(0), 1),
|
|
EncodeVop2(0x25, 2, InlineU32(64), 1),
|
|
EncodeVop2(0x1a, 3, InlineU32(2), 1),
|
|
};
|
|
AppendBufferStoreDword(&code, 2, 3);
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "MultipleWorkitemsGlobalId";
|
|
test.code = code;
|
|
test.expected = {64, 65, 66, 67, 68, 69, 70, 71};
|
|
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
|
|
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
|
test.compute_info.threads_num[0] = 4;
|
|
test.compute_info.threads_num[1] = 1;
|
|
test.compute_info.threads_num[2] = 1;
|
|
test.compute_info.group_id[0] = true;
|
|
test.compute_info.thread_ids_num = 1;
|
|
test.compute_info.workgroup_register = 0;
|
|
test.has_compute_info = true;
|
|
test.dispatch_x = 2;
|
|
return test;
|
|
}
|
|
|
|
TestCase DispatcherIrreducibleControlFlow() {
|
|
using O = ShaderOpcode;
|
|
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSopp(0x05, 5)); // entry -> B, fallthrough A
|
|
AppendVMovU32(&code, 0, 7);
|
|
AppendStoreVgpr(&code, 0, 0);
|
|
code.push_back(EncodeSopp(0x04, 1)); // A exits while SCC is initially zero
|
|
code.push_back(EncodeSopp(0x02, 0xfffau)); // B -> A
|
|
AppendEnd(&code);
|
|
|
|
TestCase test;
|
|
test.name = "DispatcherIrreducibleControlFlow";
|
|
test.code = code;
|
|
test.expected = {7};
|
|
test.opcodes = {O::S_CBRANCH_SCC1, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
|
|
O::S_CBRANCH_SCC0, O::S_BRANCH, O::S_ENDPGM};
|
|
return test;
|
|
}
|
|
|
|
std::vector<TestCase> MakeCases() {
|
|
std::vector<TestCase> cases;
|
|
cases.reserve(128);
|
|
auto AddCase = [&cases](TestCase (*factory)()) {
|
|
cases.push_back(factory());
|
|
};
|
|
|
|
AddCase(IntegerAddSubMul);
|
|
AddCase(BitwiseOps);
|
|
AddCase(Shifts);
|
|
AddCase(ExactPushConstantExtent);
|
|
AddCase(ScalarShiftCountsMaskLowBits);
|
|
AddCase(Rdna2ScalarOpcodes);
|
|
AddCase(ScalarTrapDisabledFallsThroughExactRaw);
|
|
AddCase(ScalarWaitcntDepctrCapturedVmVsrc);
|
|
AddCase(ScalarExtendedArithmetic);
|
|
AddCase(ScalarArithmeticSccCarryBorrowOverflow);
|
|
AddCase(ScalarMinMaxSccComparisonEdges);
|
|
AddCase(ScalarAbsI32UpdatesScc);
|
|
AddCase(ScalarAbsdiffBitset64AndSetprio);
|
|
AddCase(ScalarShiftLeftAddSccCarryEdges);
|
|
AddCase(ScalarCompareOps);
|
|
AddCase(ScalarShiftAddAndMaskOps);
|
|
AddCase(ScalarNotB64UpdatesScc);
|
|
AddCase(ScalarQuadmaskB64);
|
|
AddCase(ScalarFlbitI32B64Gpu);
|
|
AddCase(ScalarFlbitI32B32Gpu);
|
|
AddCase(ScalarSaveExecOps);
|
|
AddCase(ScalarOrn2SaveexecUsesSourceOrNotExec);
|
|
cases.push_back(ScalarOrn2SaveexecB32(32, 32));
|
|
cases.push_back(ScalarOrn2SaveexecB32(64, 64));
|
|
cases.push_back(ScalarOrn2SaveexecB32(32, 4));
|
|
cases.push_back(ScalarSubvectorLoops(32));
|
|
cases.push_back(ScalarSubvectorLoops(64));
|
|
AddCase(ScalarGetpcWritesNextInstructionPc);
|
|
AddCase(ScalarBitfieldPack);
|
|
AddCase(ScalarBrevB32PreservesScc);
|
|
AddCase(ScalarBfeI32CapturedRawSignExtends);
|
|
AddCase(BitfieldExtractWidthPastEndEdges);
|
|
AddCase(Scalar64BitOps);
|
|
AddCase(ScalarConditionalMoveB64);
|
|
AddCase(ScalarConditionalMoveB64PreservesMasks);
|
|
AddCase(ScalarAndn2B64SccUsesMaskShadow);
|
|
AddCase(ScalarMaskHighWriteInvalidatesProvenance);
|
|
AddCase(ScalarSelectB64PreservesMaskProvenance);
|
|
AddCase(ScalarWqmB64SelectsSccDomain);
|
|
AddCase(ScalarWqmB64PreservesPartialMasks);
|
|
cases.push_back(ScalarWqmB32Masks(32));
|
|
cases.push_back(ScalarWqmB32Masks(64));
|
|
AddCase(ScalarMaskProvenanceOverlapAndMixedBinary);
|
|
AddCase(ScalarLiteral);
|
|
AddCase(VectorMoves);
|
|
AddCase(VectorVop3MoveAppliesFloatSourceModifiers);
|
|
AddCase(VectorIntegerOps);
|
|
AddCase(VectorFfbhI32NativeAndVop3OnGpu);
|
|
AddCase(Vop1SdwaFfblCapturedHighWordSource);
|
|
AddCase(Vop1SdwaNotCapturedByte0Source);
|
|
AddCase(Vop2SdwaSubNcExactByte2Destination);
|
|
AddCase(Vop2SdwaAddNcCapturedHighWordDestination);
|
|
AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends);
|
|
AddCase(Vop2SdwaLshrrevCapturedByte1Source);
|
|
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
|
|
AddCase(Vop3CvtPkI16I32Captured);
|
|
AddCase(Vop3Med3I16Captured);
|
|
AddCase(Vop2SdwaMinU32PreservesWordDestination);
|
|
AddCase(VectorShiftCountsMaskLowBits);
|
|
AddCase(VectorVop3LshrrevB64Captured);
|
|
AddCase(VectorVop3LshlrevB64Captured);
|
|
AddCase(VectorVop3IntegerOps);
|
|
AddCase(VectorBfeI32SignExtendsField);
|
|
AddCase(VectorAlignByteUsesFiveBitByteOffset);
|
|
AddCase(VectorCarryAndBitCountOps);
|
|
AddCase(VectorMbcntUsesThreadMask);
|
|
AddCase(VectorAddcWritesPerLaneCarryOut);
|
|
AddCase(VectorAddcUsesPerLaneCarryIn);
|
|
AddCase(VectorSubCoCiU32CompactAndVop3);
|
|
AddCase(VectorSubrevCoCiU32ExactRawOnGpu);
|
|
AddCase(VectorVop3BSubrevCoCiUsesEncodedMasks);
|
|
AddCase(VectorVop3BCarryOutWritesSgprMask);
|
|
AddCase(VectorVop3BCarryOutUsesEncodedSdst);
|
|
AddCase(VectorVop3BSubCoU32UsesRdna2Opcode310);
|
|
AddCase(VectorMadU64U32UnsignedCarryOut);
|
|
AddCase(VectorLaneAndPackedOps);
|
|
AddCase(Vop2PkFmacF16AccumulatesPackedHalvesIndependently);
|
|
AddCase(Vop2PkFmacF16DppNegatesBothPackedHalves);
|
|
AddCase(Vop3pOpselHiUsesArchitecturalSourceBits);
|
|
AddCase(CvtPkU8F32PacksSelectedByte);
|
|
AddCase(CvtPkrtzF16F32SubnormalRoundsTowardZero);
|
|
AddCase(CvtPkrtzF16F32SdwaAndOutputModifiers);
|
|
AddCase(PackedMinMaxF16NanAndSignedZeroEdges);
|
|
AddCase(VectorMinMaxF16Ops);
|
|
AddCase(VectorCvtU16F16Sdwa);
|
|
AddCase(NativeAndSdwa16BitDestinationWrites);
|
|
AddCase(VectorMinMaxMed3F16Ops);
|
|
AddCase(VectorSpecialF16Ops);
|
|
AddCase(VectorCosF16CapturedSdwaAndEdges);
|
|
AddCase(VectorSinF16SdwaAndEdges);
|
|
AddCase(VectorWritelaneIgnoresExecMask);
|
|
AddCase(VectorReadlaneFromInactiveWrittenLane);
|
|
AddCase(VectorLaneWave32RuntimeSelectorWraps);
|
|
AddCase(VectorPermlanex16);
|
|
AddCase(VectorPermlane16FetchInactiveZero);
|
|
AddCase(VectorPermlane16FetchInactiveFi);
|
|
AddCase(VectorDppQuadPermuteReverse);
|
|
AddCase(VectorDppRowXmask);
|
|
AddCase(VectorDppBankMaskPreservesDestination);
|
|
AddCase(VectorDppBoundsControlZeroPreservesDestination);
|
|
AddCase(Vop3FmacF32NegatedSourceAccumulates);
|
|
AddCase(Vop3LdexpSourceModifier);
|
|
AddCase(Vop1MoveRelSource);
|
|
AddCase(Vop1MoveRelDestination);
|
|
AddCase(VectorFloatSpecialOps);
|
|
AddCase(MadMixF16LiteralHalfSourceUsesOpsel);
|
|
AddCase(MadMixF16NegHiIsAbsAndNegIsIndependent);
|
|
AddCase(VectorVop3FmaF16UsesRdna2Opcode34b);
|
|
AddCase(FloatInlineConstantF16UsesNumericValue);
|
|
AddCase(VectorFloatControlContractPreservesInfNan);
|
|
AddCase(VectorFloatArithmeticOps);
|
|
AddCase(VectorMinMaxF32NanAndSignedZeroEdges);
|
|
AddCase(VectorMed3F32NanUsesMin3Path);
|
|
AddCase(VectorFloatConversionOps);
|
|
AddCase(VectorFrexpF32Edges);
|
|
AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange);
|
|
AddCase(VectorSpecialF32FlushesDenormalInputs);
|
|
AddCase(VectorRcpIflagF32IntegerReciprocal);
|
|
AddCase(VectorSinCosMaxFiniteSpecialCases);
|
|
AddCase(VectorCompareOps);
|
|
AddCase(VectorVop3CompareEqI64OnGpu);
|
|
AddCase(VectorVop3CompareEqU64OnGpu);
|
|
AddCase(VectorVop3CompareGtU64OnGpu);
|
|
AddCase(VectorVopcCompareLtU64OnGpu);
|
|
AddCase(VectorVop3CompareNeU64OnGpu);
|
|
AddCase(VectorVopcCmpxNeU64CapturedExecMask);
|
|
AddCase(VectorVop3CmpxNeI64CapturedExecMask);
|
|
AddCase(VectorCompareClassF32);
|
|
AddCase(VectorVopcSdwaCmpxClassF32CapturedExecMask);
|
|
AddCase(VectorCompareF16Ops);
|
|
AddCase(Wave32VccMasksPreserveOtherHalf);
|
|
AddCase(Vop2SdwaCndmaskSourceModifier);
|
|
AddCase(Vop2SdwaCndmaskCapturedByte3Source);
|
|
AddCase(Vop3CndmaskUsesSgprMaskLaneBits);
|
|
AddCase(Vop3CndmaskAllowsDataSourceModifier);
|
|
AddCase(VectorCompareExecOps);
|
|
AddCase(VectorVop3FloatCompareNegSourceModifier);
|
|
AddCase(VectorVop3CmpxWritesExecMask);
|
|
AddCase(VectorVopcSdwaCmpxWritesExecMask);
|
|
AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask);
|
|
AddCase(VectorVopcCmpxNgtF16CapturedSdwaExecMask);
|
|
AddCase(VectorCompareInvertedMaskSelect);
|
|
AddCase(BranchSelect);
|
|
AddCase(SimpleLoop);
|
|
AddCase(SharedReturnKeepsSelectedValues);
|
|
AddCase(BranchVccnzUsesWaveMask);
|
|
AddCase(BranchVccnzUsesCarryProducedWaveMask);
|
|
AddCase(ScalarMemoryLoadVariants);
|
|
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
|
|
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
|
|
AddCase(BufferLoadStore);
|
|
AddCase(BufferLoadDwordOffenIdxenUsesVaddrPlusOneOffset);
|
|
AddCase(BufferStoreDwordOffenIdxenUsesVaddrPlusOneOffset);
|
|
AddCase(BufferLoadDwordNoAddressFlagsIgnoresVaddr);
|
|
AddCase(BufferLoadDwordIdxenUsesDescriptorStride);
|
|
AddCase(BufferStoreDwordIdxenUsesDescriptorStride);
|
|
AddCase(BufferStoreDwordAppliesHostOffset);
|
|
AddCase(BufferOffsetsUsePackedLaneAndStorageFallback);
|
|
AddCase(BufferLoadVariants);
|
|
AddCase(BufferLoadDwordx2SnapshotsOverlappingAddress);
|
|
AddCase(BufferLoadDwordx3SnapshotsOverlappingAddress);
|
|
AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress);
|
|
AddCase(BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail);
|
|
AddCase(BufferStoreDwordx4DropsOnlyOutOfBoundsTail);
|
|
AddCase(BufferLoadFormatXyzwRejectsPartialRecord);
|
|
AddCase(BufferStoreFormatXyzwDropsPartialRecord);
|
|
AddCase(BufferLoadFormatXChecksOnlyTransferredComponent);
|
|
AddCase(BufferStoreFormatXChecksOnlyTransferredComponent);
|
|
AddCase(BufferLoadFormatXyChecksOnlyTransferredComponents);
|
|
AddCase(BufferStoreFormatXyChecksOnlyTransferredComponents);
|
|
AddCase(BufferStoreVariants);
|
|
AddCase(BufferFormatVariants);
|
|
AddCase(BufferLoadFormatXyzwSnapshotsOverlappingAddress);
|
|
AddCase(BufferLoadFormatXyzwResource32_32FloatAppliesXy01);
|
|
AddCase(BufferLoadFormatXyzwResource323232FloatRepeatsSource);
|
|
AddCase(BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1);
|
|
AddCase(BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress);
|
|
AddCase(BufferFormatStoreVariants);
|
|
AddCase(BufferStoreFormatXResource16UintWritesHalfword);
|
|
AddCase(BufferStoreFormatXResource16UintPreservesAdjacentLanes);
|
|
AddCase(BufferLoadFormatXResource8UintZeroExtendsByte);
|
|
AddCase(BufferLoadFormatXyResource88UintExtractsBytes);
|
|
AddCase(BufferLoadFormatXyResource8888UnormConvertsFirstTwoComponents);
|
|
AddCase(BufferStoreFormatXyResource88UintWritesBytes);
|
|
AddCase(BufferStoreFormatXyzResource3232UintWritesTwoDwords);
|
|
AddCase(BufferStoreFormatXyzwResource323232UintWritesThreeDwords);
|
|
AddCase(BufferStoreFormatXyResource32UintWritesOneDword);
|
|
AddCase(BufferStoreFormatXyzResource8UintWritesOneByte);
|
|
AddCase(BufferStoreFormatXAddTidUsesLaneIndex);
|
|
AddCase(BufferStoreFormatXDropsOutOfRangeRecord);
|
|
AddCase(TBufferLoadVariants);
|
|
AddCase(TBufferLoadFormatXyzwSnapshotsOverlappingAddress);
|
|
AddCase(TBufferLoadFormatXyzwPackedSnapshotsOverlappingAddress);
|
|
AddCase(TBufferLoadFormatX8UintZeroExtendsByte);
|
|
AddCase(TBufferLoadFormatX8888UintExtractsFirstByte);
|
|
AddCase(TBufferLoadFormatXIdxenUsesDescriptorStride);
|
|
AddCase(TBufferLoadFormatX16FloatConvertsToFloat);
|
|
AddCase(TBufferStoreFormatX8UintWritesOneByte);
|
|
AddCase(TBufferStoreFormatXSintWritesSubDword);
|
|
AddCase(TBufferLoadFormatXSintSignExtendsSubDword);
|
|
AddCase(TBufferLoadFormatXy1616IntegerComponents);
|
|
AddCase(TBufferStoreFormatXy1616IntegerComponents);
|
|
AddCase(TBufferLoadFormatXyz16161616UintLoadsHalfwords);
|
|
AddCase(TBufferLoadFormatXy1616UnormConvertsToFloat);
|
|
AddCase(TBufferLoadFormatXy88UnormConvertsToFloat);
|
|
AddCase(TBufferLoadFormatXy88SnormConvertsToFloat);
|
|
AddCase(TBufferLoadFormatXy8888UnormConvertsFirstTwoComponents);
|
|
AddCase(TBufferLoadFormatXyzw8888UintExtractsBytes);
|
|
AddCase(TBufferLoadFormatXyzw1010102SnormConvertsToFloat);
|
|
AddCase(TBufferLoadFormatXyz111110FloatUnpacks);
|
|
AddCase(TBufferLoadFormatXyzw3232FloatZerosMissingComponents);
|
|
AddCase(TBufferStoreFormatXyzw3232FloatWritesOnlyPresentComponents);
|
|
AddCase(TBufferStoreFormatXy88IntegerComponents);
|
|
AddCase(TBufferLoadFormatXy88IntegerComponents);
|
|
AddCase(TBufferStoreVariants);
|
|
AddCase(FlatLoadVariants);
|
|
AddCase(FlatSubdwordLoadsApplyByteOffset);
|
|
AddCase(FlatVirtualAddressRebasesGuestAllocation);
|
|
AddCase(GlobalSignedImmediateRebasesBeforeSaddr);
|
|
AddCase(FlatSegmentIgnoresSaddrAndMasksOffsetMsb);
|
|
AddCase(ScratchIsPrivatePerInvocation);
|
|
AddCase(DsReadWriteVariants);
|
|
AddCase(DsWriteB16D16HiCapturedUsesHighHalf);
|
|
AddCase(DsReadU16D16CapturedPreservesHighHalf);
|
|
AddCase(DsReadU16D16HiCapturedPreservesLowHalf);
|
|
AddCase(DsAppendConsumeUsesEncodedLdsSelector);
|
|
AddCase(DsAppendUsesEncodedGdsSelector);
|
|
AddCase(DsGdsSubdwordAndAtomicWrites);
|
|
AddCase(DsReadWrite2Variants);
|
|
AddCase(DsWideReadSnapshotsOverlappingAddress);
|
|
AddCase(DsReadWrite2EqualOffsetsUseData0);
|
|
AddCase(DsWideLdsPartialBounds);
|
|
AddCase(DsWideGdsPartialBounds);
|
|
AddCase(DsAtomicNoReturnVariants);
|
|
AddCase(DsAtomicReturnVariants);
|
|
for (bool decrement : {false, true}) {
|
|
for (bool gds : {false, true}) {
|
|
cases.push_back(DsBoundedAtomicBoundaries(decrement, gds));
|
|
for (u32 wave_size : {32u, 64u}) {
|
|
cases.push_back(DsBoundedAtomicContention(decrement, gds, wave_size));
|
|
}
|
|
}
|
|
}
|
|
AddCase(DsMiscVariants);
|
|
AddCase(DsFloatMinMaxUsesSeparateCompareOperand);
|
|
AddCase(DsSwizzleInvalidSourceLaneZero);
|
|
AddCase(DsBpermuteCapturedExecOffsetAndWrap);
|
|
AddCase(DsBpermuteWave64UsesIndependentHalves);
|
|
AddCase(Wave64CrossHalfLaneAndLds);
|
|
AddCase(Wave64RawMasksAndScalarBranch);
|
|
AddCase(Wave64PartialMultidimensionalWorkgroup);
|
|
AddCase(Wave64AppendConsumeHighHalf);
|
|
AddCase(BufferAtomicVariants);
|
|
AddCase(BufferAtomicCmpSwapExactRaw);
|
|
AddCase(BufferAtomicGlc0DoesNotReturnOldValue);
|
|
AddCase(BufferAtomicFMinExactRawGlcModes);
|
|
AddCase(BufferAtomicFMinSpecialValues);
|
|
AddCase(BufferAtomicFMinContendedWorkgroup);
|
|
AddCase(BufferAtomicFMaxExactRawGlcModes);
|
|
AddCase(BufferAtomicFMaxSpecialValues);
|
|
AddCase(BufferAtomicFMaxContendedWorkgroup);
|
|
AddCase(ImageLoadVariants);
|
|
AddCase(ImageLoadR32UintUsesIntegerSampledImage);
|
|
AddCase(ImageLoadFmaskUsesNativeSampleMapping);
|
|
AddCase(ImageLoadR32SintUsesSignedSampledImage);
|
|
AddCase(ImageLoadPackedUintUnpacksAndSwizzles);
|
|
AddCase(ImageSamplePackedUintConvertsSampleAndGather);
|
|
AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs);
|
|
AddCase(ImageLoad1DUsesScalarCoordinate);
|
|
AddCase(ImageGather2DInstructionWith1DDescriptor);
|
|
AddCase(ImageLoad1DArrayUsesLayerCoordinate);
|
|
AddCase(ImageLoad1DArrayDescriptorUsesSelectedLayer);
|
|
AddCase(ImageLoadMipUsesVaddr2Lod2D);
|
|
AddCase(ImageLoadMipNsaUsesSelectedAddressVgprs);
|
|
AddCase(ImageLoadA16UintCoordsOnGpu);
|
|
AddCase(ImageGetResinfoDmaskWidthHeight);
|
|
AddCase(ImageGetResinfoDmaskMipLevels);
|
|
AddCase(ImageSampleAndGather);
|
|
AddCase(ImageD16GatherPacksHalfPairs);
|
|
AddCase(ImageSampleA16SamplerCoordsOnGpu);
|
|
AddCase(ImageSampleOpcodeAliasUsesNormalCoords);
|
|
AddCase(ImageSampleA16OffsetKeepsTexelOffset32BitOnGpu);
|
|
AddCase(ImageSampleA16CompareBiasRdna2AddressOrder);
|
|
AddCase(ImageGatherCompareOpcodes);
|
|
AddCase(ImageStoreVariants);
|
|
AddCase(ImageD16StoreUnpacksHalfPairs);
|
|
AddCase(ImageStoreMipSelectsPpsa01340Descriptor);
|
|
AddCase(ImageStoreRgbOneUsesInverseSwizzle);
|
|
AddCase(ImageStoreDuplicateSelectorUsesInverseSwizzle);
|
|
AddCase(ImageStoreBgraUsesInverseSwizzle);
|
|
AddCase(ImageStoreYzwxUsesInverseSwizzle);
|
|
AddCase(ImageStoreR32FloatUsesFormatlessStorageImage);
|
|
AddCase(ImageStoreR32SintUsesRawUintView);
|
|
AddCase(ImageStoreR32UintUsesFormatlessStorageImage);
|
|
AddCase(ImageStorePackedUintSaturatesChannels);
|
|
AddCase(ImageStorePackedUintHonorsSparseDmask);
|
|
AddCase(ComputeTgSizeSgprUsesWaveMetadata);
|
|
AddCase(ImageStoreAndAtomicShareTypedBinding);
|
|
AddCase(ImageAtomicSwapReturnsPreviousTexel);
|
|
AddCase(ImageStoreAndAtomicUseSeparateBindings);
|
|
AddCase(ImageAtomicVariants);
|
|
AddCase(ImageAtomicGlc0DoesNotReturnOldValue);
|
|
AddCase(MultipleWorkitemsGlobalId);
|
|
AddCase(DispatcherIrreducibleControlFlow);
|
|
|
|
return cases;
|
|
}
|
|
|
|
std::vector<GraphicsCase> MakeGraphicsCases() {
|
|
return {
|
|
GraphicsInterpolationExport(),
|
|
GraphicsPositionWExport(),
|
|
GraphicsAncillaryLayer(false),
|
|
GraphicsAncillaryLayer(true),
|
|
GraphicsAncillarySampleId(),
|
|
GraphicsFlatInterpolatorExport(),
|
|
GraphicsDsAddtidScratchExport(),
|
|
GraphicsDirectSgprPushConstantExport(),
|
|
GraphicsInlineSrtScalarPromotionExport(),
|
|
GraphicsNullVmExportDiscardsInactiveExec(),
|
|
GraphicsMrt0OffVmExportDiscardsInactiveExec(),
|
|
GraphicsFinalVmExportSupersedesEarlierVmMask(),
|
|
GraphicsBranchPathFinalVmExportDiscardsInactiveExec(),
|
|
};
|
|
}
|
|
|
|
void CheckPs5GameExampleImageClearRuntimeShape() {
|
|
const auto MakeCode = [] {
|
|
std::vector<u32> code;
|
|
AppendVop3(&code, 0x346u, 4, 8, InlineU32(6), Vgpr(0));
|
|
for (u32 i = 0; i < 4; i++) {
|
|
code.push_back(EncodeVop1(0x01u, i, i + 4u));
|
|
}
|
|
code.push_back(EncodeMubuf0(0x07u, 0, true, false));
|
|
code.push_back(EncodeMubuf1(0, 0, 4));
|
|
AppendEnd(&code);
|
|
return code;
|
|
};
|
|
std::array<u32, 8> user_data{};
|
|
user_data[0] = 0x00010000u;
|
|
user_data[1] = 16u << 16u;
|
|
user_data[2] = 64;
|
|
user_data[3] =
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32UInt) << 12u) |
|
|
4u | (5u << 3u) | (6u << 6u) | (7u << 9u);
|
|
std::fill(user_data.begin() + 4, user_data.end(), 0xff000000u);
|
|
ShaderComputeInputInfo compute{};
|
|
compute.threads_num[0] = 64;
|
|
compute.threads_num[1] = 1;
|
|
compute.threads_num[2] = 1;
|
|
compute.dispatch_threads_num[0] = 64;
|
|
compute.dispatch_threads_num[1] = 1;
|
|
compute.dispatch_threads_num[2] = 1;
|
|
compute.group_id[0] = true;
|
|
compute.dispatch_thread_dimensions = true;
|
|
compute.wave_size = 64;
|
|
compute.thread_ids_num = 1;
|
|
compute.workgroup_register = 8;
|
|
u32 scalar_clear = 0x40404040u;
|
|
bool clean_scalar = false;
|
|
|
|
const auto Compile = [&](const std::vector<u32> &code) {
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = ShaderType::Compute;
|
|
options.wave_size = 64;
|
|
options.user_data_base = 0;
|
|
options.user_data = user_data;
|
|
options.input_info.compute = &compute;
|
|
options.dump_ir = false;
|
|
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
|
auto resource_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
|
.userdata = &scalar_clear,
|
|
.read_specialization_memory = clean_scalar
|
|
? +[](void *data, uint64_t address, uint32_t *word) {
|
|
if (address != reinterpret_cast<uint64_t>(data)) return false;
|
|
*word = *static_cast<uint32_t *>(data);
|
|
return true;
|
|
} : nullptr,
|
|
};
|
|
Require("Ps5GameExampleImageClear", "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
resource_plan, runtime, resources, specialization),
|
|
"runtime clear resources could not be materialized");
|
|
auto result = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
ValidateSpirv("Ps5GameExampleImageClear", result.spirv);
|
|
return resources;
|
|
};
|
|
|
|
const auto code = MakeCode();
|
|
auto positive = Compile(code);
|
|
|
|
compute.stage.resources = positive;
|
|
ShaderBufferResource descriptor{};
|
|
u32 packed_clear = 0;
|
|
uint64_t size = 0;
|
|
Require("Ps5GameExampleImageClear", "runtime shape",
|
|
ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
|
|
packed_clear, size) &&
|
|
descriptor.Base48() == 0x10000u && size == 64u * 16u &&
|
|
packed_clear == 0xff000000u,
|
|
"exact Prospero runtime binding did not resolve to a complete clear");
|
|
|
|
auto wrong_index = code;
|
|
wrong_index[0] = (wrong_index[0] & ~(0x3ffu << 16u)) | (0x347u << 16u);
|
|
compute.stage.resources = Compile(wrong_index);
|
|
Require("Ps5GameExampleImageClear", "add-before-shift address",
|
|
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
|
|
packed_clear, size),
|
|
"V_ADD_LSHL was mistaken for V_LSHL_ADD contiguous coverage");
|
|
|
|
user_data[7] ^= 1u;
|
|
compute.stage.resources = Compile(code);
|
|
Require("Ps5GameExampleImageClear", "non-repeated clear",
|
|
!ResolveComputeBufferFill(compute, 64, 1, 1, 0x61u, descriptor,
|
|
packed_clear, size),
|
|
"non-uniform uint4 data was replaced with a color clear");
|
|
compute.stage.resources = positive;
|
|
compute.dispatch_threads_num[0] = 32;
|
|
Require("Ps5GameExampleImageClear", "partial dispatch",
|
|
!ResolveComputeBufferFill(compute, 32, 1, 1, 0x61u, descriptor,
|
|
packed_clear, size),
|
|
"partial buffer coverage was classified as a complete clear");
|
|
|
|
// Same descriptor/dispatch as GTA3, with the stored value encoded in the
|
|
// shader.
|
|
user_data = {0x00200000u, 4u << 16u, 64u, 0x14204u};
|
|
compute.workgroup_register = 4;
|
|
compute.dispatch_thread_dimensions = false;
|
|
std::vector<u32> scalar_code;
|
|
AppendVop3(&scalar_code, 0x346u, 0, 4, InlineU32(6), Vgpr(0));
|
|
scalar_code.push_back(EncodeVop1(0x01u, 1, InlineU32(0)));
|
|
scalar_code.push_back(EncodeMubuf0(0x04u, 0, true, false));
|
|
scalar_code.push_back(EncodeMubuf1(1, 0, 0));
|
|
AppendEnd(&scalar_code);
|
|
auto scalar = Compile(scalar_code);
|
|
compute.stage.resources = scalar;
|
|
Require("Ps5GameExampleImageClear", "inline scalar fill",
|
|
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size) &&
|
|
descriptor.Base48() == 0x200000u && size == 256u &&
|
|
packed_clear == 0u,
|
|
"GTA3's actual scalar store was not recognized from IR");
|
|
Require("Ps5GameExampleImageClear", "excess dispatch",
|
|
!ResolveComputeBufferFill(compute, 2, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size),
|
|
"excess invocation coverage was accepted as a fill");
|
|
auto alias = scalar.buffers.front();
|
|
compute.stage.resources.buffers.push_back(alias);
|
|
Require("Ps5GameExampleImageClear", "aliased scalar input",
|
|
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size),
|
|
"a scalar read aliasing the destination was accepted as uniform");
|
|
compute.stage.resources = scalar;
|
|
compute.stage.resources.buffers[0].dwords[3] =
|
|
(static_cast<u32>(Prospero::BufferFormat::k32Float) << 12u) | 0x204u;
|
|
Require("Ps5GameExampleImageClear", "wrong store format",
|
|
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size),
|
|
"a format that changes the stored value was accepted as a uint fill");
|
|
// A body change preserving every descriptor and dispatch field must change
|
|
// the result.
|
|
scalar_code[2] = EncodeVop1(0x01u, 1, InlineU32(7));
|
|
compute.stage.resources = Compile(scalar_code);
|
|
Require("Ps5GameExampleImageClear", "changed store value",
|
|
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size) &&
|
|
packed_clear == 7u,
|
|
"clear recognition ignored the shader's actual stored value");
|
|
scalar_code[2] = EncodeVop1(0x01u, 1, Vgpr(0));
|
|
compute.stage.resources = Compile(scalar_code);
|
|
Require("Ps5GameExampleImageClear", "varying store value",
|
|
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size),
|
|
"a varying store with identical resources was treated as a fill");
|
|
|
|
const auto scalar_address = reinterpret_cast<uint64_t>(&scalar_clear);
|
|
user_data[4] = static_cast<u32>(scalar_address);
|
|
user_data[5] = static_cast<u32>(scalar_address >> 32u) | (16u << 16u);
|
|
user_data[6] = 1;
|
|
user_data[7] = 0x4dfacu;
|
|
compute.workgroup_register = 8;
|
|
scalar_code.clear();
|
|
AppendVop3(&scalar_code, 0x346u, 0, 8, InlineU32(6), Vgpr(0));
|
|
scalar_code.push_back(EncodeSmem0(0x08u, 106, 2));
|
|
scalar_code.push_back(EncodeSmem1(0, 125));
|
|
scalar_code.push_back(EncodeVop1(0x01u, 1, 106));
|
|
scalar_code.push_back(EncodeMubuf0(0x04u, 0, true, false));
|
|
scalar_code.push_back(EncodeMubuf1(1, 0, 0));
|
|
AppendEnd(&scalar_code);
|
|
clean_scalar = true;
|
|
auto loaded = Compile(scalar_code);
|
|
compute.stage.resources = loaded;
|
|
Require("Ps5GameExampleImageClear", "scalar-loaded fill",
|
|
ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size) &&
|
|
packed_clear == scalar_clear && descriptor.Base48() == 0x200000u,
|
|
"GTA3's scalar input was confused with its destination");
|
|
clean_scalar = false;
|
|
compute.stage.resources = Compile(scalar_code);
|
|
Require("Ps5GameExampleImageClear", "unavailable clean scalar",
|
|
!ResolveComputeBufferFill(compute, 1, 1, 1, 0x41u, descriptor,
|
|
packed_clear, size),
|
|
"a scalar without a clean reader was replaced by a fill");
|
|
std::printf("[host] %-32s ok\n", "Ps5GameExampleImageClear");
|
|
}
|
|
|
|
void CheckEmbeddedFetchVertexOffset() {
|
|
const auto MakeFetch = [](std::initializer_list<std::pair<u32, u32>> adds,
|
|
std::optional<std::pair<u32, u32>> late_add = {},
|
|
u32 accumulator_vgpr = 0, bool ngg_sad = false) {
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
code.push_back(EncodeSmem0(0x02u, 20, 4));
|
|
code.push_back(EncodeSmem1(0));
|
|
const auto AppendOffsets = [&]() {
|
|
for (const auto [sgpr, index_vgpr] : adds) {
|
|
if (ngg_sad) {
|
|
AppendVop3(&code, 0x15du, accumulator_vgpr, sgpr, InlineU32(0),
|
|
Vgpr(index_vgpr));
|
|
} else {
|
|
AppendVop3B(&code, 0x30fu, accumulator_vgpr, 0, sgpr,
|
|
Vgpr(index_vgpr));
|
|
}
|
|
}
|
|
};
|
|
if (ngg_sad) {
|
|
AppendOffsets();
|
|
}
|
|
code.push_back(EncodeVop2(0x01u, 0, Vgpr(8), 5));
|
|
if (!ngg_sad) {
|
|
AppendOffsets();
|
|
}
|
|
code.push_back(EncodeMubuf0(0x03u, 0, true));
|
|
code.push_back(EncodeMubuf1(9, 5, 0));
|
|
if (late_add.has_value()) {
|
|
AppendVop3B(&code, 0x30fu, 0, 0, late_add->first, Vgpr(late_add->second));
|
|
}
|
|
AppendEnd(&code);
|
|
return code;
|
|
};
|
|
|
|
const auto Compile = [&](const char *name, const std::vector<u32> &code,
|
|
u32 slot10) {
|
|
std::array<u32, 11> user_data{};
|
|
user_data[10] = slot10;
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.fetch_embedded = true;
|
|
vertex.fetch_buffer_reg = 0;
|
|
vertex.fetch_attrib_reg = 2;
|
|
vertex.resources_num = 1;
|
|
vertex.resources[0].fields[3] =
|
|
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float) << 12u | DstSel(4, 5, 6, 7);
|
|
vertex.resources_dst[0].attr_id = 0;
|
|
vertex.resources_dst[0].registers_num = 4;
|
|
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = ShaderType::Vertex;
|
|
options.user_data_base = 8;
|
|
options.user_data = user_data;
|
|
options.input_info.vertex = &vertex;
|
|
|
|
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
|
auto resource_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
|
};
|
|
Require(name, "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
resource_plan, runtime, resources, specialization),
|
|
"embedded fetch resources could not be materialized");
|
|
auto result = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
Require(name, "fetch rewrite",
|
|
result.program.info.vertex_fetch_components[0] == 4,
|
|
"encoded fetch sequence was not recognized and rewritten");
|
|
return CompiledShader{std::move(result.spirv), std::move(result.program),
|
|
std::move(resources), {}};
|
|
};
|
|
|
|
const auto Resolve = [](const CompiledShader &result,
|
|
u32 index_offset) {
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.fetch_embedded = true;
|
|
ShaderRecompiler::IR::CompiledShaderInfo program{};
|
|
program.user_data_base = result.program.user_data_base;
|
|
program.info.vertex_offset_sgpr = result.program.info.vertex_offset_sgpr;
|
|
vertex.stage.program = &program;
|
|
vertex.stage.resources = result.resources;
|
|
return ResolveVertexOffset(index_offset, vertex);
|
|
};
|
|
|
|
const auto ResolveInstance = [](const CompiledShader &result) {
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.fetch_embedded = true;
|
|
ShaderRecompiler::IR::CompiledShaderInfo program{};
|
|
program.user_data_base = result.program.user_data_base;
|
|
program.info.instance_offset_sgpr =
|
|
result.program.info.instance_offset_sgpr;
|
|
vertex.stage.program = &program;
|
|
vertex.stage.resources = result.resources;
|
|
return ResolveInstanceOffset(vertex);
|
|
};
|
|
|
|
const auto valid =
|
|
Compile("EmbeddedFetchVertexOffset", MakeFetch({{18, 0}}), 7);
|
|
Require(
|
|
"EmbeddedFetchVertexOffset", "parse",
|
|
valid.program.info.vertex_offset_sgpr == 18 && Resolve(valid, 0) == 7 &&
|
|
Resolve(valid, 5) == 5,
|
|
"canonical fetch offset or register index-offset precedence is wrong");
|
|
|
|
const auto ngg_code = MakeFetch({{18, 5}}, {}, 5, true);
|
|
Require("EmbeddedFetchNggVertexOffset", "encoding",
|
|
ngg_code[3] == 0xd55d0005u && ngg_code[4] == 0x04150012u,
|
|
"test does not encode the PS5 V_SAD_U32 vertex-offset prolog");
|
|
const auto ngg = Compile("EmbeddedFetchNggVertexOffset", ngg_code, 8);
|
|
Require("EmbeddedFetchNggVertexOffset", "parse",
|
|
ngg.program.info.vertex_offset_sgpr == 18 && Resolve(ngg, 0) == 8 &&
|
|
Resolve(ngg, 5) == 5,
|
|
"PS5 NGG vertex-index offset or register index-offset precedence is "
|
|
"wrong");
|
|
|
|
const auto ngg_instance =
|
|
Compile("EmbeddedFetchNggInstanceOffset",
|
|
MakeFetch({{18, 8}}, {}, 8, true), 81);
|
|
Require("EmbeddedFetchNggInstanceOffset", "parse",
|
|
ngg_instance.program.info.vertex_offset_sgpr == -1 &&
|
|
ngg_instance.program.info.instance_offset_sgpr == 18 &&
|
|
ResolveInstance(ngg_instance) == 81,
|
|
"PS5 NGG instance-index offset was not detected or resolved");
|
|
|
|
const auto instance_conflict = Compile(
|
|
"EmbeddedFetchConflictingInstanceOffset",
|
|
MakeFetch({{17, 8}, {18, 8}}, {}, 8, true), 81);
|
|
const auto instance_malformed =
|
|
Compile("EmbeddedFetchMalformedInstanceOffset",
|
|
MakeFetch({{18, 7}}, {}, 7, true), 81);
|
|
const auto instance_outside =
|
|
Compile("EmbeddedFetchOutsideInstanceOffset",
|
|
MakeFetch({{19, 8}}, {}, 8, true), 81);
|
|
for (const auto *result :
|
|
{&instance_conflict, &instance_malformed, &instance_outside}) {
|
|
Require("EmbeddedFetchNggInstanceOffset", "fail closed",
|
|
result->program.info.instance_offset_sgpr == -1 &&
|
|
ResolveInstance(*result) == 0,
|
|
"conflicting, malformed, or out-of-window instance add was "
|
|
"classified");
|
|
}
|
|
|
|
const auto pointer = 0x5b7c5100u;
|
|
const auto late = Compile("EmbeddedFetchLateOffset",
|
|
MakeFetch({}, std::pair<u32, u32>{18, 0}), pointer);
|
|
const auto conflict = Compile("EmbeddedFetchConflictingOffset",
|
|
MakeFetch({{17, 0}, {18, 0}}), pointer);
|
|
const auto malformed =
|
|
Compile("EmbeddedFetchMalformedOffset", MakeFetch({{18, 1}}), pointer);
|
|
const auto outside =
|
|
Compile("EmbeddedFetchOutsideOffset", MakeFetch({{19, 0}}), pointer);
|
|
for (const auto *result : {&late, &conflict, &malformed, &outside}) {
|
|
Require("EmbeddedFetchVertexOffset", "fail closed",
|
|
result->program.info.vertex_offset_sgpr == -1 &&
|
|
Resolve(*result, 0) == 0,
|
|
"non-prolog, conflicting, malformed, or out-of-window add was "
|
|
"classified");
|
|
}
|
|
std::printf("[host] %-32s ok\n", "EmbeddedFetchVertexOffset");
|
|
}
|
|
|
|
[[noreturn]] void RunReverseRenderTargetDeathCase() {
|
|
(void)TextureGetRenderTargetFormat(Prospero::ChannelLayout::k16_16_16_16,
|
|
Prospero::ChannelType::kSrgb,
|
|
Prospero::ChannelOrder::kStandard);
|
|
std::_Exit(0x7f);
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
void CheckRenderTargetFormatContract() {
|
|
const auto r8_uint = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k8, Prospero::ChannelType::kUInt,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Require("RenderTargetFormat", "R8 uint",
|
|
r8_uint.format == vk::Format::eR8Uint &&
|
|
r8_uint.bytes_per_element == 1u &&
|
|
r8_uint.export_mapping.IsIdentity(),
|
|
"R8 uint render-target tuple was rejected");
|
|
|
|
const auto rgb565 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k5_6_5, Prospero::ChannelType::kUNorm,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Require("RenderTargetFormat", "RGB565 UNorm",
|
|
rgb565.format == vk::Format::eB5G6R5UnormPack16 &&
|
|
rgb565.bytes_per_element == 2u &&
|
|
rgb565.export_mapping.IsIdentity(),
|
|
"RGB565 UNorm render-target tuple was rejected");
|
|
|
|
const auto uint_format = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kUInt,
|
|
Prospero::ChannelOrder::kStandard);
|
|
Require("RenderTargetFormat", "RGBA16 uint",
|
|
uint_format.format == vk::Format::eR16G16B16A16Uint &&
|
|
uint_format.bytes_per_element == 8u &&
|
|
uint_format.export_mapping.IsIdentity(),
|
|
"RGBA16 uint render-target tuple was rejected");
|
|
|
|
const auto format = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kReversed);
|
|
Require("ReverseRenderTarget", "exact format",
|
|
format.format == vk::Format::eR16G16B16A16Sfloat &&
|
|
format.bytes_per_element == 8u &&
|
|
format.export_mapping == Prospero::ColorMappingAbgr,
|
|
"exact reverse RGBA16F render-target tuple was rejected");
|
|
Require("ReverseRenderTarget", "write masks",
|
|
format.export_mapping.ApplyMask(0x1u) == 0x8u &&
|
|
format.export_mapping.ApplyMask(0x2u) == 0x4u &&
|
|
format.export_mapping.ApplyMask(0x4u) == 0x2u &&
|
|
format.export_mapping.ApplyMask(0x8u) == 0x1u &&
|
|
format.export_mapping.ApplyMask(0xfu) == 0xfu,
|
|
"reverse RGBA16F component mask was not mapped exactly once");
|
|
|
|
const auto rg32 = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k32_32, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kReversed);
|
|
Require(
|
|
"ReverseRenderTarget", "GR32 float",
|
|
rg32.format == vk::Format::eR32G32Sfloat &&
|
|
rg32.bytes_per_element == 8u &&
|
|
rg32.export_mapping == Prospero::ColorMappingGr &&
|
|
rg32.export_mapping.ApplyMask(0x1u) == 0x2u &&
|
|
rg32.export_mapping.ApplyMask(0x2u) == 0x1u &&
|
|
rg32.export_mapping.ApplyMask(0x3u) == 0x3u &&
|
|
rg32.export_mapping.ApplyMask(0xfu) == 0xfu,
|
|
"reverse GR32F render-target export or write-mask mapping is incorrect");
|
|
|
|
const auto argb = TextureGetRenderTargetFormat(
|
|
Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat,
|
|
Prospero::ChannelOrder::kAltReversed);
|
|
Require(
|
|
"ReverseRenderTarget", "ARGB float",
|
|
argb.format == vk::Format::eR16G16B16A16Sfloat &&
|
|
argb.export_mapping == Prospero::ColorMappingArgb &&
|
|
argb.export_mapping.ApplyMask(0x1u) == 0x2u &&
|
|
argb.export_mapping.ApplyMask(0x2u) == 0x4u &&
|
|
argb.export_mapping.ApplyMask(0x4u) == 0x8u &&
|
|
argb.export_mapping.ApplyMask(0x8u) == 0x1u,
|
|
"alternate-reversed render-target mapping did not invert its ARGB cycle");
|
|
|
|
char path[MAX_PATH]{};
|
|
Require("ReverseRenderTarget", "host",
|
|
GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
|
|
"GetModuleFileName failed");
|
|
std::string command = std::string("\"") + path + "\" --reverse-rt-death";
|
|
std::vector<char> mutable_command(command.begin(), command.end());
|
|
mutable_command.push_back('\0');
|
|
STARTUPINFOA startup{sizeof(startup)};
|
|
PROCESS_INFORMATION process{};
|
|
Require("ReverseRenderTarget", "host",
|
|
CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr,
|
|
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup,
|
|
&process) != 0,
|
|
"CreateProcess failed");
|
|
Require("ReverseRenderTarget", "host",
|
|
WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0,
|
|
"unsupported adjacent render-target tuple timed out");
|
|
DWORD exit_code = 0;
|
|
const bool exited = GetExitCodeProcess(process.hProcess, &exit_code) != 0;
|
|
CloseHandle(process.hThread);
|
|
CloseHandle(process.hProcess);
|
|
Require("ReverseRenderTarget", "hard failure", exited && exit_code == 321,
|
|
"invalid render-target tuple did not retain the fatal guard");
|
|
std::printf("[host] %-32s ok\n", "RenderTargetFormat");
|
|
}
|
|
#endif
|
|
|
|
[[noreturn]] void RunImageViewDeathCase(const char *kind) {
|
|
if (std::strcmp(kind, "sampled-invalid-selector") == 0) {
|
|
(void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(4, 5, 6, 2));
|
|
} else if (std::strcmp(kind, "sampled-incompatible-format") == 0) {
|
|
(void)SelectSampledColorView(vk::Format::eR8Unorm, vk::Format::eR16Unorm,
|
|
DstSel(4, 0, 0, 1));
|
|
} else if (std::strcmp(kind, "sampled-invalid-high") == 0) {
|
|
(void)SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(7, 6, 5, 3));
|
|
} else if (std::strcmp(kind, "storage-incompatible-format") == 0) {
|
|
ValidateStorageColorView(vk::Format::eR8G8B8A8Srgb,
|
|
vk::Format::eR16G16B16A16Unorm,
|
|
DstSel(4, 5, 6, 7));
|
|
} else if (std::strcmp(kind, "volume-mip-count") == 0 ||
|
|
std::strcmp(kind, "volume-slice-range") == 0) {
|
|
VulkanHarness vulkan;
|
|
auto &graphics = vulkan.RuntimeContext();
|
|
CommandScheduler scheduler(vulkan.RuntimeRenderer(), graphics);
|
|
ImageInfo volume_info{};
|
|
volume_info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
volume_info.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
volume_info.type = Prospero::ImageType::kColor3D;
|
|
volume_info.extent = {8, 8, 4};
|
|
volume_info.resources = {2, 1};
|
|
volume_info.pitch = 8;
|
|
volume_info.bytes_per_block = 4;
|
|
volume_info.samples = 1;
|
|
volume_info.tile_mode = Prospero::TileMode::kLinear;
|
|
Libs::Graphics::Image volume(graphics, scheduler, volume_info);
|
|
ImageViewInfo view{};
|
|
view.format = volume_info.pixel_format;
|
|
view.type = vk::ImageViewType::e2DArray;
|
|
view.aspect = vk::ImageAspectFlagBits::eColor;
|
|
view.base_level = 1;
|
|
view.level_count = 1;
|
|
view.layer_count = 1;
|
|
view.usage = vk::ImageUsageFlagBits::eSampled;
|
|
if (std::strcmp(kind, "volume-mip-count") == 0) {
|
|
view.base_level = 0;
|
|
view.level_count = 2;
|
|
} else {
|
|
view.base_layer = 1;
|
|
view.layer_count = 2;
|
|
}
|
|
(void)volume.FindView(view);
|
|
} else {
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.written = true;
|
|
if (std::strcmp(kind, "storage-kind") == 0) {
|
|
resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
} else if (std::strcmp(kind, "storage-no-write") == 0) {
|
|
resource.written = false;
|
|
} else if (std::strcmp(kind, "storage-nonuint-atomic") == 0) {
|
|
resource.atomic = true;
|
|
} else if (std::strcmp(kind, "storage-compare") == 0) {
|
|
resource.depth_compare = true;
|
|
} else if (std::strcmp(kind, "storage-dimension") == 0) {
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Unknown;
|
|
} else {
|
|
std::_Exit(0x7e);
|
|
}
|
|
ValidateStorageImageResource(resource);
|
|
}
|
|
std::_Exit(0x7f);
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
void CheckSampledColorViews() {
|
|
Require("SampledColorViews", "identity",
|
|
SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7),
|
|
"RGBA did not select the identity view");
|
|
uint32_t valid_swizzles = 0;
|
|
for (uint32_t swizzle = 0; swizzle <= 0xfffu; swizzle++) {
|
|
bool expected = true;
|
|
for (uint32_t channel = 0; channel < 4; channel++) {
|
|
const auto selector = GetDstSel(swizzle, channel);
|
|
if (selector == 2 || selector == 3) {
|
|
expected = false;
|
|
}
|
|
}
|
|
const bool valid = IsValidImageSwizzle(swizzle);
|
|
const bool supported = IsSupportedSampledColorView(
|
|
vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Unorm, swizzle);
|
|
Require("SampledColorViews", "exhaustive read swizzle domain",
|
|
valid == expected && supported == expected,
|
|
"sampled swizzle validator disagreed with the PS5 selector domain");
|
|
valid_swizzles += valid;
|
|
}
|
|
Require("SampledColorViews", "all PS5 read swizzles",
|
|
valid_swizzles == 1296 && !IsValidImageSwizzle(DstSel(4, 5, 6, 2)) &&
|
|
!IsValidImageSwizzle(DstSel(4, 5, 6, 3)) &&
|
|
!IsValidImageSwizzle(0x1000),
|
|
"valid PS5 sampled mappings were rejected or reserved selectors were "
|
|
"admitted");
|
|
const auto arbitrary = DstSel(5, 1, 7, 0);
|
|
const auto components = TextureGetComponentMapping(arbitrary, {});
|
|
Require("SampledColorViews", "generic Vulkan component mapping",
|
|
SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
arbitrary) == arbitrary &&
|
|
components.r == vk::ComponentSwizzle::eG &&
|
|
components.g == vk::ComponentSwizzle::eOne &&
|
|
components.b == vk::ComponentSwizzle::eA &&
|
|
components.a == vk::ComponentSwizzle::eZero,
|
|
"arbitrary valid sampled mapping did not use the generic view path");
|
|
Require("SampledColorViews", "R8 R001",
|
|
SelectSampledColorView(vk::Format::eR8Unorm, vk::Format::eR8Unorm,
|
|
DstSel(4, 0, 0, 1)) == DstSel(4, 0, 0, 1),
|
|
"R8 did not select its R001 view");
|
|
Require("SampledColorViews", "R8 000R",
|
|
SelectSampledColorView(vk::Format::eR8Unorm, vk::Format::eR8Unorm,
|
|
DstSel(0, 0, 0, 4)) == DstSel(0, 0, 0, 4),
|
|
"R8 did not select its 000R component-mapped view");
|
|
Require("SampledColorViews", "mutable R8 uint/unorm 000R",
|
|
SelectSampledColorView(vk::Format::eR8Uint, vk::Format::eR8Unorm,
|
|
DstSel(0, 0, 0, 4)) == DstSel(0, 0, 0, 4),
|
|
"compatible R8 integer target sampled view was rejected");
|
|
Require("SampledColorViews", "R16G16 RG01",
|
|
SelectSampledColorView(vk::Format::eR16G16Sfloat,
|
|
vk::Format::eR16G16Sfloat,
|
|
DstSel(4, 5, 0, 1)) == DstSel(4, 5, 0, 1),
|
|
"R16G16 did not select its RG01 view");
|
|
Require("SampledColorViews", "alpha one",
|
|
SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(4, 5, 6, 1)) == DstSel(4, 5, 6, 1),
|
|
"RGB1 did not select the alpha-one view");
|
|
Require("SampledColorViews", "mutable BGRA target",
|
|
SelectSampledColorView(vk::Format::eB8G8R8A8Unorm,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7),
|
|
"BGRA target did not select the exact RGBA/BGRA mutable view");
|
|
Require(
|
|
"SampledColorViews", "mutable sRGB view of UNORM BGRA target",
|
|
SelectSampledColorView(vk::Format::eB8G8R8A8Unorm,
|
|
vk::Format::eR8G8B8A8Srgb,
|
|
DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7),
|
|
"UNORM BGRA target did not select its compatible sRGB RGBA sampled view");
|
|
Require("SampledColorViews", "mutable sRGB BGRA target",
|
|
SelectSampledColorView(vk::Format::eB8G8R8A8Srgb,
|
|
vk::Format::eR8G8B8A8Srgb,
|
|
DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7),
|
|
"sRGB BGRA target did not select its matching mutable RGBA view");
|
|
constexpr auto colorspace_swizzle = DstSel(5, 1, 7, 0);
|
|
Require("SampledColorViews", "mutable sRGB/UNORM views",
|
|
SelectSampledColorView(vk::Format::eR8G8B8A8Srgb,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7) &&
|
|
SelectSampledColorView(vk::Format::eB8G8R8A8Unorm,
|
|
vk::Format::eB8G8R8A8Srgb,
|
|
colorspace_swizzle) == colorspace_swizzle,
|
|
"same-order mutable sRGB/UNORM sampled views were rejected");
|
|
Require("SampledColorViews", "mutable packed RGB10 view",
|
|
SelectSampledColorView(vk::Format::eA2R10G10B10UnormPack32,
|
|
vk::Format::eA2B10G10R10UnormPack32,
|
|
DstSel(6, 5, 4, 7)) == DstSel(6, 5, 4, 7),
|
|
"packed RGB10 target did not select its matching mutable "
|
|
"channel-order view");
|
|
Require(
|
|
"SampledColorViews", "reverse RGBA16F sampled view",
|
|
SelectSampledColorView(vk::Format::eR16G16B16A16Sfloat,
|
|
vk::Format::eR16G16B16A16Sfloat,
|
|
DstSel(7, 6, 5, 4)) == DstSel(7, 6, 5, 4),
|
|
"reverse RGBA16F target did not select its reciprocal ABGR sampled view");
|
|
Require("SampledColorViews", "mutable integer-class views",
|
|
SelectSampledColorView(vk::Format::eR16G16B16A16Sfloat,
|
|
vk::Format::eR16G16B16A16Uint,
|
|
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7) &&
|
|
SelectSampledColorView(
|
|
vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Uint,
|
|
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7) &&
|
|
SelectSampledColorView(vk::Format::eR8G8B8A8Uint,
|
|
vk::Format::eR8G8B8A8Unorm,
|
|
DstSel(4, 5, 6, 7)) == DstSel(4, 5, 6, 7),
|
|
"compatible integer render-target sampled view was rejected");
|
|
Require("SampledColorViews", "invalid depth format",
|
|
!IsSupportedSampledDepthView(vk::Format::eD24UnormS8Uint,
|
|
vk::Format::eR8Unorm,
|
|
DstSel(4, 4, 4, 4)),
|
|
"incompatible sampled depth format was accepted");
|
|
Require("SampledColorViews", "invalid depth swizzle",
|
|
!IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
|
|
vk::Format::eR32Sfloat,
|
|
DstSel(4, 5, 6, 7)),
|
|
"incompatible sampled depth swizzle was accepted");
|
|
Require("SampledColorViews", "D32 depth target",
|
|
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
|
|
vk::Format::eR32Sfloat,
|
|
DstSel(4, 4, 4, 4)),
|
|
"D32 depth target did not select its depth-aspect view");
|
|
Require("SampledColorViews", "D16 R000 depth target",
|
|
IsSupportedSampledDepthView(
|
|
vk::Format::eD16Unorm, vk::Format::eR16Unorm, DstSel(4, 0, 0, 0)),
|
|
"D16 depth target did not select its R000 depth-aspect view");
|
|
Require("SampledColorViews", "D16S8 R001 depth target",
|
|
IsSupportedSampledDepthView(vk::Format::eD16UnormS8Uint,
|
|
vk::Format::eR16Unorm,
|
|
DstSel(4, 0, 0, 1)),
|
|
"D16S8 depth target did not select its R001 depth-aspect view");
|
|
Require("SampledColorViews", "promoted D24S8 R001 depth target",
|
|
IsSupportedSampledDepthView(vk::Format::eD24UnormS8Uint,
|
|
vk::Format::eR16Unorm,
|
|
DstSel(4, 0, 0, 1)),
|
|
"D24S8 host fallback did not preserve the guest R16 depth view");
|
|
Require("SampledColorViews", "promoted D32S8 R001 depth target",
|
|
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
|
|
vk::Format::eR16Unorm,
|
|
DstSel(4, 0, 0, 1)),
|
|
"D32S8 host fallback did not preserve the guest R16 depth view");
|
|
Require("SampledColorViews", "D32S8 R001 depth target",
|
|
IsSupportedSampledDepthView(vk::Format::eD32SfloatS8Uint,
|
|
vk::Format::eR32Sfloat,
|
|
DstSel(4, 0, 0, 1)),
|
|
"D32S8 depth target did not select its R001 depth-aspect view");
|
|
ShaderRecompiler::IR::ImageResource storage_resource{};
|
|
storage_resource.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
storage_resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
storage_resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
storage_resource.written = true;
|
|
Require("SampledColorViews", "storage resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"exact storage resource contract was rejected");
|
|
storage_resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim3D;
|
|
storage_resource.read = true;
|
|
Require("SampledColorViews", "read-write 3D storage resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"basic read-write 3D storage resource was rejected");
|
|
storage_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
storage_resource.read = false;
|
|
Require("SampledColorViews", "write-only 2D-array storage resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"basic write-only 2D-array storage resource was rejected");
|
|
storage_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
Require("SampledColorViews", "write-only uint 2D-array storage resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"basic write-only uint 2D-array storage resource was rejected");
|
|
storage_resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
storage_resource.read = true;
|
|
storage_resource.atomic = true;
|
|
Require("SampledColorViews", "atomic uint 2D storage resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"atomic uint storage resource was rejected");
|
|
storage_resource.atomic = false;
|
|
storage_resource.mip_mode =
|
|
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
|
storage_resource.mip_count = 3;
|
|
Require("SampledColorViews", "dynamic storage mip resource",
|
|
IsSupportedStorageImageResource(storage_resource),
|
|
"dynamic storage mip resource was rejected");
|
|
|
|
char path[MAX_PATH]{};
|
|
Require("SampledColorViews", "host",
|
|
GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
|
|
"GetModuleFileName failed");
|
|
for (const char *kind :
|
|
{"sampled-invalid-selector", "sampled-incompatible-format",
|
|
"sampled-invalid-high", "storage-incompatible-format", "storage-kind",
|
|
"storage-no-write", "storage-nonuint-atomic", "storage-compare",
|
|
"storage-dimension", "volume-mip-count", "volume-slice-range"}) {
|
|
std::string command =
|
|
std::string("\"") + path + "\" --image-view-death " + kind;
|
|
std::vector<char> mutable_command(command.begin(), command.end());
|
|
mutable_command.push_back('\0');
|
|
STARTUPINFOA startup{sizeof(startup)};
|
|
PROCESS_INFORMATION process{};
|
|
Require("SampledColorViews", "host",
|
|
CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr,
|
|
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup,
|
|
&process) != 0,
|
|
"CreateProcess failed");
|
|
Require("SampledColorViews", "host",
|
|
WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0,
|
|
"unsupported view death case timed out");
|
|
DWORD exit_code = 0;
|
|
const bool exited = GetExitCodeProcess(process.hProcess, &exit_code) != 0;
|
|
CloseHandle(process.hThread);
|
|
CloseHandle(process.hProcess);
|
|
Require("SampledColorViews", "host", exited && exit_code == 321,
|
|
std::string(kind) +
|
|
" component mapping did not report a fatal error");
|
|
}
|
|
std::printf("[host] %-32s ok\n", "SampledColorRenderTargetViews");
|
|
}
|
|
#endif
|
|
|
|
void CheckSampledDepthResource() {
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = true;
|
|
resource.depth_compare = true;
|
|
Require("SampledDepthResource", "comparison read",
|
|
IsSupportedSampledDepthResource(resource),
|
|
"basic comparison-sampled depth resource was rejected");
|
|
|
|
resource.depth_compare = false;
|
|
Require("SampledDepthResource", "ordinary read",
|
|
IsSupportedSampledDepthResource(resource),
|
|
"basic non-comparison depth read was rejected");
|
|
|
|
const auto basic = resource;
|
|
resource.read = false;
|
|
Require("SampledDepthResource", "read required",
|
|
!IsSupportedSampledDepthResource(resource),
|
|
"non-reading depth resource was accepted");
|
|
resource = basic;
|
|
resource.written = true;
|
|
Require("SampledDepthResource", "write rejected",
|
|
!IsSupportedSampledDepthResource(resource),
|
|
"writable depth resource was accepted");
|
|
resource = basic;
|
|
resource.atomic = true;
|
|
Require("SampledDepthResource", "atomic rejected",
|
|
!IsSupportedSampledDepthResource(resource),
|
|
"atomic depth resource was accepted");
|
|
resource = basic;
|
|
resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
|
Require("SampledDepthResource", "dynamic mip rejected",
|
|
!IsSupportedSampledDepthResource(resource),
|
|
"dynamic-storage mip depth resource was accepted");
|
|
resource = basic;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
Require("SampledDepthResource", "singleton array accepted",
|
|
IsSupportedSampledDepthResource(resource),
|
|
"array depth resource was rejected");
|
|
resource = basic;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
Require("SampledDepthResource", "integer read",
|
|
IsSupportedSampledDepthResource(resource),
|
|
"read-only uint depth resource was rejected");
|
|
resource.depth_compare = true;
|
|
Require("SampledDepthResource", "integer comparison rejected",
|
|
!IsSupportedSampledDepthResource(resource),
|
|
"uint depth resource accepted comparison sampling");
|
|
Require("SampledDepthResource", "R32 uint depth view",
|
|
IsSupportedSampledDepthFormat(vk::Format::eD32Sfloat,
|
|
vk::Format::eR32Uint) &&
|
|
!IsSupportedSampledDepthFormat(vk::Format::eR32Uint,
|
|
vk::Format::eR32Uint),
|
|
"R32 uint depth-view compatibility diverged from its backing role");
|
|
std::printf("[host] %-32s ok\n", "SampledDepthResource");
|
|
}
|
|
|
|
void CheckSampledVideoOutView(RenderContext &renderer) {
|
|
auto &context = renderer.GetGraphics();
|
|
CommandScheduler scheduler(renderer, context);
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = true;
|
|
|
|
ShaderTextureResource descriptor{};
|
|
descriptor.fields[3] = static_cast<uint32_t>(Prospero::ImageType::kColor2D)
|
|
<< 28u;
|
|
ImageInfo info{};
|
|
info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
info.guest_format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
info.type = Prospero::ImageType::kColor2D;
|
|
info.extent = {1, 1, 1};
|
|
info.resources = {1, 1};
|
|
info.pitch = 1;
|
|
info.bytes_per_block = 4;
|
|
info.samples = 1;
|
|
info.tile_mode = Prospero::TileMode::kLinear;
|
|
info.mip_layout[0] = {0, 4, 1, 1};
|
|
Image image(context, scheduler, info);
|
|
image.usage.video_out = true;
|
|
Require("SampledVideoOutView", "basic 2D",
|
|
IsSupportedSampledVideoOutView(resource, descriptor, image),
|
|
"basic 2D video-out view was rejected");
|
|
|
|
const auto basic_resource = resource;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
const bool rejects_array_resource =
|
|
!IsSupportedSampledVideoOutView(resource, descriptor, image);
|
|
resource = basic_resource;
|
|
descriptor.fields[3] =
|
|
static_cast<uint32_t>(Prospero::ImageType::kColor2DArray) << 28u;
|
|
const bool rejects_array_descriptor =
|
|
!IsSupportedSampledVideoOutView(resource, descriptor, image);
|
|
descriptor.fields[3] = static_cast<uint32_t>(Prospero::ImageType::kColor2D)
|
|
<< 28u;
|
|
descriptor.fields[4] = 1u << 16u;
|
|
const bool rejects_base_layer =
|
|
!IsSupportedSampledVideoOutView(resource, descriptor, image);
|
|
descriptor.fields[4] = 1u;
|
|
const bool rejects_layer_count =
|
|
!IsSupportedSampledVideoOutView(resource, descriptor, image);
|
|
descriptor.fields[4] = 0;
|
|
image.info.resources.layers = 2;
|
|
const bool rejects_layered_image =
|
|
!IsSupportedSampledVideoOutView(resource, descriptor, image);
|
|
Require("SampledVideoOutView", "array hard failures",
|
|
rejects_array_resource && rejects_array_descriptor &&
|
|
rejects_base_layer && rejects_layer_count &&
|
|
rejects_layered_image,
|
|
"unsupported layered video-out view was accepted");
|
|
std::printf("[host] %-32s ok\n", "SampledVideoOutView");
|
|
}
|
|
|
|
void CheckImageTransitionState(RenderContext &renderer) {
|
|
constexpr const char *name = "ImageTransitionState";
|
|
auto &context = renderer.GetGraphics();
|
|
CommandScheduler scheduler(renderer, context);
|
|
const auto MakeInfo = [](vk::Format format, uint32_t levels,
|
|
uint32_t layers) {
|
|
ImageInfo info{};
|
|
info.pixel_format = format;
|
|
info.guest_format = Prospero::BufferFormat::k8UNorm;
|
|
info.type = Prospero::ImageType::kColor2D;
|
|
info.extent = {4, 4, 1};
|
|
info.resources = {levels, layers};
|
|
info.pitch = 4;
|
|
info.bytes_per_block = 1;
|
|
info.samples = 1;
|
|
info.tile_mode = Prospero::TileMode::kLinear;
|
|
return info;
|
|
};
|
|
const auto graphics_stage = vk::PipelineStageFlagBits2::eAllGraphics |
|
|
vk::PipelineStageFlagBits2::eComputeShader;
|
|
constexpr uint64_t copy_capacity = 128ull << 20;
|
|
Require(
|
|
name, "buffered-copy capacity bands",
|
|
ImageTestAccess::CopyRows(32ull << 10, 4096, copy_capacity) == 4096 &&
|
|
ImageTestAccess::CopyRows(32ull << 10, 4097, copy_capacity) == 4096 &&
|
|
ImageTestAccess::CopyRows(copy_capacity + 4, 1, copy_capacity) == 0,
|
|
"buffered image copy does not split at the fixed scratch capacity");
|
|
|
|
Image image(context, scheduler, MakeInfo(vk::Format::eR8Unorm, 2, 3));
|
|
auto barriers =
|
|
image.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, graphics_stage, {});
|
|
Require(
|
|
name, "initial full state",
|
|
barriers.size() == 1 &&
|
|
barriers[0].srcStageMask ==
|
|
vk::PipelineStageFlagBits2::eAllCommands &&
|
|
barriers[0].srcAccessMask == vk::AccessFlagBits2::eNone &&
|
|
barriers[0].dstStageMask == graphics_stage &&
|
|
barriers[0].dstAccessMask == vk::AccessFlagBits2::eShaderRead &&
|
|
barriers[0].oldLayout == vk::ImageLayout::eUndefined &&
|
|
barriers[0].newLayout == vk::ImageLayout::eShaderReadOnlyOptimal &&
|
|
barriers[0].subresourceRange.aspectMask ==
|
|
vk::ImageAspectFlagBits::eColor &&
|
|
barriers[0].subresourceRange.levelCount == VK_REMAINING_MIP_LEVELS &&
|
|
barriers[0].subresourceRange.layerCount ==
|
|
VK_REMAINING_ARRAY_LAYERS &&
|
|
image.backing.state.layout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal &&
|
|
image.backing.state.access_mask == vk::AccessFlagBits2::eShaderRead &&
|
|
image.backing.state.pl_stage == graphics_stage,
|
|
"initial transition did not record or retain pinned state");
|
|
Require(name, "repeat read elision",
|
|
image
|
|
.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, graphics_stage, {})
|
|
.empty(),
|
|
"identical read-only transition emitted a barrier");
|
|
barriers = image.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eTransferRead,
|
|
graphics_stage, {});
|
|
Require(name, "access-only transition",
|
|
barriers.size() == 1 &&
|
|
barriers[0].oldLayout == barriers[0].newLayout &&
|
|
barriers[0].srcAccessMask == vk::AccessFlagBits2::eShaderRead,
|
|
"same-layout access change was lost");
|
|
|
|
const auto CheckRepeatedWrite = [&](vk::AccessFlagBits2 access,
|
|
const char *label) {
|
|
image.backing.state = {};
|
|
image.backing.subresource_states.clear();
|
|
(void)image.GetBarriers(vk::ImageLayout::eGeneral, access, graphics_stage,
|
|
{});
|
|
Require(
|
|
name, label,
|
|
image.GetBarriers(vk::ImageLayout::eGeneral, access, graphics_stage, {})
|
|
.size() == 1,
|
|
"pinned repeated-write dependency was elided");
|
|
};
|
|
CheckRepeatedWrite(vk::AccessFlagBits2::eTransferWrite,
|
|
"repeat transfer write");
|
|
CheckRepeatedWrite(vk::AccessFlagBits2::eShaderWrite, "repeat shader write");
|
|
CheckRepeatedWrite(vk::AccessFlagBits2::eMemoryWrite, "repeat memory write");
|
|
image.backing.state = {};
|
|
image.backing.subresource_states.clear();
|
|
const auto attachment_access = vk::AccessFlagBits2::eColorAttachmentRead |
|
|
vk::AccessFlagBits2::eColorAttachmentWrite;
|
|
(void)image.GetBarriers(vk::ImageLayout::eColorAttachmentOptimal,
|
|
attachment_access, graphics_stage, {});
|
|
Require(name, "pinned attachment-write quirk",
|
|
image
|
|
.GetBarriers(vk::ImageLayout::eColorAttachmentOptimal,
|
|
attachment_access, graphics_stage, {})
|
|
.empty(),
|
|
"attachment writes diverged from the pinned repeated-write set");
|
|
|
|
image.backing.state = {};
|
|
image.backing.subresource_states.clear();
|
|
(void)image.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, graphics_stage, {});
|
|
constexpr ImageSubresourceRange mip1_layer0{1, 1, 0, 1};
|
|
barriers = image.GetBarriers(vk::ImageLayout::eGeneral,
|
|
vk::AccessFlagBits2::eShaderWrite,
|
|
graphics_stage, mip1_layer0);
|
|
Require(name, "first partial",
|
|
barriers.size() == 1 &&
|
|
barriers[0].subresourceRange.baseMipLevel == 1 &&
|
|
barriers[0].subresourceRange.baseArrayLayer == 0 &&
|
|
image.backing.subresource_states.size() == 6 &&
|
|
image.backing.subresource_states[3].layout ==
|
|
vk::ImageLayout::eGeneral &&
|
|
image.backing.subresource_states[3].access_mask ==
|
|
vk::AccessFlagBits2::eShaderWrite &&
|
|
image.backing.subresource_states[1].layout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
"partial state did not use mip-major indexing");
|
|
constexpr ImageSubresourceRange mip0_layer1{0, 1, 1, 1};
|
|
barriers = image.GetBarriers(
|
|
vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
|
|
vk::PipelineStageFlagBits2::eTransfer, mip0_layer1);
|
|
Require(name, "second partial",
|
|
barriers.size() == 1 &&
|
|
barriers[0].subresourceRange.baseMipLevel == 0 &&
|
|
barriers[0].subresourceRange.baseArrayLayer == 1 &&
|
|
image.backing.subresource_states[1].layout ==
|
|
vk::ImageLayout::eTransferDstOptimal &&
|
|
image.backing.subresource_states[1].access_mask ==
|
|
vk::AccessFlagBits2::eTransferWrite &&
|
|
image.backing.subresource_states[3].layout ==
|
|
vk::ImageLayout::eGeneral,
|
|
"second partial transition changed the wrong cell");
|
|
const ImageSubresourceRange explicit_full{0, 2, 0, 3};
|
|
barriers = image.GetBarriers(vk::ImageLayout::eShaderReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, graphics_stage,
|
|
explicit_full);
|
|
bool saw_mip0_layer1 = false;
|
|
bool saw_mip1_layer0 = false;
|
|
for (const auto &barrier : barriers) {
|
|
saw_mip0_layer1 |=
|
|
barrier.subresourceRange.baseMipLevel == 0 &&
|
|
barrier.subresourceRange.baseArrayLayer == 1 &&
|
|
barrier.oldLayout == vk::ImageLayout::eTransferDstOptimal &&
|
|
barrier.srcStageMask == vk::PipelineStageFlagBits2::eTransfer;
|
|
saw_mip1_layer0 |=
|
|
barrier.subresourceRange.baseMipLevel == 1 &&
|
|
barrier.subresourceRange.baseArrayLayer == 0 &&
|
|
barrier.oldLayout == vk::ImageLayout::eGeneral &&
|
|
barrier.srcAccessMask == vk::AccessFlagBits2::eShaderWrite;
|
|
}
|
|
Require(name, "partial normalization",
|
|
barriers.size() == 2 && saw_mip0_layer1 && saw_mip1_layer0 &&
|
|
image.backing.subresource_states.empty() &&
|
|
image.backing.state.layout ==
|
|
vk::ImageLayout::eShaderReadOnlyOptimal &&
|
|
image.backing.state.access_mask ==
|
|
vk::AccessFlagBits2::eShaderRead &&
|
|
image.backing.state.pl_stage == graphics_stage,
|
|
"full transition did not reconcile and collapse split state");
|
|
barriers = image.GetBarriers(vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::AccessFlagBits2::eTransferRead,
|
|
vk::PipelineStageFlagBits2::eTransfer, {});
|
|
Require(name, "normalized full transition",
|
|
barriers.size() == 1 &&
|
|
barriers[0].subresourceRange.levelCount ==
|
|
VK_REMAINING_MIP_LEVELS &&
|
|
barriers[0].subresourceRange.layerCount ==
|
|
VK_REMAINING_ARRAY_LAYERS &&
|
|
barriers[0].srcStageMask == graphics_stage &&
|
|
barriers[0].dstStageMask == vk::PipelineStageFlagBits2::eTransfer,
|
|
"normalized state did not return to one full barrier");
|
|
|
|
auto depth_info = MakeInfo(vk::Format::eD32SfloatS8Uint, 1, 1);
|
|
depth_info.guest_format = Prospero::BufferFormat::k32Float;
|
|
depth_info.bytes_per_block = 4;
|
|
depth_info.tile_mode = Prospero::TileMode::kDepth;
|
|
Image depth(context, scheduler, depth_info);
|
|
barriers =
|
|
depth.GetBarriers(vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
|
vk::AccessFlagBits2::eShaderRead, graphics_stage, {});
|
|
Require(name, "depth stencil aspects",
|
|
barriers.size() == 1 && barriers[0].subresourceRange.aspectMask ==
|
|
(vk::ImageAspectFlagBits::eDepth |
|
|
vk::ImageAspectFlagBits::eStencil),
|
|
"depth/stencil transition did not use the full image aspect");
|
|
|
|
HW::Context registers{};
|
|
HW::UserConfig user_config{};
|
|
HW::Shader shaders{};
|
|
scheduler.Begin(registers, user_config, shaders);
|
|
image.backing.state = {};
|
|
image.backing.subresource_states.clear();
|
|
image.Transit(vk::ImageLayout::eGeneral,
|
|
vk::AccessFlagBits2::eShaderRead |
|
|
vk::AccessFlagBits2::eTransferRead,
|
|
{}, scheduler.Current().Handle());
|
|
Require(name, "mixed access stages",
|
|
image.backing.state.pl_stage ==
|
|
(graphics_stage | vk::PipelineStageFlagBits2::eTransfer),
|
|
"mixed shader/transfer access omitted a required pipeline stage");
|
|
|
|
auto source_info = depth_info;
|
|
source_info.extent = {2, 2, 1};
|
|
source_info.pitch = 2;
|
|
source_info.stencil = {0x1000, 4};
|
|
auto destination_info = depth_info;
|
|
destination_info.resources.levels = 2;
|
|
destination_info.stencil = {0x2000, 16};
|
|
Image source(context, scheduler, source_info);
|
|
Image destination(context, scheduler, destination_info);
|
|
StreamBuffer upload(context, scheduler, MemoryUsage::Upload, 4096);
|
|
StreamBuffer download(context, scheduler, MemoryUsage::Download, 4096);
|
|
const auto [upload_data, upload_offset] = upload.Map(128, 4);
|
|
Require(name, "depth/stencil upload map", upload_data != nullptr,
|
|
"combined depth/stencil upload allocation failed");
|
|
constexpr std::array<uint32_t, 4> expected_depth{0x3e800000u, 0x3f000000u,
|
|
0x3f400000u, 0x3f800000u};
|
|
constexpr std::array<uint8_t, 4> expected_stencil{0x12, 0x34, 0x56, 0x78};
|
|
std::memcpy(upload_data, expected_depth.data(), sizeof(expected_depth));
|
|
std::memcpy(upload_data + 64, expected_stencil.data(),
|
|
sizeof(expected_stencil));
|
|
upload.Commit();
|
|
std::array<vk::BufferImageCopy, 2> source_copies{};
|
|
source_copies[0].bufferOffset = upload_offset;
|
|
source_copies[0].imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, 0,
|
|
1};
|
|
source_copies[0].imageExtent = {2, 2, 1};
|
|
source_copies[1].bufferOffset = upload_offset + 64;
|
|
source_copies[1].imageSubresource = {vk::ImageAspectFlagBits::eStencil, 0, 0,
|
|
1};
|
|
source_copies[1].imageExtent = {2, 2, 1};
|
|
source.Upload(source_copies, upload.Handle(), upload_offset, 128);
|
|
destination.CopyMip(source, 1, 0);
|
|
|
|
const auto [download_data, download_offset] = download.Map(128, 4);
|
|
Require(name, "depth/stencil download map", download_data != nullptr,
|
|
"combined depth/stencil download allocation failed");
|
|
download.Commit();
|
|
auto destination_copies = source_copies;
|
|
destination_copies[0].bufferOffset = download_offset;
|
|
destination_copies[0].imageSubresource.mipLevel = 1;
|
|
destination_copies[1].bufferOffset = download_offset + 64;
|
|
destination_copies[1].imageSubresource.mipLevel = 1;
|
|
destination.Download(destination_copies, download.Handle(), download_offset,
|
|
128);
|
|
|
|
auto buffered_info = MakeInfo(vk::Format::eR8Unorm, 1, 1);
|
|
buffered_info.extent = {8, 4, 1};
|
|
buffered_info.pitch = 8;
|
|
buffered_info.mip_layout[0] = {0, 32, 8, 4};
|
|
Image buffered_source(context, scheduler, buffered_info);
|
|
Image buffered_destination(context, scheduler, buffered_info);
|
|
constexpr std::array<uint8_t, 32> buffered_expected{
|
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa,
|
|
0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x10, 0x21, 0x32, 0x43, 0x54, 0x65,
|
|
0x76, 0x87, 0x98, 0xa9, 0xba, 0xcb, 0xdc, 0xed, 0xfe, 0x0f};
|
|
const auto [buffered_upload_data, buffered_upload_offset] =
|
|
upload.Map(buffered_expected.size(), 4);
|
|
Require(name, "buffered-copy upload map", buffered_upload_data != nullptr,
|
|
"buffered-copy source allocation failed");
|
|
std::memcpy(buffered_upload_data, buffered_expected.data(),
|
|
buffered_expected.size());
|
|
upload.Commit();
|
|
vk::BufferImageCopy buffered_region{};
|
|
buffered_region.bufferOffset = buffered_upload_offset;
|
|
buffered_region.bufferRowLength = buffered_info.pitch;
|
|
buffered_region.bufferImageHeight = buffered_info.extent.height;
|
|
buffered_region.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
|
|
buffered_region.imageExtent = buffered_info.extent;
|
|
buffered_source.Upload(std::span{&buffered_region, 1}, upload.Handle(),
|
|
buffered_upload_offset, buffered_expected.size());
|
|
Buffer copy_scratch(context, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
|
16);
|
|
buffered_destination.CopyImageWithBuffer(buffered_source, copy_scratch);
|
|
const auto [buffered_download_data, buffered_download_offset] =
|
|
download.Map(buffered_expected.size(), 4);
|
|
Require(name, "buffered-copy download map", buffered_download_data != nullptr,
|
|
"buffered-copy destination allocation failed");
|
|
download.Commit();
|
|
buffered_region.bufferOffset = buffered_download_offset;
|
|
buffered_destination.Download(std::span{&buffered_region, 1},
|
|
download.Handle(), buffered_download_offset,
|
|
buffered_expected.size());
|
|
scheduler.Finish();
|
|
download.Invalidate(download_offset, 128);
|
|
download.Invalidate(buffered_download_offset, buffered_expected.size());
|
|
Require(name, "depth/stencil mip contents",
|
|
std::memcmp(download_data, expected_depth.data(),
|
|
sizeof(expected_depth)) == 0 &&
|
|
std::memcmp(download_data + 64, expected_stencil.data(),
|
|
sizeof(expected_stencil)) == 0,
|
|
"combined depth/stencil mip copy lost an aspect");
|
|
Require(name, "buffered-copy chunk contents",
|
|
std::memcmp(buffered_download_data, buffered_expected.data(),
|
|
buffered_expected.size()) == 0,
|
|
"real buffered image copy lost a row across its scratch boundary");
|
|
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckDepthTextureEncoding() {
|
|
const ShaderTextureResource disabled_sampler_tweaks{{
|
|
0x05135600u,
|
|
0xc1600000u,
|
|
0x010dc1dfu,
|
|
0x91800924u,
|
|
0x00000000u,
|
|
0x00000000u,
|
|
0x00000000u,
|
|
0x00000000u,
|
|
}};
|
|
Require("DepthTextureEncoding", "disabled sampler tweaks",
|
|
disabled_sampler_tweaks.PerfMod5() == 0 &&
|
|
IsSupportedDepthTextureEncoding(disabled_sampler_tweaks),
|
|
"valid sampler modulation factor zero was rejected");
|
|
|
|
const ShaderTextureResource uncompressed_msaa{{
|
|
0x00705d00u,
|
|
0xc1600000u,
|
|
0x010dc1dfu,
|
|
0xe1810924u,
|
|
0x00000000u,
|
|
0x00700010u,
|
|
0x00000000u,
|
|
0x00000000u,
|
|
}};
|
|
Require("DepthTextureEncoding", "uncompressed 2x MSAA depth",
|
|
IsSupportedDepthTextureEncoding(uncompressed_msaa),
|
|
"valid uncompressed MSAA depth descriptor required an HTILE "
|
|
"compatibility flag");
|
|
|
|
auto r128_msaa = uncompressed_msaa;
|
|
std::fill(r128_msaa.fields + 4, r128_msaa.fields + 8, 0u);
|
|
Require("DepthTextureEncoding", "R128 uncompressed 2x MSAA depth",
|
|
IsSupportedDepthTextureEncoding(r128_msaa, true),
|
|
"valid R128 MSAA depth descriptor required omitted dwords");
|
|
|
|
const ShaderTextureResource compressed_descriptor{{
|
|
0x0104d500u,
|
|
0xc1600000u,
|
|
0x021bc3bfu,
|
|
0x91800924u,
|
|
0x00000000u,
|
|
0x00700000u,
|
|
0x80280000u,
|
|
0x000106d4u,
|
|
}};
|
|
Require("DepthTextureEncoding", "compressed HTILE descriptor",
|
|
IsSupportedDepthTextureEncoding(compressed_descriptor),
|
|
"valid compressed depth descriptor required a prior depth target");
|
|
|
|
const ShaderTextureResource first_use_depth{{
|
|
0x0225fc00u, 0x01600000u, 0x00000000u, 0x91800924u,
|
|
0x00000000u, 0x00700000u, 0x80280000u, 0x000225fdu,
|
|
}};
|
|
Require("DepthTextureEncoding", "PPSA30490 first-use 1x1 depth",
|
|
IsSupportedDepthTextureEncoding(first_use_depth),
|
|
"captured comparison texture required tracked HTILE metadata");
|
|
|
|
auto missing_metadata = compressed_descriptor;
|
|
missing_metadata.fields[6] &= 0x00ffffffu;
|
|
missing_metadata.fields[7] = 0;
|
|
auto out_of_range_metadata = compressed_descriptor;
|
|
out_of_range_metadata.fields[7] = TRACKER_ADDRESS_SIZE >> 16u;
|
|
auto unaligned_metadata = compressed_descriptor;
|
|
unaligned_metadata.fields[6] ^= 1u << 24u;
|
|
auto dcc_only_control = compressed_descriptor;
|
|
dcc_only_control.fields[6] |= 1u << 22u;
|
|
auto non_msaa_iterate_256 = compressed_descriptor;
|
|
non_msaa_iterate_256.fields[6] |= 1u << 10u;
|
|
Require(
|
|
"DepthTextureEncoding", "invalid HTILE encoding",
|
|
!IsSupportedDepthTextureEncoding(missing_metadata) &&
|
|
!IsSupportedDepthTextureEncoding(out_of_range_metadata) &&
|
|
!IsSupportedDepthTextureEncoding(unaligned_metadata) &&
|
|
!IsSupportedDepthTextureEncoding(dcc_only_control) &&
|
|
!IsSupportedDepthTextureEncoding(non_msaa_iterate_256),
|
|
"compressed sampled depth accepted an unsupported address or control");
|
|
|
|
std::printf("[host] %-32s ok\n", "DepthTextureEncoding");
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicStorageTextureResource() {
|
|
ShaderRecompiler::IR::ImageResource resource{};
|
|
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim3D;
|
|
resource.read = true;
|
|
resource.written = true;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicStorageTextureDescriptor() {
|
|
return {{0x00785d00u, 0x04700000u, 0x00080008u, 0xa1b00facu, 0x00000020u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicLinearStorageTextureResource() {
|
|
auto resource = BasicStorageTextureResource();
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = false;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicLinearStorageTextureDescriptor() {
|
|
return {{0x04bcc401u, 0xc3800000u, 0x021bc3bfu, 0x900003acu, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicBgraStorageTextureResource() {
|
|
auto resource = BasicStorageTextureResource();
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
resource.read = false;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicBgraStorageTextureDescriptor() {
|
|
return {{0x007c6500u, 0xc3800000u, 0x010dc1dfu, 0x91b00f2eu, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa06228R11G11B10StorageTextureDescriptor() {
|
|
return {{0x10c6b500u, 0xc2400000u, 0x010dc1dfu, 0x91b003acu, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa01530MaxMipStorageTextureDescriptor() {
|
|
return {{0x04a42900u, 0xc3e00000u, 0x000fc00fu, 0x91b0022cu, 0x00000000u,
|
|
0x00700050u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa01340MipRangeStorageTextureDescriptor() {
|
|
return {{0x0294dc00u, 0xc4700000u, 0x00b3c13fu, 0x91b31facu, 0x00000000u,
|
|
0x00700030u, 0xb07b0000u, 0x0002ac3cu}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa01340OverwideMipStorageTextureDescriptor() {
|
|
return {{0x0346c000u, 0xc4b00000u, 0x007fc07fu, 0x90990facu, 0x00000000u,
|
|
0x00700080u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource Ppsa01530MaxMipStorageTextureResource() {
|
|
auto resource = BasicBgraStorageTextureResource();
|
|
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource Ppsa02527R16FloatStorageTextureDescriptor() {
|
|
return {{0x00ce3500u, 0xc0d00000u, 0x010dc1dfu, 0x91b00204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa02527R32FloatStorageTextureDescriptor() {
|
|
return {{0x00cea900u, 0xc1600000u, 0x0086c0efu, 0x91b00204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa02527R8UnormStorageTextureDescriptor() {
|
|
return {{0x00c7d500u, 0xc0100000u, 0x0086c0efu, 0x91b00204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource BasicYzwxStorageTextureDescriptor() {
|
|
return {{0x00627801u, 0xc4d00000u, 0x0001c001u, 0x900009f5u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicArrayStorageTextureResource() {
|
|
auto resource = BasicLinearStorageTextureResource();
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicArrayStorageTextureDescriptor() {
|
|
return {{0x20179000u, 0x03800000u, 0x00000000u, 0xd1b00f2eu, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicUintArrayStorageTextureResource() {
|
|
auto resource = BasicArrayStorageTextureResource();
|
|
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicUintArrayStorageTextureDescriptor() {
|
|
return {{0x20179200u, 0x01400000u, 0x00000000u, 0xd1b00204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Standard4KBUintArrayStorageTextureDescriptor() {
|
|
return {{0x006c6600u, 0x01400000u, 0x00000000u, 0xd0500204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Standard64KBStorageTextureDescriptor() {
|
|
return {{0x011fab00u, 0xc3800000u, 0x0003c003u, 0xd0900facu, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource Ppsa14053DepthTileStorageTextureResource() {
|
|
return BasicUintArrayStorageTextureResource();
|
|
}
|
|
|
|
ShaderTextureResource Ppsa14053DepthTileStorageTextureDescriptor() {
|
|
return {{0x20144c00u, 0x00500000u, 0x00000000u, 0xd1800204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderTextureResource Ppsa10112D16StorageTextureDescriptor() {
|
|
return {{0x205b9000u, 0xc0700000u, 0x0021803bu, 0xd1800204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource BasicUintVolumeStorageTextureResource() {
|
|
auto resource = BasicStorageTextureResource();
|
|
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
resource.read = false;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource BasicUintVolumeStorageTextureDescriptor() {
|
|
return {{0x20180600u, 0xc0b00000u, 0x0003c003u, 0xa0000004u, 0x0000000fu,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
ShaderRecompiler::IR::ImageResource AtomicStorageTextureResource() {
|
|
auto resource = BasicLinearStorageTextureResource();
|
|
resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
resource.read = true;
|
|
resource.atomic = true;
|
|
return resource;
|
|
}
|
|
|
|
ShaderTextureResource AtomicStorageTextureDescriptor() {
|
|
return {{0x304bb700u, 0xc1400000u, 0x0000001fu, 0x91b00204u, 0x00000000u,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
}
|
|
|
|
[[noreturn]] void RunStorageTextureDescriptorDeathCase(const char *kind) {
|
|
auto resource = BasicStorageTextureResource();
|
|
auto descriptor = BasicStorageTextureDescriptor();
|
|
if (std::strcmp(kind, "linear-rgb1-read") == 0) {
|
|
resource = BasicLinearStorageTextureResource();
|
|
descriptor = BasicLinearStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "bgra-read") == 0) {
|
|
resource = BasicBgraStorageTextureResource();
|
|
descriptor = BasicBgraStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "r16-float-read") == 0) {
|
|
resource = BasicBgraStorageTextureResource();
|
|
descriptor = Ppsa02527R16FloatStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "r8-unorm-read") == 0) {
|
|
resource = BasicBgraStorageTextureResource();
|
|
descriptor = Ppsa02527R8UnormStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "yzwx-read") == 0) {
|
|
resource = BasicLinearStorageTextureResource();
|
|
descriptor = BasicYzwxStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "reserved-swizzle") == 0) {
|
|
resource = BasicLinearStorageTextureResource();
|
|
descriptor = BasicLinearStorageTextureDescriptor();
|
|
descriptor.fields[3] =
|
|
(descriptor.fields[3] & ~0xfffu) | DstSel(4, 5, 6, 2);
|
|
} else if (std::strcmp(kind, "resource") == 0) {
|
|
resource.written = false;
|
|
} else if (std::strcmp(kind, "type") == 0) {
|
|
descriptor.fields[3] =
|
|
(descriptor.fields[3] & 0x0fffffffu) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
} else if (std::strcmp(kind, "standard256b-volume") == 0) {
|
|
descriptor.fields[3] =
|
|
(descriptor.fields[3] & ~(0x1fu << 20u)) |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kStandard256B) << 20u);
|
|
} else if (std::strcmp(kind, "base-mip-out-of-resource") == 0) {
|
|
descriptor.fields[3] |= (1u << 12u) | (1u << 16u);
|
|
} else if (std::strcmp(kind, "dynamic-mip-out-of-resource") == 0) {
|
|
resource.mip_mode = ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
|
resource.mip_count = 2;
|
|
descriptor.fields[3] |= 1u << 16u;
|
|
} else if (std::strcmp(kind, "inverted-mip-range") == 0) {
|
|
descriptor.fields[3] |= 1u << 12u;
|
|
descriptor.fields[5] |= 1u << 4u;
|
|
} else if (std::strcmp(kind, "swizzle") == 0) {
|
|
descriptor.fields[3] =
|
|
(descriptor.fields[3] & ~0xfffu) | DstSel(4, 5, 6, 1);
|
|
} else if (std::strcmp(kind, "array-base-out-of-range") == 0) {
|
|
resource = BasicArrayStorageTextureResource();
|
|
descriptor = BasicArrayStorageTextureDescriptor();
|
|
descriptor.fields[4] |= 1u << 16u;
|
|
} else if (std::strcmp(kind, "reserved") == 0) {
|
|
descriptor.fields[1] |= 1u << 29u;
|
|
} else if (std::strcmp(kind, "uint-format") == 0) {
|
|
descriptor.fields[1] =
|
|
(descriptor.fields[1] & ~0x1ff00000u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k8_8_8_8UInt) << 20u);
|
|
} else if (std::strcmp(kind, "uint-resource-float-format") == 0) {
|
|
resource = BasicUintArrayStorageTextureResource();
|
|
descriptor = BasicArrayStorageTextureDescriptor();
|
|
} else if (std::strcmp(kind, "atomic-format") == 0) {
|
|
resource = AtomicStorageTextureResource();
|
|
descriptor = AtomicStorageTextureDescriptor();
|
|
descriptor.fields[1] =
|
|
(descriptor.fields[1] & ~0x1ff00000u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k8UInt) << 20u);
|
|
} else if (std::strcmp(kind, "depth-tile-read") == 0) {
|
|
resource = Ppsa14053DepthTileStorageTextureResource();
|
|
descriptor = Ppsa14053DepthTileStorageTextureDescriptor();
|
|
resource.read = true;
|
|
} else if (std::strcmp(kind, "depth-tile-extent") == 0) {
|
|
resource = Ppsa14053DepthTileStorageTextureResource();
|
|
descriptor = Ppsa14053DepthTileStorageTextureDescriptor();
|
|
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
descriptor.fields[3] =
|
|
(descriptor.fields[3] & 0x0fffffffu) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
descriptor.fields[4] |= 1u;
|
|
} else if (std::strcmp(kind, "depth-tile-fmask") == 0) {
|
|
resource = BasicArrayStorageTextureResource();
|
|
descriptor = Ppsa10112D16StorageTextureDescriptor();
|
|
descriptor.fields[1] =
|
|
(descriptor.fields[1] & ~(0x1ffu << 20u)) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::kFmask8_S4_F4) << 20u);
|
|
} else {
|
|
std::_Exit(0x7e);
|
|
}
|
|
ValidateStorageTexture(resource, descriptor, 0x10000);
|
|
std::_Exit(0x7f);
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
void CheckBasicStorageTextureDescriptor() {
|
|
const auto descriptor = BasicStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "descriptor",
|
|
descriptor.Base40() == 0x785d0000ull &&
|
|
descriptor.Width5() + 1u == 33 &&
|
|
descriptor.Height5() + 1u == 33 && descriptor.Depth() + 1u == 33,
|
|
"basic 3D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicStorageTextureResource(), descriptor, 0x10000);
|
|
|
|
const ShaderTextureResource extended{{0x204aca00u, 0xc4700000u, 0x000fc00fu,
|
|
0xa1b00facu, 0x0000003fu, 0x00700000u,
|
|
0x006b0000u, 0x00204b0au}};
|
|
auto extended_resource = BasicStorageTextureResource();
|
|
extended_resource.read = false;
|
|
Require("BasicStorageTexture", "extended descriptor",
|
|
extended.fields[6] != 0 && extended.fields[7] != 0,
|
|
"extended 3D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(extended_resource, extended, 0x400000);
|
|
|
|
const auto linear = BasicLinearStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "linear descriptor",
|
|
linear.Base40() == 0x4bcc40100ull && linear.Width5() + 1u == 3840 &&
|
|
linear.Height5() + 1u == 2160 && linear.Depth() + 1u == 1 &&
|
|
linear.Format() == Prospero::BufferFormat::k8_8_8_8UNorm &&
|
|
linear.TileMode() == Prospero::TileMode::kLinear,
|
|
"PPSA07429 linear 2D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicLinearStorageTextureResource(), linear,
|
|
0x1fa4000);
|
|
|
|
const auto bgra = BasicBgraStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "BGRA descriptor",
|
|
bgra.Base40() == 0x7c650000ull && bgra.Width5() + 1u == 1920 &&
|
|
bgra.Height5() + 1u == 1080 && bgra.Depth() + 1u == 1 &&
|
|
bgra.Format() == Prospero::BufferFormat::k8_8_8_8UNorm &&
|
|
bgra.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
bgra.DstSelXYZW() == DstSel(6, 5, 4, 7),
|
|
"PPSA02604 BGRA 2D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), bgra, 0x870000);
|
|
|
|
const ShaderTextureResource r128{
|
|
{0x0202e500u, 0xc8200000u, 0x010dc1dfu, 0x91b00facu, 0, 0, 0, 0}};
|
|
auto r128_resource = BasicBgraStorageTextureResource();
|
|
r128_resource.r128 = true;
|
|
Require("BasicStorageTexture", "PPSA01736 R128 descriptor",
|
|
r128.Base40() == 0x202e50000ull && r128.Width5() + 1u == 1920 &&
|
|
r128.Height5() + 1u == 1080 && r128.Depth() + 1u == 1 &&
|
|
r128.Format() == Prospero::BufferFormat::k8_8_8_8Srgb &&
|
|
r128.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
r128.DstSelXYZW() == DstSel(4, 5, 6, 7),
|
|
"PPSA01736 R128 storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(r128_resource, r128, 0x870000);
|
|
|
|
const auto r11g11b10 = Ppsa06228R11G11B10StorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA06228 R11G11B10 descriptor",
|
|
r11g11b10.Base40() == 0x10c6b50000ull &&
|
|
r11g11b10.Width5() + 1u == 1920 &&
|
|
r11g11b10.Height5() + 1u == 1080 && r11g11b10.Depth() + 1u == 1 &&
|
|
r11g11b10.Format() == Prospero::BufferFormat::k11_11_10Float &&
|
|
r11g11b10.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
r11g11b10.DstSelXYZW() == DstSel(4, 5, 6, 1),
|
|
"PPSA06228 R11G11B10 storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), r11g11b10,
|
|
0x870000);
|
|
ValidateStorageColorView(vk::Format::eB8G8R8A8Unorm,
|
|
vk::Format::eB10G11R11UfloatPack32,
|
|
r11g11b10.DstSelXYZW());
|
|
|
|
const auto max_mip = Ppsa01530MaxMipStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA01530 max-mip descriptor",
|
|
max_mip.Base40() == 0x4a4290000ull && max_mip.Width5() + 1u == 64 &&
|
|
max_mip.Height5() + 1u == 64 && max_mip.Depth() + 1u == 1 &&
|
|
max_mip.BaseLevel() == 0 && max_mip.LastLevel() == 0 &&
|
|
max_mip.MaxMip() == 5 &&
|
|
max_mip.Format() == Prospero::BufferFormat::k32_32UInt &&
|
|
max_mip.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
max_mip.DstSelXYZW() == DstSel(4, 5, 0, 1),
|
|
"PPSA01530 max-mip storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(Ppsa01530MaxMipStorageTextureResource(), max_mip,
|
|
0x20000);
|
|
auto mip_one = max_mip;
|
|
mip_one.fields[3] |= (1u << 12u) | (1u << 16u);
|
|
Require("BasicStorageTexture", "PPSA01530 mip-one descriptor",
|
|
mip_one.BaseLevel() == 1 && mip_one.LastLevel() == 1 &&
|
|
mip_one.MaxMip() == 5,
|
|
"PPSA01530 mip-one storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(Ppsa01530MaxMipStorageTextureResource(), mip_one,
|
|
0x20000);
|
|
|
|
const auto mip_range = Ppsa01340MipRangeStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA01340 mip-range descriptor",
|
|
mip_range.Base40() == 0x294dc0000ull &&
|
|
mip_range.Width5() + 1u == 1280 &&
|
|
mip_range.Height5() + 1u == 720 && mip_range.Depth() + 1u == 1 &&
|
|
mip_range.BaseLevel() == 1 && mip_range.LastLevel() == 3 &&
|
|
mip_range.MaxMip() == 3 &&
|
|
mip_range.Format() == Prospero::BufferFormat::k16_16_16_16Float &&
|
|
mip_range.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
mip_range.DstSelXYZW() == DstSel(4, 5, 6, 7),
|
|
"PPSA01340 mip-range storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), mip_range,
|
|
0xa30000);
|
|
|
|
const auto overwide_mip = Ppsa01340OverwideMipStorageTextureDescriptor();
|
|
Require(
|
|
"BasicStorageTexture", "PPSA01340 over-wide mip view",
|
|
overwide_mip.Base40() == 0x346c00000ull &&
|
|
overwide_mip.Width5() + 1u == 512 &&
|
|
overwide_mip.Height5() + 1u == 512 && overwide_mip.BaseLevel() == 0 &&
|
|
overwide_mip.LastLevel() == 9 && overwide_mip.MaxMip() == 8 &&
|
|
overwide_mip.Format() == Prospero::BufferFormat::k32_32_32_32UInt &&
|
|
overwide_mip.TileMode() == Prospero::TileMode::kStandard64KB,
|
|
"captured over-wide storage mip view is malformed");
|
|
TileSizeAlign overwide_size{};
|
|
TileGetTextureTotalSize(overwide_mip.Format(), overwide_mip.Width5() + 1u,
|
|
overwide_mip.Height5() + 1u, 1,
|
|
overwide_mip.MaxMip() + 1u, overwide_mip.TileMode(),
|
|
false, overwide_size);
|
|
Require("BasicStorageTexture", "PPSA01340 over-wide mip footprint",
|
|
overwide_size.size == 0x560000 && overwide_size.align == 0x10000,
|
|
"captured nine-level Standard64KB footprint changed");
|
|
ValidateStorageTexture(Ppsa01530MaxMipStorageTextureResource(), overwide_mip,
|
|
overwide_size.size);
|
|
|
|
const auto r16_float = Ppsa02527R16FloatStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA02527 R16F descriptor",
|
|
r16_float.Base40() == 0xce350000ull &&
|
|
r16_float.Width5() + 1u == 1920 &&
|
|
r16_float.Height5() + 1u == 1080 && r16_float.Depth() + 1u == 1 &&
|
|
r16_float.Format() == Prospero::BufferFormat::k16Float &&
|
|
r16_float.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
r16_float.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA02527 R16F 2D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), r16_float,
|
|
0x480000);
|
|
|
|
const auto r32_float = Ppsa02527R32FloatStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA02527 R32F descriptor",
|
|
r32_float.Base40() == 0xcea90000ull &&
|
|
r32_float.Width5() + 1u == 960 &&
|
|
r32_float.Height5() + 1u == 540 && r32_float.Depth() + 1u == 1 &&
|
|
r32_float.Format() == Prospero::BufferFormat::k32Float &&
|
|
r32_float.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
r32_float.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA02527 R32F 2D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), r32_float,
|
|
0x280000);
|
|
|
|
const auto r8_unorm = Ppsa02527R8UnormStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA02527 R8 UNORM descriptor",
|
|
r8_unorm.Base40() == 0xc7d50000ull && r8_unorm.Width5() + 1u == 960 &&
|
|
r8_unorm.Height5() + 1u == 540 && r8_unorm.Depth() + 1u == 1 &&
|
|
r8_unorm.Format() == Prospero::BufferFormat::k8UNorm &&
|
|
r8_unorm.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
r8_unorm.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA02527 R8 UNORM 2D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), r8_unorm, 0xc0000);
|
|
|
|
const auto yzwx = BasicYzwxStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "YZWX descriptor",
|
|
yzwx.Base40() == 0x62780100ull && yzwx.Width5() + 1u == 8 &&
|
|
yzwx.Height5() + 1u == 8 && yzwx.Depth() + 1u == 1 &&
|
|
yzwx.Format() == Prospero::BufferFormat::k32_32_32_32Float &&
|
|
yzwx.TileMode() == Prospero::TileMode::kLinear &&
|
|
yzwx.DstSelXYZW() == DstSel(5, 6, 7, 4),
|
|
"PPSA04181 linear YZWX storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicLinearStorageTextureResource(), yzwx, 0x800);
|
|
auto all_swizzles = yzwx;
|
|
all_swizzles.fields[1] =
|
|
(all_swizzles.fields[1] & ~0x1ff00000u) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k16_16_16_16Float) << 20u);
|
|
uint32_t valid_storage_swizzles = 0;
|
|
for (uint32_t swizzle = 0; swizzle <= 0xfffu; swizzle++) {
|
|
if (!IsValidImageSwizzle(swizzle)) {
|
|
continue;
|
|
}
|
|
all_swizzles.fields[3] = (all_swizzles.fields[3] & ~0xfffu) | swizzle;
|
|
ValidateStorageTexture(BasicLinearStorageTextureResource(), all_swizzles,
|
|
0x800);
|
|
ValidateStorageColorView(vk::Format::eR16G16B16A16Sfloat,
|
|
vk::Format::eR16G16B16A16Sfloat, swizzle);
|
|
ValidateStorageColorView(vk::Format::eR8G8B8A8Srgb,
|
|
vk::Format::eR8G8B8A8Unorm, swizzle);
|
|
ValidateStorageColorView(vk::Format::eB8G8R8A8Srgb,
|
|
vk::Format::eR8G8B8A8Unorm, swizzle);
|
|
valid_storage_swizzles++;
|
|
}
|
|
Require("BasicStorageTexture", "all write swizzles",
|
|
valid_storage_swizzles == 1296,
|
|
"valid write-only storage image mappings were rejected");
|
|
|
|
const ShaderTextureResource cube{{0x025bca00u, 0xc4700000u, 0x003fc03fu,
|
|
0xb1b00facu, 0x00000005u, 0x00700080u,
|
|
0x00000000u, 0x00000000u}};
|
|
auto cube_resource = BasicArrayStorageTextureResource();
|
|
cube_resource.cube = true;
|
|
Require("BasicStorageTexture", "PPSA07429 cube descriptor",
|
|
cube.Base40() == 0x25bca0000ull && cube.Width5() + 1u == 256 &&
|
|
cube.Height5() + 1u == 256 && cube.Depth() + 1u == 6 &&
|
|
cube.BaseArray5() == 0 && cube.MaxMip() == 8 &&
|
|
cube.Type() == Prospero::ImageType::kCube &&
|
|
cube.Format() == Prospero::BufferFormat::k16_16_16_16Float &&
|
|
cube.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
cube.DstSelXYZW() == DstSel(4, 5, 6, 7),
|
|
"PPSA07429 cube storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(cube_resource, cube, 0x420000);
|
|
|
|
const auto array = BasicArrayStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "2D-array descriptor",
|
|
array.Base40() == 0x2017900000ull && array.Width5() + 1u == 1 &&
|
|
array.Height5() + 1u == 1 && array.Depth() + 1u == 1 &&
|
|
array.BaseArray5() == 0 &&
|
|
array.Type() == Prospero::ImageType::kColor2DArray &&
|
|
array.Format() == Prospero::BufferFormat::k8_8_8_8UNorm &&
|
|
array.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
array.DstSelXYZW() == DstSel(6, 5, 4, 7),
|
|
"PPSA21268 2D-array storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicArrayStorageTextureResource(), array, 0x10000);
|
|
const ShaderTextureResource mip_array{{0x20268d00u, 0xc4700000u, 0x001fc01fu,
|
|
0xd1b11facu, 0x00000000u, 0x00700070u,
|
|
0x00000000u, 0x00000000u}};
|
|
Require("BasicStorageTexture", "PPSA14457 mip-one 2D-array descriptor",
|
|
mip_array.BaseLevel() == 1 && mip_array.LastLevel() == 1 &&
|
|
mip_array.MaxMip() == 7 &&
|
|
mip_array.Type() == Prospero::ImageType::kColor2DArray &&
|
|
mip_array.Depth() == 0 && mip_array.BaseArray5() == 0,
|
|
"PPSA14457 mip-one 2D-array storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicArrayStorageTextureResource(), mip_array,
|
|
0x30000);
|
|
|
|
const auto uint_array = BasicUintArrayStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "uint 2D-array descriptor",
|
|
uint_array.Base40() == 0x2017920000ull &&
|
|
uint_array.Width5() + 1u == 1 && uint_array.Height5() + 1u == 1 &&
|
|
uint_array.Depth() + 1u == 1 && uint_array.BaseArray5() == 0 &&
|
|
uint_array.Type() == Prospero::ImageType::kColor2DArray &&
|
|
uint_array.Format() == Prospero::BufferFormat::k32UInt &&
|
|
uint_array.TileMode() == Prospero::TileMode::kRenderTarget &&
|
|
uint_array.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA21268 uint 2D-array storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicUintArrayStorageTextureResource(), uint_array,
|
|
0x10000);
|
|
|
|
const ShaderTextureResource standard256b{{
|
|
0x204e4900u,
|
|
0x43c00000u,
|
|
0x00004000u,
|
|
0x90100facu,
|
|
0x00000000u,
|
|
0x00700000u,
|
|
0x00000000u,
|
|
0x00000000u,
|
|
}};
|
|
auto standard256b_resource = BasicBgraStorageTextureResource();
|
|
standard256b_resource.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
TileSizeAlign standard256b_size{};
|
|
TileGetTextureTotalSize(standard256b.Format(), standard256b.Width5() + 1u,
|
|
standard256b.Height5() + 1u,
|
|
standard256b.Depth() + 1u, 1, standard256b.TileMode(),
|
|
false, standard256b_size);
|
|
Require("BasicStorageTexture", "Standard256B uint 2D descriptor",
|
|
standard256b.Width5() + 1u == 2 && standard256b.Height5() + 1u == 2 &&
|
|
standard256b.Depth() + 1u == 1 &&
|
|
standard256b.Type() == Prospero::ImageType::kColor2D &&
|
|
standard256b.Format() == Prospero::BufferFormat::k8_8_8_8UInt &&
|
|
standard256b.TileMode() == Prospero::TileMode::kStandard256B &&
|
|
standard256b.DstSelXYZW() == DstSel(4, 5, 6, 7) &&
|
|
standard256b_size.size == 0x100 &&
|
|
standard256b_size.align == 0x100,
|
|
"captured PPSA08511 Standard256B storage descriptor is malformed");
|
|
ValidateStorageTexture(standard256b_resource, standard256b,
|
|
standard256b_size.size);
|
|
|
|
const auto standard4kb_array = Standard4KBUintArrayStorageTextureDescriptor();
|
|
TileSizeAlign standard4kb_size{};
|
|
TileGetTextureTotalSize(standard4kb_array.Format(), 1, 1, 1, 1,
|
|
standard4kb_array.TileMode(), false,
|
|
standard4kb_size);
|
|
Require(
|
|
"BasicStorageTexture", "Standard4KB uint 2D-array descriptor",
|
|
standard4kb_array.Type() == Prospero::ImageType::kColor2DArray &&
|
|
standard4kb_array.Format() == Prospero::BufferFormat::k32UInt &&
|
|
standard4kb_array.TileMode() == Prospero::TileMode::kStandard4KB &&
|
|
standard4kb_array.DstSelXYZW() == DstSel(4, 0, 0, 1) &&
|
|
standard4kb_size.size == 0x1000 && standard4kb_size.align == 0x1000,
|
|
"captured Standard4KB uint 2D-array descriptor is malformed");
|
|
ValidateStorageTexture(BasicUintArrayStorageTextureResource(),
|
|
standard4kb_array, standard4kb_size.size);
|
|
|
|
auto based_standard4kb_array = standard4kb_array;
|
|
based_standard4kb_array.fields[0] = 0x006c6800u;
|
|
based_standard4kb_array.fields[4] = 0x00010001u;
|
|
TileSizeAlign based_standard4kb_size{};
|
|
TileGetTextureTotalSize(based_standard4kb_array.Format(), 1, 1,
|
|
based_standard4kb_array.Depth() + 1u, 1,
|
|
based_standard4kb_array.TileMode(), false,
|
|
based_standard4kb_size);
|
|
Require("BasicStorageTexture", "based Standard4KB array view",
|
|
based_standard4kb_array.Base40() == 0x6c680000ull &&
|
|
based_standard4kb_array.BaseArray5() == 1 &&
|
|
based_standard4kb_array.Depth() == 1 &&
|
|
based_standard4kb_size.size == 0x2000 &&
|
|
based_standard4kb_size.align == 0x1000,
|
|
"captured based Standard4KB array view is malformed");
|
|
ValidateStorageTexture(BasicUintArrayStorageTextureResource(),
|
|
based_standard4kb_array, based_standard4kb_size.size);
|
|
|
|
const auto standard64kb = Standard64KBStorageTextureDescriptor();
|
|
TileSizeAlign standard64kb_size{};
|
|
TileGetTextureTotalSize(standard64kb.Format(), standard64kb.Width5() + 1u,
|
|
standard64kb.Height5() + 1u,
|
|
standard64kb.Depth() + 1u, 1, standard64kb.TileMode(),
|
|
false, standard64kb_size);
|
|
Require("BasicStorageTexture", "Standard64KB 2D descriptor",
|
|
standard64kb.Type() == Prospero::ImageType::kColor2DArray &&
|
|
standard64kb.Format() == Prospero::BufferFormat::k8_8_8_8UNorm &&
|
|
standard64kb.TileMode() == Prospero::TileMode::kStandard64KB &&
|
|
standard64kb.DstSelXYZW() == DstSel(4, 5, 6, 7) &&
|
|
standard64kb_size.size == 0x10000 &&
|
|
standard64kb_size.align == 0x10000,
|
|
"captured Standard64KB storage descriptor is malformed");
|
|
ValidateStorageTexture(BasicBgraStorageTextureResource(), standard64kb,
|
|
standard64kb_size.size);
|
|
|
|
const auto uint_volume = BasicUintVolumeStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "uint 3D descriptor",
|
|
uint_volume.Base40() == 0x2018060000ull &&
|
|
uint_volume.Width5() + 1u == 16 &&
|
|
uint_volume.Height5() + 1u == 16 &&
|
|
uint_volume.Depth() + 1u == 16 && uint_volume.BaseArray5() == 0 &&
|
|
uint_volume.Type() == Prospero::ImageType::kColor3D &&
|
|
uint_volume.Format() == Prospero::BufferFormat::k16UInt &&
|
|
uint_volume.TileMode() == Prospero::TileMode::kLinear &&
|
|
uint_volume.DstSelXYZW() == DstSel(4, 0, 0, 0),
|
|
"PPSA21268 uint 3D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicUintVolumeStorageTextureResource(), uint_volume,
|
|
0x10000);
|
|
|
|
const ShaderTextureResource tiled_uint_volume{
|
|
{0x1ac0e530u, 0xc0b00000u, 0x0003c003u, 0xa0500004u, 0x0000000fu,
|
|
0x00700000u, 0x00000000u, 0x00000000u}};
|
|
Require("BasicStorageTexture", "tiled uint 3D descriptor",
|
|
tiled_uint_volume.Width5() + 1u == 16 &&
|
|
tiled_uint_volume.Height5() + 1u == 16 &&
|
|
tiled_uint_volume.Depth() + 1u == 16 &&
|
|
tiled_uint_volume.Type() == Prospero::ImageType::kColor3D &&
|
|
tiled_uint_volume.TileMode() == Prospero::TileMode::kStandard4KB,
|
|
"captured tiled uint 3D storage descriptor fixture is malformed");
|
|
ValidateStorageTexture(BasicUintVolumeStorageTextureResource(),
|
|
tiled_uint_volume, 0x2000);
|
|
|
|
const auto depth_tile = Ppsa14053DepthTileStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA14053 depth-tile descriptor",
|
|
depth_tile.Base40() == 0x20144c0000ull &&
|
|
depth_tile.Width5() + 1u == 1 && depth_tile.Height5() + 1u == 1 &&
|
|
depth_tile.Depth() + 1u == 1 && depth_tile.BaseArray5() == 0 &&
|
|
depth_tile.Type() == Prospero::ImageType::kColor2DArray &&
|
|
depth_tile.Format() == Prospero::BufferFormat::k8UInt &&
|
|
depth_tile.TileMode() == Prospero::TileMode::kDepth &&
|
|
depth_tile.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA14053 write-only depth-tile storage descriptor fixture is "
|
|
"malformed");
|
|
ValidateStorageTexture(Ppsa14053DepthTileStorageTextureResource(), depth_tile,
|
|
0x10000);
|
|
const auto d16_depth_tile = Ppsa10112D16StorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "PPSA10112 D16 depth-tile descriptor",
|
|
d16_depth_tile.Base40() == 0x205b900000ull &&
|
|
d16_depth_tile.Width5() + 1u == 240 &&
|
|
d16_depth_tile.Height5() + 1u == 135 &&
|
|
d16_depth_tile.Depth() + 1u == 1 &&
|
|
d16_depth_tile.Type() == Prospero::ImageType::kColor2DArray &&
|
|
d16_depth_tile.Format() == Prospero::BufferFormat::k16UNorm &&
|
|
d16_depth_tile.TileMode() == Prospero::TileMode::kDepth &&
|
|
d16_depth_tile.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA10112 writable D16 depth-plane descriptor fixture is malformed");
|
|
const auto d16_pitch =
|
|
TileGetTexturePitch(d16_depth_tile.Format(), d16_depth_tile.Width5() + 1u,
|
|
d16_depth_tile.TileMode());
|
|
TileSizeAlign d16_size{};
|
|
TileGetTextureTotalSize(d16_depth_tile.Format(), d16_depth_tile.Width5() + 1u,
|
|
d16_depth_tile.Height5() + 1u,
|
|
d16_depth_tile.Depth() + 1u, 1,
|
|
d16_depth_tile.TileMode(), false, d16_size);
|
|
Require("BasicStorageTexture", "PPSA10112 D16 depth-tile footprint",
|
|
d16_pitch == 256 && d16_size.size == 0x20000 &&
|
|
d16_size.align == 0x10000,
|
|
"PPSA10112 writable D16 depth-plane footprint is incorrect");
|
|
ValidateStorageTexture(BasicArrayStorageTextureResource(), d16_depth_tile,
|
|
d16_size.size);
|
|
auto depth_tile_r32 = depth_tile;
|
|
depth_tile_r32.fields[1] =
|
|
(depth_tile_r32.fields[1] & ~(0x1ffu << 20u)) |
|
|
(static_cast<uint32_t>(Prospero::BufferFormat::k32UInt) << 20u);
|
|
depth_tile_r32.fields[3] =
|
|
(depth_tile_r32.fields[3] & ~(0xfu << 28u)) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
auto depth_tile_r32_resource = Ppsa14053DepthTileStorageTextureResource();
|
|
depth_tile_r32_resource.dimension =
|
|
ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
ValidateStorageTexture(depth_tile_r32_resource, depth_tile_r32, 0x10000);
|
|
Require("BasicStorageTexture", "R32_UINT replicated write mapping",
|
|
IsValidImageSwizzle(DstSel(4, 4, 4, 4)),
|
|
"single-channel replicated destination selection was rejected");
|
|
|
|
const auto atomic = AtomicStorageTextureDescriptor();
|
|
Require("BasicStorageTexture", "atomic R32_UINT descriptor",
|
|
atomic.Width5() + 1u == 128 && atomic.Height5() + 1u == 1 &&
|
|
atomic.Depth() + 1u == 1 &&
|
|
atomic.Type() == Prospero::ImageType::kColor2D &&
|
|
atomic.Format() == Prospero::BufferFormat::k32UInt &&
|
|
atomic.DstSelXYZW() == DstSel(4, 0, 0, 1),
|
|
"PPSA22102 image-atomic descriptor fixture is malformed");
|
|
ValidateStorageTexture(AtomicStorageTextureResource(), atomic, 0x10000);
|
|
|
|
char path[MAX_PATH]{};
|
|
Require("BasicStorageTexture", "host",
|
|
GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
|
|
"GetModuleFileName failed");
|
|
for (const char *kind : {"resource",
|
|
"type",
|
|
"standard256b-volume",
|
|
"base-mip-out-of-resource",
|
|
"dynamic-mip-out-of-resource",
|
|
"inverted-mip-range",
|
|
"swizzle",
|
|
"linear-rgb1-read",
|
|
"bgra-read",
|
|
"r16-float-read",
|
|
"r8-unorm-read",
|
|
"yzwx-read",
|
|
"reserved-swizzle",
|
|
"array-base-out-of-range",
|
|
"reserved",
|
|
"uint-format",
|
|
"uint-resource-float-format",
|
|
"atomic-format",
|
|
"depth-tile-read",
|
|
"depth-tile-extent",
|
|
"depth-tile-fmask"}) {
|
|
std::string command = std::string("\"") + path +
|
|
"\" --storage-texture-descriptor-death " + kind;
|
|
std::vector<char> mutable_command(command.begin(), command.end());
|
|
mutable_command.push_back('\0');
|
|
STARTUPINFOA startup{sizeof(startup)};
|
|
PROCESS_INFORMATION process{};
|
|
Require("BasicStorageTexture", "host",
|
|
CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr,
|
|
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup,
|
|
&process) != 0,
|
|
"CreateProcess failed");
|
|
Require("BasicStorageTexture", "host",
|
|
WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0,
|
|
"descriptor death case timed out");
|
|
DWORD exit_code = 0;
|
|
const bool exited = GetExitCodeProcess(process.hProcess, &exit_code) != 0;
|
|
CloseHandle(process.hThread);
|
|
CloseHandle(process.hProcess);
|
|
Require("BasicStorageTexture", "host", exited && exit_code == 321,
|
|
std::string(kind) +
|
|
" storage descriptor did not report a fatal error");
|
|
}
|
|
std::printf("[host] %-32s ok\n", "BasicStorageTextureDescriptor");
|
|
}
|
|
#endif
|
|
|
|
void CheckStorageTextureLinearUploadLayout() {
|
|
constexpr auto format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
constexpr uint32_t width = 3840;
|
|
constexpr uint32_t height = 2160;
|
|
constexpr uint32_t depth = 1;
|
|
constexpr auto tile = Prospero::TileMode::kLinear;
|
|
const auto pitch = TileGetTexturePitch(format, width, tile);
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, depth, 1, tile, false, total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, 1, depth, tile, total.size,
|
|
true, false, "StorageTextureLinearTest");
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
Require("StorageTextureLinearUpload", "layout",
|
|
pitch == width && total.size == 0x1fa4000 && total.align == 256 &&
|
|
layout.surface.description.tile_mode == tile &&
|
|
layout.pitch == width && layout.slice_stride == total.size &&
|
|
regions.size() == 1 && regions[0].bufferOffset == 0 &&
|
|
regions[0].imageExtent.width == width &&
|
|
regions[0].imageExtent.height == height &&
|
|
regions[0].bufferRowLength == width,
|
|
"linear RGBA8 storage upload lost Prospero pitch or allocation size");
|
|
std::printf("[host] %-32s ok\n", "StorageTextureLinearUpload");
|
|
}
|
|
|
|
void CheckStorageTextureDepthTileUploadLayout() {
|
|
constexpr auto format = Prospero::BufferFormat::k8UInt;
|
|
constexpr uint32_t width = 1;
|
|
constexpr uint32_t height = 1;
|
|
constexpr uint32_t depth = 1;
|
|
constexpr auto tile = Prospero::TileMode::kDepth;
|
|
const auto pitch = TileGetTexturePitch(format, width, tile);
|
|
TileSizeAlign slice{};
|
|
TileSizeAlign total{};
|
|
TileSizeOffset level{};
|
|
TilePaddedSize padded{};
|
|
TileGetTextureSize(format, width, height, 1, tile, &slice, &level, &padded);
|
|
TileGetTextureTotalSize(format, width, height, depth, 1, tile, false, total);
|
|
const auto layout =
|
|
TextureCalcUploadLayout(format, width, height, 1, depth, tile, total.size,
|
|
true, false, "StorageTextureDepthTileTest");
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
Require("StorageTextureDepthTileUpload", "PPSA14053 layout",
|
|
pitch == 256 && padded.width == 256 && padded.height == 256 &&
|
|
slice.size == 0x10000 && slice.align == 0x10000 &&
|
|
level.size == slice.size && level.offset == 0 &&
|
|
total.size == slice.size && total.align == slice.align &&
|
|
layout.surface.description.tile_mode == tile &&
|
|
layout.surface.texture.block.family ==
|
|
TileBlockFamily::Depth64KB &&
|
|
layout.pitch == pitch && layout.slice_stride == pitch &&
|
|
layout.source_slice_stride == total.size &&
|
|
layout.mips[0].size == pitch &&
|
|
layout.surface.mips[0].size == total.size &&
|
|
regions.size() == 1 && regions[0].bufferOffset == 0 &&
|
|
regions[0].imageExtent.width == width &&
|
|
regions[0].imageExtent.height == height &&
|
|
regions[0].bufferRowLength == pitch,
|
|
"1x1 R8_UINT depth tile lost its 64 KiB source footprint");
|
|
std::printf("[host] %-32s ok\n", "StorageTextureDepthTileUpload");
|
|
}
|
|
|
|
void CheckImageSamplerSpecialization() {
|
|
ShaderSamplerResource filtered_sampler{};
|
|
filtered_sampler.fields[2] =
|
|
(1u << 20u) | (3u << 22u) | (2u << 24u) | (2u << 26u);
|
|
filtered_sampler.SetPointFiltering();
|
|
Require("ImageSamplerSpecialization", "forced point sampler",
|
|
filtered_sampler.XyMagFilter() == 0u &&
|
|
filtered_sampler.XyMinFilter() == 0u &&
|
|
filtered_sampler.ZFilter() == 1u &&
|
|
filtered_sampler.MipFilter() == 1u,
|
|
"point-only image format retained a filtered sampler mode");
|
|
|
|
ShaderSamplerResource base_mip_sampler{};
|
|
base_mip_sampler.fields[2] = (3u << 20u) | (1u << 22u) | (2u << 24u);
|
|
base_mip_sampler.SetPointFiltering();
|
|
Require("ImageSamplerSpecialization", "base-mip point sampler",
|
|
base_mip_sampler.XyMagFilter() == 0u &&
|
|
base_mip_sampler.XyMinFilter() == 0u &&
|
|
base_mip_sampler.ZFilter() == 1u &&
|
|
base_mip_sampler.MipFilter() == 0u,
|
|
"forcing point filtering enabled mipmapping on a base-mip sampler");
|
|
|
|
ShaderRecompiler::IR::Program mixed_sampler_program;
|
|
mixed_sampler_program.resource_tracking_complete = true;
|
|
ShaderRecompiler::IR::ImageResource sampled_image;
|
|
sampled_image.resource_class =
|
|
ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
|
sampled_image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
sampled_image.read = true;
|
|
mixed_sampler_program.info.images = {sampled_image, sampled_image,
|
|
sampled_image};
|
|
mixed_sampler_program.info.samplers.push_back({0u, 4u});
|
|
mixed_sampler_program.info.sampled_pairs = {
|
|
{0u, 0u, 8u}, {1u, 0u, 12u}, {2u, 0u, 16u}};
|
|
ShaderRecompiler::IR::MemoryInfo signed_memory;
|
|
signed_memory.kind = ShaderRecompiler::IR::ResourceKind::Image;
|
|
signed_memory.resource = 2u;
|
|
signed_memory.sampler = 0u;
|
|
mixed_sampler_program.memory_info.push_back(signed_memory);
|
|
mixed_sampler_program.block_storage.push_back(
|
|
std::make_unique<ShaderRecompiler::IR::Block>());
|
|
auto *mixed_sampler_block = mixed_sampler_program.block_storage.back().get();
|
|
mixed_sampler_program.blocks.push_back(mixed_sampler_block);
|
|
using ShaderRecompiler::IR::Value;
|
|
using ShaderRecompiler::IR::ValueOpcode;
|
|
auto &image_handle = mixed_sampler_block->AppendNewInst(
|
|
ValueOpcode::GetImageResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u)});
|
|
auto &sampler_handle = mixed_sampler_block->AppendNewInst(
|
|
ValueOpcode::GetSamplerResource,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
|
auto &image_address = mixed_sampler_block->AppendNewInst(
|
|
ValueOpcode::MakeImageAddress,
|
|
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
|
Value(0u)});
|
|
const ShaderRecompiler::IR::MemoryFlags signed_memory_flags{0u, 16u};
|
|
uint64_t signed_memory_flag_bits = 0;
|
|
std::memcpy(&signed_memory_flag_bits, &signed_memory_flags,
|
|
sizeof(signed_memory_flags));
|
|
mixed_sampler_block->AppendNewInst(
|
|
ValueOpcode::ImageSampleRaw,
|
|
{Value(&image_handle), Value(&sampler_handle), Value(&image_address)},
|
|
signed_memory_flag_bits);
|
|
|
|
ShaderRecompiler::IR::DescriptorValue native_image_descriptor{};
|
|
native_image_descriptor.dword_count = 8;
|
|
native_image_descriptor.dwords[0] = 0x1000u;
|
|
native_image_descriptor.dwords[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k32Float) << 20u;
|
|
native_image_descriptor.dwords[2] = 3u | (3u << 14u);
|
|
native_image_descriptor.dwords[3] =
|
|
DstSel(4, 5, 6, 7) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
auto packed_image_descriptor = native_image_descriptor;
|
|
packed_image_descriptor.dwords[0] = 0x2000u;
|
|
packed_image_descriptor.dwords[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k11_11_10UInt) << 20u;
|
|
auto signed_image_descriptor = native_image_descriptor;
|
|
signed_image_descriptor.dwords[0] = 0x3000u;
|
|
signed_image_descriptor.dwords[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k32SInt) << 20u;
|
|
|
|
ShaderRecompiler::IR::DescriptorValue sampler_descriptor{};
|
|
sampler_descriptor.dword_count = 4;
|
|
const std::array descriptors{native_image_descriptor, packed_image_descriptor,
|
|
signed_image_descriptor, sampler_descriptor};
|
|
mixed_sampler_program.descriptor_sources.resize(descriptors.size());
|
|
for (u32 source = 0; source < descriptors.size(); source++) {
|
|
auto &destination = mixed_sampler_program.descriptor_sources[source];
|
|
destination.dword_count = descriptors[source].dword_count;
|
|
for (u32 dword = 0; dword < destination.dword_count; dword++) {
|
|
destination.dwords[dword] = Value(descriptors[source].dwords[dword]);
|
|
}
|
|
}
|
|
for (u32 image = 0; image < mixed_sampler_program.info.images.size(); image++) {
|
|
mixed_sampler_program.info.images[image].source = image;
|
|
}
|
|
mixed_sampler_program.info.samplers[0].source = 3;
|
|
mixed_sampler_program.srt_plan_complete = true;
|
|
auto mixed_sampler_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(mixed_sampler_program);
|
|
ShaderRecompiler::IR::ResourceSnapshot mixed_sampler_snapshot;
|
|
ShaderRecompiler::IR::ResourceSpecialization mixed_sampler_specialization;
|
|
Require("ImageSamplerSpecialization", "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
mixed_sampler_plan, {}, mixed_sampler_snapshot,
|
|
mixed_sampler_specialization),
|
|
"mixed sampler descriptors could not be materialized");
|
|
ShaderRecompiler::IR::ApplyResourceSpecialization(
|
|
mixed_sampler_program, mixed_sampler_specialization);
|
|
Require("ImageSamplerSpecialization", "mixed sampler variant",
|
|
mixed_sampler_program.info.samplers.size() == 2u &&
|
|
!mixed_sampler_program.info.samplers[0].force_point_filtering &&
|
|
mixed_sampler_program.info.samplers[1].force_point_filtering &&
|
|
mixed_sampler_program.info.sampled_pairs[0].sampler == 0u &&
|
|
mixed_sampler_program.info.sampled_pairs[1].sampler == 1u &&
|
|
mixed_sampler_program.info.sampled_pairs[2].sampler == 1u &&
|
|
mixed_sampler_program.memory_info[0].sampler == 1u &&
|
|
mixed_sampler_snapshot.samplers.size() == 2u,
|
|
"a shared float/integer sampler was not split into point and native "
|
|
"variants or the signed instruction retained the native sampler");
|
|
|
|
std::printf("[host] %-32s ok\n", "ImageSpecializationPipelineId");
|
|
}
|
|
|
|
void CheckNativeImageDescriptorTypes() {
|
|
using ShaderRecompiler::IR::DescriptorBindingKind;
|
|
using ShaderRecompiler::IR::FirstImageBinding;
|
|
using ShaderRecompiler::IR::FirstStorageImageBinding;
|
|
|
|
for (uint32_t value = FirstImageBinding;
|
|
value < static_cast<uint32_t>(DescriptorBindingKind::Samplers);
|
|
value++) {
|
|
const auto expected = value < FirstStorageImageBinding
|
|
? vk::DescriptorType::eSampledImage
|
|
: vk::DescriptorType::eStorageImage;
|
|
Require("NativeImageDescriptorTypes", "image binding",
|
|
NativeDescriptorType(static_cast<DescriptorBindingKind>(value)) ==
|
|
expected,
|
|
"generated image binding has the wrong Vulkan descriptor type");
|
|
}
|
|
Require("NativeImageDescriptorTypes", "non-image bindings",
|
|
NativeDescriptorType(DescriptorBindingKind::Samplers) ==
|
|
vk::DescriptorType::eSampler &&
|
|
NativeDescriptorType(DescriptorBindingKind::Buffers) ==
|
|
vk::DescriptorType::eStorageBuffer &&
|
|
NativeDescriptorType(DescriptorBindingKind::Gds) ==
|
|
vk::DescriptorType::eStorageBuffer &&
|
|
NativeDescriptorType(DescriptorBindingKind::BdaPagetable) ==
|
|
vk::DescriptorType::eStorageBuffer &&
|
|
NativeDescriptorType(DescriptorBindingKind::FaultBuffer) ==
|
|
vk::DescriptorType::eStorageBuffer &&
|
|
NativeDescriptorType(DescriptorBindingKind::FlattenedSrt) ==
|
|
vk::DescriptorType::eStorageBuffer &&
|
|
NativeDescriptorType(DescriptorBindingKind::ShaderData) ==
|
|
vk::DescriptorType::eStorageBuffer,
|
|
"non-image binding has the wrong Vulkan descriptor type");
|
|
std::printf("[host] %-32s ok\n", "NativeImageDescriptorTypes");
|
|
}
|
|
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
void CheckShaderRecompilerFatalContracts() {
|
|
ExpectFatal("InvalidDescriptorBindingRejection", [] {
|
|
(void)NativeDescriptorType(
|
|
ShaderRecompiler::IR::DescriptorBindingKind::Count);
|
|
});
|
|
|
|
ExpectFatal("WritableFlatStoreRejection", [] {
|
|
const auto test = FlatStoreVariants();
|
|
(void)CompileCase(test);
|
|
});
|
|
|
|
{
|
|
ShaderRecompiler::IR::Program program;
|
|
program.resource_tracking_complete = true;
|
|
|
|
ShaderRecompiler::IR::ImageResource image;
|
|
image.resource_class = ShaderRecompiler::IR::ImageResourceClass::Storage;
|
|
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
|
image.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
|
image.read = true;
|
|
image.written = true;
|
|
image.atomic = true;
|
|
program.info.images.push_back(image);
|
|
|
|
ShaderRecompiler::IR::DescriptorValue descriptor{};
|
|
descriptor.dword_count = 8;
|
|
descriptor.dwords[0] = 0x2000u;
|
|
descriptor.dwords[1] =
|
|
static_cast<uint32_t>(Prospero::BufferFormat::k11_11_10UInt) << 20u;
|
|
descriptor.dwords[2] = 3u | (3u << 14u);
|
|
descriptor.dwords[3] =
|
|
DstSel(4, 5, 6, 7) |
|
|
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
|
|
|
image.source = 0;
|
|
program.info.images[0].source = 0;
|
|
program.descriptor_sources.resize(1);
|
|
program.descriptor_sources[0].dword_count = descriptor.dword_count;
|
|
for (u32 dword = 0; dword < descriptor.dword_count; dword++) {
|
|
program.descriptor_sources[0].dwords[dword] =
|
|
ShaderRecompiler::IR::Value(descriptor.dwords[dword]);
|
|
}
|
|
program.srt_plan_complete = true;
|
|
auto plan = ShaderRecompiler::IR::ExtractResourcePlan(program);
|
|
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
Require("PackedAtomicImageRejection", "resource materialization",
|
|
!ShaderRecompiler::IR::MaterializeResources(
|
|
plan, {}, snapshot, specialization),
|
|
"packed atomic image descriptor was accepted");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CheckStorageTextureVolumeUploadLayout() {
|
|
constexpr auto format = Prospero::BufferFormat::k16_16_16_16Float;
|
|
constexpr uint32_t width = 33;
|
|
constexpr uint32_t height = 33;
|
|
constexpr uint32_t depth = 33;
|
|
const auto pitch =
|
|
TileGetTexturePitch(format, width, Prospero::TileMode::kRenderTarget);
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, depth, 1,
|
|
Prospero::TileMode::kRenderTarget, true, total);
|
|
const auto layout = TextureCalcUploadLayout(
|
|
format, width, height, 1, depth, Prospero::TileMode::kRenderTarget,
|
|
total.size, true, true, "StorageTextureVolumeTest");
|
|
const auto regions = TextureBuildImageCopies(layout);
|
|
Require(
|
|
"StorageTextureVolumeUpload", "layout",
|
|
pitch == 128 && total.size == 0x210000 && layout.slice_stride == 0x2208 &&
|
|
layout.source_slice_stride == 0 && layout.mips[0].size == 0x2208 &&
|
|
layout.surface.mips[0].size == 0x10000 && regions.size() == depth,
|
|
"3D render-target upload did not preserve its compact linear layout");
|
|
|
|
std::vector<GpuTileInfo> infos;
|
|
Require("StorageTextureVolumeUpload", "GPU records",
|
|
TextureBuildGpuTileInfos(total.size, regions, layout, 1, infos) &&
|
|
infos.size() == depth,
|
|
"3D render-target GPU records were not built");
|
|
for (const uint32_t z : {0u, 1u, depth - 1u}) {
|
|
Require("StorageTextureVolumeUpload", "slice offsets",
|
|
infos[z].linear_offset == static_cast<uint64_t>(z) * 0x2208 &&
|
|
infos[z].tiled_offset == static_cast<uint64_t>(z) * 0x10000 &&
|
|
infos[z].surface_z == z && infos[z].pitch == width,
|
|
"volume slice lost its linear stride, block slice, or Z swizzle");
|
|
}
|
|
std::printf("[host] %-32s ok\n", "StorageTextureVolumeUpload");
|
|
}
|
|
|
|
void CheckStorageTextureVolumeMipRegions() {
|
|
constexpr auto format = Prospero::BufferFormat::k8_8_8_8UNorm;
|
|
constexpr uint32_t width = 8;
|
|
constexpr uint32_t height = 4;
|
|
constexpr uint32_t depth = 5;
|
|
constexpr uint32_t levels = 3;
|
|
constexpr auto tile = Prospero::TileMode::kLinear;
|
|
TileSizeAlign total{};
|
|
TileGetTextureTotalSize(format, width, height, depth, levels, tile, true,
|
|
total);
|
|
const auto layout = TextureCalcUploadLayout(
|
|
format, width, height, levels, depth, tile, total.size, true, true,
|
|
"StorageTextureVolumeMipTest");
|
|
const auto copies = TextureBuildImageCopies(layout);
|
|
|
|
bool valid = copies.size() == 8;
|
|
size_t index = 0;
|
|
for (uint32_t level = 0; level < levels && valid; level++) {
|
|
const uint32_t mip_depth = std::max(depth >> level, 1u);
|
|
const uint32_t mip_width = std::max(width >> level, 1u);
|
|
const uint32_t mip_height = std::max(height >> level, 1u);
|
|
for (uint32_t z = 0; z < mip_depth; z++, index++) {
|
|
const auto © = copies[index];
|
|
valid &= copy.imageSubresource.mipLevel == level &&
|
|
copy.imageOffset.z == static_cast<int>(z) &&
|
|
copy.imageExtent.width == mip_width &&
|
|
copy.imageExtent.height == mip_height &&
|
|
copy.bufferOffset ==
|
|
layout.mips[level].offset + z * layout.slice_stride &&
|
|
copy.bufferRowLength == layout.mips[level].row_length &&
|
|
copy.bufferImageHeight == layout.mips[level].image_height;
|
|
}
|
|
}
|
|
valid &= index == copies.size();
|
|
Require("StorageTextureVolumeMipRegions", "per-mip depth", valid,
|
|
"direction-neutral image copies did not shrink depth or preserve "
|
|
"Vulkan Z coordinates");
|
|
std::printf("[host] %-32s ok\n", "StorageTextureVolumeMipRegions");
|
|
}
|
|
void CheckStandard64RenderTargetTileRoundTrip() {
|
|
constexpr auto format = Prospero::BufferFormat::k32Float;
|
|
constexpr auto tile = Prospero::TileMode::kStandard64KB;
|
|
|
|
constexpr uint32_t observed_width = 3840;
|
|
constexpr uint32_t observed_height = 2160;
|
|
const auto observed_pitch = TileGetTexturePitch(format, observed_width, tile);
|
|
TileSizeAlign observed{};
|
|
TileGetTextureSize(format, observed_width, observed_height, 1, tile,
|
|
&observed, nullptr, nullptr);
|
|
Require("Standard64RenderTarget", "observed layout",
|
|
observed_pitch == 3840 && observed.size == 0x1fe0000 &&
|
|
observed.align == 0x10000,
|
|
"PPSA02721 Standard64KB render-target footprint changed");
|
|
|
|
constexpr uint32_t width = 257;
|
|
constexpr uint32_t height = 131;
|
|
const auto pitch = TileGetTexturePitch(format, width, tile);
|
|
TileSizeAlign storage{};
|
|
TileGetTextureSize(format, width, height, 1, tile, &storage, nullptr,
|
|
nullptr);
|
|
Require("Standard64RenderTarget", "partial layout",
|
|
pitch == 384 && storage.size == 0x60000 && storage.align == 0x10000,
|
|
"partial Standard64KB footprint was not padded in 128x128 blocks");
|
|
|
|
ImageInfo info{};
|
|
info.data = {0x10000, storage.size};
|
|
info.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
info.guest_format = format;
|
|
info.type = Prospero::ImageType::kColor2D;
|
|
info.extent = {width, height, 1};
|
|
info.resources = {1, 1};
|
|
info.pitch = pitch;
|
|
info.bytes_per_block = 4;
|
|
info.samples = 1;
|
|
info.tile_mode = tile;
|
|
info.mip_layout[0] = {0, storage.size, pitch, height};
|
|
Require("Standard64RenderTarget", "support boundary",
|
|
IsSupportedStandard64RenderTarget(info) && IsTiledRenderTarget(info),
|
|
"exact Standard64KB render target was not classified as tiled");
|
|
Require("Standard64RenderTarget", "display tile boundary",
|
|
IsSupportedDisplayRenderTargetTileMode(
|
|
Prospero::TileMode::kRenderTarget) &&
|
|
!IsSupportedDisplayRenderTargetTileMode(tile),
|
|
"Standard64KB render target could alias a mode-27 display image");
|
|
auto unsupported = info;
|
|
unsupported.data.address += 4;
|
|
Require("Standard64RenderTarget", "address guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"unaligned Standard64KB backing was accepted");
|
|
unsupported = info;
|
|
unsupported.bytes_per_block = 8;
|
|
Require("Standard64RenderTarget", "element guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"unimplemented Standard64KB element size was accepted");
|
|
unsupported = info;
|
|
unsupported.pitch += 128;
|
|
Require("Standard64RenderTarget", "pitch guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"non-minimal Standard64KB pitch was accepted");
|
|
unsupported = info;
|
|
unsupported.data.size += 0x10000;
|
|
Require("Standard64RenderTarget", "size guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"non-exact Standard64KB allocation was accepted");
|
|
unsupported = info;
|
|
unsupported.resources.levels = 2;
|
|
Require("Standard64RenderTarget", "mip guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"unimplemented Standard64KB mip chain was accepted");
|
|
unsupported = info;
|
|
unsupported.resources.layers = 2;
|
|
Require("Standard64RenderTarget", "layer guard",
|
|
!IsSupportedStandard64RenderTarget(unsupported),
|
|
"unimplemented Standard64KB array was accepted");
|
|
|
|
std::printf("[host] %-32s ok\n", "Standard64RenderTarget");
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
void CheckStorageTextureGpuOwnedRebindState() {
|
|
constexpr uintptr_t base = 0x0000000200200000ull;
|
|
constexpr uint64_t size = 0x10000;
|
|
const auto guest_memory = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
|
base, size, Common::VirtualMemory::Mode::ReadWrite,
|
|
"storage_texture_gpu_owned_rebind", true);
|
|
auto *memory = reinterpret_cast<uint8_t *>(guest_memory);
|
|
Require("StorageTextureGpuOwnedRebind", "allocation", guest_memory == base,
|
|
"fixed guest-owner allocation failed");
|
|
PageManager page_manager;
|
|
MemoryTracker tracker(page_manager);
|
|
tracker.ForEachUploadRange(
|
|
base, size, true, [](uint64_t, uint64_t) noexcept {}, []() noexcept {});
|
|
uint64_t readable = 0;
|
|
uint64_t mapped = 0;
|
|
MEMORY_BASIC_INFORMATION protection{};
|
|
Require(
|
|
"StorageTextureGpuOwnedRebind", "owned",
|
|
tracker.IsRegionGpuModified(base, size) &&
|
|
(!HostMemoryQueryReadable(base, size, readable) || readable < size) &&
|
|
HostMemoryQueryRange(base, size, HostMemoryAccess::Mapped, mapped) &&
|
|
mapped == size &&
|
|
VirtualQuery(memory, &protection, sizeof(protection)) != 0 &&
|
|
protection.Protect == PAGE_NOACCESS,
|
|
"GPU-owned storage pages remained host-readable or lost tracker "
|
|
"identity");
|
|
|
|
tracker.UnmarkRegionAsGpuModified(base, size);
|
|
readable = 0;
|
|
Require("StorageTextureGpuOwnedRebind", "clean readback",
|
|
!tracker.IsRegionGpuModified(base, size) &&
|
|
!tracker.IsRegionCpuModified(base, size) &&
|
|
HostMemoryQueryReadable(base, size, readable) && readable == size,
|
|
"clean storage readback did not publish readable coherent backing");
|
|
tracker.MarkRegionAsGpuModified(base, size);
|
|
readable = 0;
|
|
Require(
|
|
"StorageTextureGpuOwnedRebind", "clean reclaim",
|
|
tracker.IsRegionGpuModified(base, size) &&
|
|
!tracker.IsRegionCpuModified(base, size) &&
|
|
(!HostMemoryQueryReadable(base, size, readable) || readable < size),
|
|
"clean storage rebind did not reclaim GPU ownership without an upload");
|
|
|
|
tracker.UnmarkRegionAsGpuModified(base, size);
|
|
tracker.MarkRegionAsCpuModified(base, size);
|
|
uint32_t dirty_ranges = 0;
|
|
bool upload_called = false;
|
|
tracker.ForEachUploadRange(
|
|
base, size, true, [&](uint64_t, uint64_t) noexcept { dirty_ranges++; },
|
|
[&]() noexcept { upload_called = true; });
|
|
readable = 0;
|
|
Require(
|
|
"StorageTextureGpuOwnedRebind", "dirty refresh",
|
|
dirty_ranges == 1 && upload_called &&
|
|
tracker.IsRegionGpuModified(base, size) &&
|
|
!tracker.IsRegionCpuModified(base, size) &&
|
|
(!HostMemoryQueryReadable(base, size, readable) || readable < size),
|
|
"CPU-dirty storage rebind did not refresh once and reclaim GPU "
|
|
"ownership");
|
|
|
|
tracker.UnmarkRegionAsGpuModified(base, size);
|
|
tracker.UntrackMemory(base, size);
|
|
Require("StorageTextureGpuOwnedRebind", "free",
|
|
Libs::LibKernel::Memory::FreeGuestMemory(base, size),
|
|
"guest-owner free failed");
|
|
std::printf("[host] %-32s ok\n", "StorageTextureGpuOwnedRebind");
|
|
}
|
|
#endif
|
|
|
|
void CheckNativeMsaaState() {
|
|
Require("NativeMsaaState", "sample encoding",
|
|
render_sample_count(0) == 1 && render_sample_count(1) == 2 &&
|
|
render_sample_count(2) == 4 && render_sample_count(3) == 8 &&
|
|
render_sample_count(4) == 0,
|
|
"PS5 sample encodings were not mapped exactly");
|
|
Require("NativeMsaaState", "Vulkan sample mapping",
|
|
vulkan_sample_count(1) == vk::SampleCountFlagBits::e1 &&
|
|
vulkan_sample_count(2) == vk::SampleCountFlagBits::e2 &&
|
|
vulkan_sample_count(4) == vk::SampleCountFlagBits::e4 &&
|
|
vulkan_sample_count(8) == vk::SampleCountFlagBits::e8 &&
|
|
vulkan_sample_count(3) == vk::SampleCountFlagBits{},
|
|
"native sample counts were not mapped exactly to Vulkan");
|
|
|
|
TileSizeAlign color{};
|
|
const auto color_pitch = TileGetRenderTargetPitch(1920, 8, 3);
|
|
Require("NativeMsaaState", "8x color footprint",
|
|
color_pitch == 1920 &&
|
|
TileGetRenderTargetSize(1920, 1080, color_pitch, 8, color, 3) &&
|
|
color.align == 0x10000 && color.size == 0x07f80000,
|
|
"8x R16G16B16A16 color footprint was not preserved");
|
|
|
|
TileSizeAlign depth{};
|
|
TileSizeAlign stencil{};
|
|
TileSizeAlign htile{};
|
|
Require("NativeMsaaState", "8x depth/stencil footprint",
|
|
TileGetDepthPitch(1920, 4, 3) == 1920 &&
|
|
TileGetDepthSize(1920, 1080, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth, 3) &&
|
|
depth.align == 0x10000 && depth.size == 0x03fc0000 &&
|
|
stencil.align == 0x10000 && stencil.size == 0x010e0000 &&
|
|
htile.align == 0x8000 && htile.size == 0x00030000,
|
|
"8x depth/stencil or fragment-independent HTile footprint regressed");
|
|
std::printf("[host] %-32s ok\n", "NativeMsaaState");
|
|
}
|
|
|
|
void CheckDepthHtileStencilCompatibility() {
|
|
Require("DepthHtileStencilCompatibility", "inactive stencil plane",
|
|
depth_htile_stencil_acceleration_compatible(false, true, false),
|
|
"inactive Hi-Stencil state was treated as an attachment");
|
|
Require("DepthHtileStencilCompatibility", "disabled acceleration",
|
|
depth_htile_stencil_acceleration_compatible(true, false, true),
|
|
"disabled Hi-Stencil state was rejected");
|
|
Require("DepthHtileStencilCompatibility", "PS5 stencil plus HTile",
|
|
depth_htile_stencil_acceleration_compatible(true, true, false),
|
|
"valid PS5 Hi-Stencil attachment was rejected");
|
|
Require("DepthHtileStencilCompatibility", "missing HTile metadata",
|
|
!depth_htile_stencil_acceleration_compatible(true, false, false),
|
|
"active Hi-Stencil without HTile metadata was admitted");
|
|
std::printf("[host] %-32s ok\n", "DepthHtileStencilCompatibility");
|
|
}
|
|
|
|
void CheckPs5DepthRegisterDecoding() {
|
|
constexpr uint32_t captured_z = 0x22900803u;
|
|
constexpr uint32_t captured_stencil = 0x00100801u;
|
|
const auto z = HW::DepthZInfo::Decode(captured_z);
|
|
const auto stencil = HW::DepthStencilInfo::Decode(captured_stencil);
|
|
Require(
|
|
"Ps5DepthRegisterDecoding", "captured texture-compatible state",
|
|
z.format == Prospero::DepthFormat::kZ32F && z.num_samples == 0 &&
|
|
z.htile_acceleration && !z.expclear_enabled &&
|
|
!z.partially_resident && z.max_mip_level == 0 &&
|
|
z.z_compare_base == Prospero::ZCompareBase::kZMin &&
|
|
z.texture_compatibility ==
|
|
Prospero::TextureCompatiblePlaneCompression::kEnable &&
|
|
z.HasValidTextureCompatibility() &&
|
|
stencil.format == Prospero::StencilFormat::k8UInt &&
|
|
!stencil.htile_stencil_disabled && !stencil.expclear_enabled &&
|
|
!stencil.partially_resident &&
|
|
stencil.texture_compatibility ==
|
|
Prospero::TextureCompatibleStencil::kEnable &&
|
|
stencil.HasValidTextureCompatibility(),
|
|
"valid PS5 depth/stencil aggregate encodings were not decoded exactly");
|
|
|
|
const auto malformed_z = HW::DepthZInfo::Decode(0x00000803u);
|
|
const auto malformed_stencil = HW::DepthStencilInfo::Decode(0x00100001u);
|
|
Require("Ps5DepthRegisterDecoding", "malformed aggregate state",
|
|
!malformed_z.HasValidTextureCompatibility() &&
|
|
!malformed_stencil.HasValidTextureCompatibility(),
|
|
"partial texture-compatible aggregate encodings were accepted");
|
|
std::printf("[host] %-32s ok\n", "Ps5DepthRegisterDecoding");
|
|
}
|
|
|
|
void CheckStencilAttachmentAccess() {
|
|
PipelineStencilStaticState state{vk::StencilOp::eKeep, vk::StencilOp::eKeep,
|
|
vk::StencilOp::eKeep,
|
|
vk::CompareOp::eAlways};
|
|
PipelineStencilDynamicState dynamic{0xff, 0xff, 0};
|
|
Require("StencilAttachmentAccess", "always keep",
|
|
!stencil_face_accesses_attachment(state, dynamic),
|
|
"ALWAYS/KEEP state was classified as stencil access");
|
|
state.compareOp = vk::CompareOp::eEqual;
|
|
Require("StencilAttachmentAccess", "compare reads",
|
|
stencil_face_accesses_attachment(state, dynamic),
|
|
"real stencil comparison was classified as no access");
|
|
state.compareOp = vk::CompareOp::eAlways;
|
|
state.passOp = vk::StencilOp::eZero;
|
|
Require("StencilAttachmentAccess", "write operation",
|
|
stencil_face_accesses_attachment(state, dynamic),
|
|
"write-capable stencil operation was classified as no access");
|
|
dynamic.writeMask = 0;
|
|
Require("StencilAttachmentAccess", "masked write",
|
|
!stencil_face_accesses_attachment(state, dynamic),
|
|
"fully masked stencil write was classified as access");
|
|
std::printf("[host] %-32s ok\n", "StencilAttachmentAccess");
|
|
}
|
|
|
|
void CheckDepthAttachmentWrites() {
|
|
RenderDepthInfo target{};
|
|
target.desc.view_info.format = vk::Format::eD32SfloatS8Uint;
|
|
Require("DepthAttachmentWrites", "disabled depth test",
|
|
!target.AttachmentWriteAspects(),
|
|
"disabled depth testing claimed a depth write");
|
|
|
|
target.depth_test_enable = true;
|
|
target.depth_write_enable = true;
|
|
target.depth_compare_op = vk::CompareOp::eLess;
|
|
Require("DepthAttachmentWrites", "depth write",
|
|
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eDepth,
|
|
"write-enabled depth testing did not claim depth");
|
|
|
|
target.depth_test_enable = false;
|
|
target.depth_write_enable = false;
|
|
target.depth_load_clear_enable = true;
|
|
Require("DepthAttachmentWrites", "depth clear",
|
|
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eDepth,
|
|
"depth attachment clear did not claim depth");
|
|
|
|
target.depth_load_clear_enable = false;
|
|
target.stencil_test_enable = true;
|
|
target.stencil_dynamic_front = {0xff, 0xff, 0};
|
|
target.stencil_dynamic_back = target.stencil_dynamic_front;
|
|
target.stencil_static_front = {vk::StencilOp::eKeep, vk::StencilOp::eKeep,
|
|
vk::StencilOp::eKeep, vk::CompareOp::eAlways};
|
|
target.stencil_static_back = target.stencil_static_front;
|
|
Require("DepthAttachmentWrites", "stencil keep",
|
|
!target.AttachmentWriteAspects(),
|
|
"KEEP-only stencil state claimed a stencil write");
|
|
|
|
target.stencil_static_front.failOp = vk::StencilOp::eZero;
|
|
Require("DepthAttachmentWrites", "unreachable stencil fail",
|
|
!target.AttachmentWriteAspects(),
|
|
"ALWAYS comparison claimed an unreachable fail operation");
|
|
|
|
target.stencil_static_front.failOp = vk::StencilOp::eKeep;
|
|
target.stencil_static_front.passOp = vk::StencilOp::eReplace;
|
|
Require("DepthAttachmentWrites", "stencil pass write",
|
|
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eStencil,
|
|
"write-capable stencil pass did not claim stencil");
|
|
|
|
target.stencil_dynamic_front.writeMask = 0;
|
|
Require("DepthAttachmentWrites", "back-face keep",
|
|
!target.AttachmentWriteAspects(),
|
|
"masked front write or KEEP-only back face claimed stencil");
|
|
|
|
target.stencil_test_enable = false;
|
|
target.stencil_clear_enable = true;
|
|
Require("DepthAttachmentWrites", "stencil clear",
|
|
target.AttachmentWriteAspects() == vk::ImageAspectFlagBits::eStencil,
|
|
"stencil attachment clear did not claim stencil");
|
|
|
|
target.desc.view_info.format = vk::Format::eD32Sfloat;
|
|
Require("DepthAttachmentWrites", "missing stencil aspect",
|
|
!target.AttachmentWriteAspects(),
|
|
"depth-only format claimed a stencil write");
|
|
std::printf("[host] %-32s ok\n", "DepthAttachmentWrites");
|
|
}
|
|
|
|
void CheckDynamicRenderingState() {
|
|
RenderState first{};
|
|
first.width = 64;
|
|
first.height = 32;
|
|
first.num_layers = 2;
|
|
first.num_color_attachments = 1;
|
|
first.color_attachments[0].image_layout =
|
|
vk::ImageLayout::eColorAttachmentOptimal;
|
|
first.color_attachments[0].clear_value = {1, 2, 3, 4};
|
|
first.color_attachments[0].is_clear = true;
|
|
auto changed = first;
|
|
Require("DynamicRenderingState", "identical state", first == changed,
|
|
"fieldwise RenderState identity rejected an identical scope");
|
|
changed.color_attachments[0].is_clear = false;
|
|
Require("DynamicRenderingState", "clear identity", first != changed,
|
|
"clear/load state did not participate in rendering identity");
|
|
changed = first;
|
|
changed.num_layers = 1;
|
|
Require("DynamicRenderingState", "layer identity", first != changed,
|
|
"layer count did not participate in rendering identity");
|
|
changed = first;
|
|
changed.color_attachments[0].image_layout = vk::ImageLayout::eGeneral;
|
|
Require("DynamicRenderingState", "layout identity", first != changed,
|
|
"attachment layout did not participate in rendering identity");
|
|
|
|
PipelineRenderingState rgba{};
|
|
rgba.color_count = 1;
|
|
rgba.color_formats[0] = vk::Format::eR8G8B8A8Unorm;
|
|
auto uint_color = rgba;
|
|
uint_color.color_formats[0] = vk::Format::eR8G8B8A8Uint;
|
|
Require("DynamicRenderingState", "pipeline color format identity",
|
|
rgba != uint_color,
|
|
"distinct dynamic-rendering formats can alias one pipeline key");
|
|
auto depth = rgba;
|
|
depth.depth_format = vk::Format::eD32SfloatS8Uint;
|
|
depth.stencil_format = vk::Format::eD32SfloatS8Uint;
|
|
Require("DynamicRenderingState", "pipeline depth/stencil identity",
|
|
rgba != depth,
|
|
"depth/stencil formats did not participate in pipeline identity");
|
|
|
|
RenderDepthInfo attachment{};
|
|
attachment.desc.view_info.format = vk::Format::eD32SfloatS8Uint;
|
|
Require("DynamicRenderingState", "read-only depth/stencil layout",
|
|
depth_attachment_layout(attachment) ==
|
|
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
|
"fully read-only depth/stencil used a writable layout");
|
|
attachment.depth_load_clear_enable = true;
|
|
Require("DynamicRenderingState", "read-only deferred depth clear",
|
|
depth_attachment_layout(attachment) ==
|
|
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
|
"a load clear changed the guest depth-write layout");
|
|
attachment.depth_test_enable = true;
|
|
attachment.depth_write_enable = true;
|
|
Require("DynamicRenderingState", "depth-write stencil-read layout",
|
|
depth_attachment_layout(attachment) ==
|
|
vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal,
|
|
"depth-only writes did not retain read-only stencil");
|
|
attachment.depth_load_clear_enable = false;
|
|
attachment.depth_write_enable = false;
|
|
attachment.stencil_clear_enable = true;
|
|
Require("DynamicRenderingState", "depth-read stencil-write layout",
|
|
depth_attachment_layout(attachment) ==
|
|
vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal,
|
|
"stencil-only writes did not retain read-only depth");
|
|
attachment.depth_write_enable = true;
|
|
Require("DynamicRenderingState", "writable depth/stencil layout",
|
|
depth_attachment_layout(attachment) ==
|
|
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
|
"combined depth/stencil writes did not use the writable layout");
|
|
std::printf("[host] %-32s ok\n", "DynamicRenderingState");
|
|
}
|
|
|
|
void CheckDepthTargetFootprints() {
|
|
TileSizeAlign stencil{};
|
|
TileSizeAlign htile{};
|
|
TileSizeAlign depth{};
|
|
struct AttachmentFormatCase {
|
|
const char *name;
|
|
Prospero::DepthFormat depth_format;
|
|
Prospero::StencilFormat stencil_format;
|
|
vk::Format expected;
|
|
};
|
|
constexpr AttachmentFormatCase attachment_cases[] = {
|
|
{"Z16", Prospero::DepthFormat::kZ16, Prospero::StencilFormat::kInvalid,
|
|
vk::Format::eD16Unorm},
|
|
{"Z16S8", Prospero::DepthFormat::kZ16, Prospero::StencilFormat::k8UInt,
|
|
vk::Format::eD16UnormS8Uint},
|
|
{"Z32", Prospero::DepthFormat::kZ32F, Prospero::StencilFormat::kInvalid,
|
|
vk::Format::eD32Sfloat},
|
|
{"Z32S8", Prospero::DepthFormat::kZ32F, Prospero::StencilFormat::k8UInt,
|
|
vk::Format::eD32SfloatS8Uint},
|
|
{"invalid depth", static_cast<Prospero::DepthFormat>(2u),
|
|
Prospero::StencilFormat::k8UInt, vk::Format::eUndefined},
|
|
{"invalid stencil", Prospero::DepthFormat::kZ16,
|
|
static_cast<Prospero::StencilFormat>(2u), vk::Format::eUndefined},
|
|
};
|
|
for (const auto &test : attachment_cases) {
|
|
Require("DepthTargetFootprints", test.name,
|
|
DepthAttachmentFormat(test.depth_format, test.stencil_format) ==
|
|
test.expected,
|
|
"PS5 depth/stencil attachment mapping changed");
|
|
}
|
|
Require("DepthTargetFootprints", "1920x1080 Z16S8 without HTile",
|
|
TileGetDepthSize(1920, 1080, 0, Prospero::DepthFormat::kZ16,
|
|
Prospero::StencilFormat::k8UInt, false, stencil,
|
|
htile, depth) &&
|
|
depth.size == 0x480000 && depth.align == 0x10000 &&
|
|
stencil.size == 0x280000 && stencil.align == 0x10000 &&
|
|
htile.size == 0,
|
|
"captured Z16S8 footprint disagrees with Prospero block rules");
|
|
struct TargetFormatCase {
|
|
const char *name;
|
|
vk::Format host_format;
|
|
Prospero::BufferFormat guest_format;
|
|
uint32_t bytes_per_element;
|
|
bool stencil;
|
|
bool supported;
|
|
bool readback;
|
|
};
|
|
constexpr TargetFormatCase target_cases[] = {
|
|
{"D16", vk::Format::eD16Unorm, Prospero::BufferFormat::k16UNorm, 2, false,
|
|
true, true},
|
|
{"D16S8", vk::Format::eD16UnormS8Uint, Prospero::BufferFormat::k16UNorm,
|
|
2, true, true, true},
|
|
{"D16 via D24S8", vk::Format::eD24UnormS8Uint,
|
|
Prospero::BufferFormat::k16UNorm, 2, true, true, true},
|
|
{"D16 via D32S8", vk::Format::eD32SfloatS8Uint,
|
|
Prospero::BufferFormat::k16UNorm, 2, true, true, true},
|
|
{"D32", vk::Format::eD32Sfloat, Prospero::BufferFormat::k32Float, 4,
|
|
false, true, true},
|
|
{"D32S8", vk::Format::eD32SfloatS8Uint, Prospero::BufferFormat::k32Float,
|
|
4, true, true, true},
|
|
{"D16 plus stencil mismatch", vk::Format::eD16Unorm,
|
|
Prospero::BufferFormat::k16UNorm, 2, true, false, false},
|
|
{"fallback without stencil", vk::Format::eD24UnormS8Uint,
|
|
Prospero::BufferFormat::k16UNorm, 2, false, false, false},
|
|
{"D32 guest via D24", vk::Format::eD24UnormS8Uint,
|
|
Prospero::BufferFormat::k32Float, 4, true, false, false},
|
|
{"D16 byte mismatch", vk::Format::eD16Unorm,
|
|
Prospero::BufferFormat::k16UNorm, 4, false, false, false},
|
|
};
|
|
for (const auto &test : target_cases) {
|
|
ImageInfo target{};
|
|
target.pixel_format = test.host_format;
|
|
target.guest_format = test.guest_format;
|
|
target.bytes_per_block = test.bytes_per_element;
|
|
target.stencil = test.stencil ? GuestRange{0x10000, 0x10000} : GuestRange{};
|
|
Require("DepthTargetFootprints", test.name,
|
|
IsSupportedDepthTargetFormat(target) == test.supported &&
|
|
IsSupportedDepthPlaneReadback(target) == test.readback,
|
|
"host/guest depth format or exact readback policy changed");
|
|
}
|
|
ImageInfo compressed_stencil{};
|
|
compressed_stencil.pixel_format = vk::Format::eD32SfloatS8Uint;
|
|
compressed_stencil.guest_format = Prospero::BufferFormat::k32Float;
|
|
compressed_stencil.bytes_per_block = 4;
|
|
compressed_stencil.stencil = {0x10000, 0x10000};
|
|
compressed_stencil.metadata.stencil_compressed = true;
|
|
Require("DepthTargetFootprints", "compressed stencil depth-plane readback",
|
|
IsSupportedDepthTargetFormat(compressed_stencil) &&
|
|
IsSupportedDepthPlaneReadback(compressed_stencil),
|
|
"stencil metadata incorrectly blocked independent depth-plane "
|
|
"readback");
|
|
Require("DepthTargetFootprints", "lexicographic subresource comparison",
|
|
ImageSubresources{2, 1} > ImageSubresources{1, 4} &&
|
|
ImageSubresources{1, 4} < ImageSubresources{2, 1},
|
|
"subresource ordering diverged from required overlap semantics");
|
|
struct TransferPlaneCase {
|
|
const char *name;
|
|
vk::Format attachment;
|
|
vk::Format transfer;
|
|
uint32_t bytes;
|
|
};
|
|
constexpr TransferPlaneCase transfer_cases[] = {
|
|
{"D16S8 transfer", vk::Format::eD16UnormS8Uint, vk::Format::eD16Unorm, 2},
|
|
{"D24S8 transfer", vk::Format::eD24UnormS8Uint,
|
|
vk::Format::eX8D24UnormPack32, 4},
|
|
{"D32S8 transfer", vk::Format::eD32SfloatS8Uint, vk::Format::eD32Sfloat,
|
|
4},
|
|
{"invalid transfer", vk::Format::eR16Unorm, vk::Format::eUndefined, 0},
|
|
};
|
|
for (const auto &test : transfer_cases) {
|
|
Require("DepthTargetFootprints", test.name,
|
|
DepthAspectTransferFormat(test.attachment) == test.transfer &&
|
|
DepthAspectTransferBytes(test.attachment) == test.bytes,
|
|
"combined depth transfer-plane layout changed");
|
|
}
|
|
struct PromotionCase {
|
|
const char *name;
|
|
uint16_t source;
|
|
uint32_t d24;
|
|
uint32_t d32;
|
|
};
|
|
constexpr PromotionCase promotion_cases[] = {
|
|
{"zero", 0, 0, 0},
|
|
{"midpoint", 0x8000, 0x00800080, 0x3f000080},
|
|
{"maximum", 0xffff, 0x00ffffff, 0x3f800000},
|
|
};
|
|
for (const auto &test : promotion_cases) {
|
|
Require("DepthTargetFootprints", test.name,
|
|
EncodeD16AsD24(test.source) == test.d24 &&
|
|
EncodeD16AsD32(test.source) == test.d32,
|
|
"D16 host promotion changed the represented depth value");
|
|
}
|
|
Require("DepthTargetFootprints", "640x360 Z32S8 without HTile",
|
|
TileGetDepthSize(640, 360, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, false, stencil,
|
|
htile, depth),
|
|
"valid non-HTile depth/stencil footprint was rejected");
|
|
Require(
|
|
"DepthTargetFootprints", "640x360 Prospero block sizes",
|
|
depth.size == 0xf0000 && depth.align == 0x10000 &&
|
|
stencil.size == 0x60000 && stencil.align == 0x10000 &&
|
|
htile.size == 0 && htile.align == 0,
|
|
"non-HTile depth/stencil footprint disagrees with Prospero block rules");
|
|
|
|
const auto depth_pitch = TileGetTexturePitch(Prospero::BufferFormat::k32Float,
|
|
640, Prospero::TileMode::kDepth);
|
|
TileSizeAlign texture_depth{};
|
|
TileGetTextureTotalSize(Prospero::BufferFormat::k32Float, 640, 360, 1, 1,
|
|
Prospero::TileMode::kDepth, false, texture_depth);
|
|
Require("DepthTargetFootprints", "640x360 generic depth tile",
|
|
depth_pitch == 640 && texture_depth.size == 0xf0000 &&
|
|
texture_depth.align == 0x10000,
|
|
"generic depth texture sizing bypassed 64 KiB block padding");
|
|
|
|
Require(
|
|
"DepthTargetFootprints", "960x540 Z32S8 with HTile",
|
|
TileGetDepthSize(960, 540, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil, htile,
|
|
depth) &&
|
|
depth.size == 0x280000 && depth.align == 0x10000 &&
|
|
stencil.size == 0xc0000 && stencil.align == 0x10000 &&
|
|
htile.size == 0x10000 && htile.align == 0x8000,
|
|
"generic Prospero HTile block calculation rejected the title footprint");
|
|
Require("DepthTargetFootprints", "known HTile extent",
|
|
TileGetDepthSize(1280, 720, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth) &&
|
|
depth.size == 0x3c0000 && stencil.size == 0xf0000 &&
|
|
htile.size == 0x20000,
|
|
"validated 1280x720 HTile footprint regressed");
|
|
Require("DepthTargetFootprints", "PPSA06228 3840x2160 Z32S8 with HTile",
|
|
TileGetDepthSize(3840, 2160, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth) &&
|
|
depth.size == 0x1fe0000 && depth.align == 0x10000 &&
|
|
stencil.size == 0x870000 && stencil.align == 0x10000 &&
|
|
htile.size == 0xa0000 && htile.align == 0x8000,
|
|
"captured 3840x2160 depth/stencil/HTile footprint disagrees with "
|
|
"Prospero rules");
|
|
Require("DepthTargetFootprints", "invalid depth format",
|
|
!TileGetDepthSize(960, 540, 0, static_cast<Prospero::DepthFormat>(2u),
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth),
|
|
"unsupported depth format was silently admitted");
|
|
Require("DepthTargetFootprints", "invalid stencil format",
|
|
!TileGetDepthSize(960, 540, 0, Prospero::DepthFormat::kZ32F,
|
|
static_cast<Prospero::StencilFormat>(2u), true,
|
|
stencil, htile, depth),
|
|
"unsupported stencil format was silently admitted");
|
|
Require("DepthTargetFootprints", "invalid extent",
|
|
!TileGetDepthSize(0, 540, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth) &&
|
|
!TileGetDepthSize(16385, 540, 0, Prospero::DepthFormat::kZ32F,
|
|
Prospero::StencilFormat::k8UInt, true, stencil,
|
|
htile, depth),
|
|
"invalid HTile extent was silently admitted");
|
|
std::printf("[host] %-32s ok\n", "DepthTargetFootprints");
|
|
}
|
|
|
|
struct SlotLifetimeProbe {
|
|
explicit SlotLifetimeProbe(bool *destroyed) : destroyed(destroyed) {
|
|
if (destroyed == nullptr || *destroyed) {
|
|
EXIT("fence-lifetime probe has invalid construction state\n");
|
|
}
|
|
}
|
|
~SlotLifetimeProbe() { *destroyed = true; }
|
|
|
|
bool *destroyed = nullptr;
|
|
};
|
|
|
|
void CheckSlotVectorLifetime() {
|
|
bool destroyed = false;
|
|
Common::SlotVector<SlotLifetimeProbe> slots;
|
|
const auto first = slots.insert(&destroyed);
|
|
Require("SlotVectorLifetime", "inserted",
|
|
slots.is_allocated(first) && slots.try_get(first) != nullptr &&
|
|
!destroyed,
|
|
"slot insertion did not expose its cache-owned object");
|
|
slots.erase(first);
|
|
const bool first_destroyed = destroyed;
|
|
destroyed = false;
|
|
const auto second = slots.insert(&destroyed);
|
|
Require("SlotVectorLifetime", "generation",
|
|
first_destroyed && !destroyed && first.index == second.index &&
|
|
first != second && !slots.is_allocated(first) &&
|
|
slots.is_allocated(second),
|
|
"reused slot accepted a stale cache resource id");
|
|
std::printf("[host] %-32s ok\n", "SlotVectorLifetime");
|
|
}
|
|
|
|
void CheckEmbeddedFetchLaneSpill() {
|
|
std::vector<u32> code;
|
|
code.push_back(EncodeSMovB32(0, InlineU32(0)));
|
|
code.push_back(EncodeSmem0(0x02u, 20, 4));
|
|
code.push_back(EncodeSmem1(0)); // s_load_dwordx4 s[20:23], s[8:9]
|
|
AppendVop3(&code, 0x361u, 6, 20, InlineU32(0)); // v_writelane_b32 v6, s20, 0
|
|
code.push_back(EncodeSMovB32(20, InlineU32(0)));
|
|
AppendVop3(&code, 0x360u, 20, Vgpr(6),
|
|
InlineU32(0)); // v_readlane_b32 s20, v6, 0
|
|
code.push_back(EncodeVop2(0x01u, 0, Vgpr(8), 5));
|
|
code.push_back(EncodeMubuf0(0x03u, 0, true));
|
|
code.push_back(EncodeMubuf1(9, 5, 0));
|
|
AppendEnd(&code);
|
|
|
|
std::array<u32, 11> user_data{};
|
|
ShaderVertexInputInfo vertex;
|
|
vertex.fetch_embedded = true;
|
|
vertex.fetch_buffer_reg = 0;
|
|
vertex.resources_num = 1;
|
|
vertex.resources[0].fields[3] =
|
|
BufferFormat(Prospero::BufferFormat::k32_32_32_32Float) << 12u | DstSel(4, 5, 6, 7);
|
|
vertex.resources_dst[0].attr_id = 0;
|
|
vertex.resources_dst[0].registers_num = 4;
|
|
|
|
ShaderRecompiler::CompileOptions options;
|
|
options.stage = ShaderType::Vertex;
|
|
options.user_data_base = 8;
|
|
options.user_data = user_data;
|
|
options.input_info.vertex = &vertex;
|
|
|
|
auto translated = ShaderRecompiler::TranslateProgram(code, options);
|
|
auto resource_plan =
|
|
ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
|
ShaderRecompiler::IR::ResourceSnapshot resources;
|
|
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
|
const ShaderRecompiler::IR::SrtRuntime runtime{
|
|
.user_data = options.user_data,
|
|
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
|
};
|
|
Require("EmbeddedFetchLaneSpill", "resource materialization",
|
|
ShaderRecompiler::IR::MaterializeResources(
|
|
resource_plan, runtime, resources, specialization),
|
|
"lane-spilled fetch resources could not be materialized");
|
|
auto result = ShaderRecompiler::CompileProgram(
|
|
std::move(translated), options, specialization);
|
|
Require("EmbeddedFetchLaneSpill", "fetch rewrite",
|
|
result.program.info.vertex_fetch_components[0] == 4,
|
|
"lane-spilled fetch descriptor was not recognized and rewritten");
|
|
std::printf("[host] %-32s ok\n", "EmbeddedFetchLaneSpill");
|
|
}
|
|
|
|
void CheckReferenceClockScale() {
|
|
uint64_t value = 0;
|
|
Require("ReferenceClockScale", "zero",
|
|
Sync::ScaleReferenceClock(0, 3000000000ull, value) && value == 0,
|
|
"zero host tick did not produce a zero GPU clock");
|
|
Require("ReferenceClockScale", "fractional second",
|
|
Sync::ScaleReferenceClock(1500000000ull, 3000000000ull, value) &&
|
|
value == 50000000ull,
|
|
"host half-second did not scale to 50,000,000 ticks");
|
|
Require("ReferenceClockScale", "whole and fractional",
|
|
Sync::ScaleReferenceClock(3750000000ull, 3000000000ull, value) &&
|
|
value == 125000000ull,
|
|
"host 1.25 seconds did not scale to 125,000,000 ticks");
|
|
Require("ReferenceClockScale", "monotonic floor",
|
|
Sync::ScaleReferenceClock(3750000001ull, 3000000000ull, value) &&
|
|
value == 125000000ull,
|
|
"sub-reference-tick increment did not use a monotonic floor");
|
|
Require("ReferenceClockScale", "guards",
|
|
!Sync::ScaleReferenceClock(1, 0, value) &&
|
|
!Sync::ScaleReferenceClock(UINT64_MAX, 1, value),
|
|
"invalid frequency or overflow was accepted");
|
|
std::printf("[host] %-32s ok\n", "ReferenceClockScale");
|
|
}
|
|
|
|
void CheckClipControlDepthClipState() {
|
|
HW::ClipControl clip;
|
|
Require("ClipControlDepthClipState", "default", clip.IsZClipEnabled(),
|
|
"default paired Z clipping was not enabled");
|
|
|
|
clip.min_z_clip_disable = true;
|
|
Require("ClipControlDepthClipState", "asymmetric near",
|
|
!clip.IsZClipEnabled(),
|
|
"near-plane disable did not disable paired host Z clipping");
|
|
|
|
clip.min_z_clip_disable = false;
|
|
clip.max_z_clip_disable = true;
|
|
Require("ClipControlDepthClipState", "asymmetric far", !clip.IsZClipEnabled(),
|
|
"far-plane disable did not disable paired host Z clipping");
|
|
|
|
clip.min_z_clip_disable = true;
|
|
Require("ClipControlDepthClipState", "both disabled", !clip.IsZClipEnabled(),
|
|
"paired Z-clip disable was not represented");
|
|
std::printf("[host] %-32s ok\n", "ClipControlDepthClipState");
|
|
}
|
|
|
|
void CheckVulkan13FeatureRequirements() {
|
|
const auto features = WindowContext::RequiredVulkan13Features();
|
|
Require(
|
|
"Vulkan13FeatureRequirements", "production requirements",
|
|
features.sType == vk::StructureType::ePhysicalDeviceVulkan13Features &&
|
|
features.pNext == nullptr && features.dynamicRendering == VK_TRUE &&
|
|
features.synchronization2 == VK_TRUE,
|
|
"production device creation did not require dynamic rendering and "
|
|
"synchronization2 from one feature declaration");
|
|
std::printf("[host] %-32s ok\n", "Vulkan13FeatureRequirements");
|
|
}
|
|
|
|
void CheckPm4AcquireMemNoOp(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
const std::array<uint32_t, 6> standard_payload{0x00400000u, 1u, 0u,
|
|
0u, 0u, 10u};
|
|
const std::array<uint32_t, 7> custom_payload{
|
|
0xdeadbeefu, 0xffffffffu, 0x80000000u, 0x13579bdfu,
|
|
0x2468ace0u, 0xaaaaaaaau, 0x55555555u};
|
|
const auto standard_before = standard_payload;
|
|
const auto custom_before = custom_payload;
|
|
Require("Pm4AcquireMemNoOp", "recognized packets",
|
|
CpOpAcquireMem(processor, 0xC0055800u, standard_payload.data(), 0,
|
|
0) == 6 &&
|
|
CpOpAcquireMem(processor, 0xc0061050u, custom_payload.data(), 0,
|
|
0) == 7 &&
|
|
standard_payload == standard_before &&
|
|
custom_payload == custom_before,
|
|
"ACQUIRE_MEM did not consume both pinned packet forms as "
|
|
"side-effect-free "
|
|
"no-ops");
|
|
std::printf("[host] %-32s ok\n", "Pm4AcquireMemNoOp");
|
|
}
|
|
|
|
void CheckPm4SyntheticOcclusionCounterDump(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
constexpr uint64_t untouched = 0x1122334455667788ull;
|
|
constexpr uint64_t ready_bit = 1ull << 63u;
|
|
alignas(16) std::array<std::array<uint64_t, 2>, 16> results;
|
|
for (auto &pair : results) {
|
|
pair.fill(untouched);
|
|
}
|
|
|
|
const auto dump = [&](uint64_t *address) {
|
|
const auto raw_address = reinterpret_cast<uint64_t>(address);
|
|
std::array<uint32_t, 4> packet{
|
|
KYTY_PM4(4, Pm4::IT_EVENT_WRITE, Pm4::R_ZERO), 0x00000139u,
|
|
static_cast<uint32_t>(raw_address),
|
|
static_cast<uint32_t>(raw_address >> 32u)};
|
|
Pm4Execution execution;
|
|
return processor.Process(execution, packet);
|
|
};
|
|
|
|
const auto begin_result = dump(&results[0][0]);
|
|
bool begin_written = begin_result == Pm4ProcessResult::Complete;
|
|
for (const auto &pair : results) {
|
|
begin_written &= pair[0] == ready_bit && pair[1] == untouched;
|
|
}
|
|
|
|
const auto end_result = dump(&results[0][1]);
|
|
bool end_written = end_result == Pm4ProcessResult::Complete;
|
|
uint64_t visible_samples = 0;
|
|
for (const auto &pair : results) {
|
|
end_written &= pair[0] == ready_bit && pair[1] == (ready_bit | 1u);
|
|
visible_samples += pair[1] - pair[0];
|
|
}
|
|
|
|
Require("Pm4SyntheticOcclusionCounterDump", "always-visible result",
|
|
begin_written && end_written && visible_samples == results.size(),
|
|
"EVENT_WRITE did not publish valid nonzero begin/end counters to "
|
|
"every PS5 DB");
|
|
std::printf("[host] %-32s ok\n", "Pm4SyntheticOcclusionCounterDump");
|
|
}
|
|
|
|
void CheckPm4StencilInfoValueLane(RenderContext &renderer) {
|
|
CommandProcessor processor(renderer, 0);
|
|
constexpr std::array<uint32_t, 2> payload{0x00100801u, 0x28000000u};
|
|
const auto consumed = HwCtxSetStencilInfo(
|
|
processor, 0xC0016900u, Pm4::DB_STENCIL_INFO, payload.data(), 1);
|
|
const auto &stencil = processor.GetCtx().GetDepthStencilInfo();
|
|
Require("Pm4StencilInfoValueLane", "standalone register value",
|
|
consumed == 1 && stencil.format == Prospero::StencilFormat::k8UInt &&
|
|
stencil.texture_compatibility ==
|
|
Prospero::TextureCompatibleStencil::kEnable &&
|
|
!stencil.expclear_enabled && !stencil.htile_stencil_disabled,
|
|
"standalone DB_STENCIL_INFO did not decode its sole payload value");
|
|
std::printf("[host] %-32s ok\n", "Pm4StencilInfoValueLane");
|
|
}
|
|
|
|
void CheckPm4NativeTargetGeometryRegisters(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor packet(renderer, 0);
|
|
CommandProcessor indirect(renderer, 0);
|
|
|
|
const auto write_context = [](CommandProcessor &processor, uint32_t offset,
|
|
uint32_t value) {
|
|
std::array<uint32_t, 3> command{
|
|
KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO), offset, value};
|
|
Pm4Execution execution;
|
|
return processor.Process(execution, command) == Pm4ProcessResult::Complete;
|
|
};
|
|
|
|
constexpr uint32_t color_slot = 7;
|
|
constexpr uint32_t width_minus_one = 1919;
|
|
constexpr uint32_t height_minus_one = 1079;
|
|
constexpr uint32_t depth_minus_one = 5;
|
|
constexpr uint32_t max_mip = 3;
|
|
constexpr uint32_t dimension_3d = 2;
|
|
constexpr uint32_t attrib2_value =
|
|
height_minus_one | (width_minus_one << 14u) | (max_mip << 28u);
|
|
constexpr uint32_t attrib3_value =
|
|
depth_minus_one |
|
|
(static_cast<uint32_t>(Prospero::TileMode::kStandard64KB) << 14u) |
|
|
(dimension_3d << 24u) | (1u << 26u) | (1u << 30u);
|
|
|
|
const bool color_packets_complete =
|
|
write_context(packet, Pm4::CB_COLOR7_ATTRIB2, attrib2_value) &&
|
|
write_context(packet, Pm4::CB_COLOR7_ATTRIB3, attrib3_value);
|
|
g_hw_ctx_indirect_func[Pm4::CB_COLOR7_ATTRIB2](
|
|
indirect, Pm4::CB_COLOR7_ATTRIB2, attrib2_value);
|
|
g_hw_ctx_indirect_func[Pm4::CB_COLOR7_ATTRIB3](
|
|
indirect, Pm4::CB_COLOR7_ATTRIB3, attrib3_value);
|
|
|
|
const auto color_matches = [](const HW::RenderTarget &target) {
|
|
return target.attrib2.width == width_minus_one &&
|
|
target.attrib2.height == height_minus_one &&
|
|
target.attrib2.num_mip_levels == max_mip &&
|
|
target.attrib3.depth == depth_minus_one &&
|
|
target.attrib3.tile_mode == Prospero::TileMode::kStandard64KB &&
|
|
target.attrib3.dimension == dimension_3d &&
|
|
target.attrib3.metadata_pipe_aligned;
|
|
};
|
|
Require("Pm4NativeTargetGeometry", "sparse color geometry",
|
|
color_packets_complete &&
|
|
color_matches(packet.GetCtx().GetRenderTarget(color_slot)) &&
|
|
color_matches(indirect.GetCtx().GetRenderTarget(color_slot)) &&
|
|
!color_matches(packet.GetCtx().GetRenderTarget(0)) &&
|
|
!color_matches(indirect.GetCtx().GetRenderTarget(0)),
|
|
"ATTRIB2/ATTRIB3 did not provide native color extent and layout");
|
|
|
|
constexpr uint32_t depth_size_value =
|
|
width_minus_one | (height_minus_one << 16u);
|
|
const bool depth_packet_complete =
|
|
write_context(packet, Pm4::DB_DEPTH_SIZE_XY, depth_size_value);
|
|
g_hw_ctx_indirect_func[Pm4::DB_DEPTH_SIZE_XY](indirect, Pm4::DB_DEPTH_SIZE_XY,
|
|
depth_size_value);
|
|
const auto depth_matches = [](const HW::DepthRenderTarget &target) {
|
|
return target.size.valid && target.size.x_max == width_minus_one &&
|
|
target.size.y_max == height_minus_one;
|
|
};
|
|
Require("Pm4NativeTargetGeometry", "native depth geometry",
|
|
depth_packet_complete &&
|
|
depth_matches(packet.GetCtx().GetDepthRenderTarget()) &&
|
|
depth_matches(indirect.GetCtx().GetDepthRenderTarget()),
|
|
"DB_DEPTH_SIZE_XY did not provide the native depth extent");
|
|
|
|
bool legacy_slots_are_unhandled = true;
|
|
for (uint32_t slot = 0; slot < 8; slot++) {
|
|
const auto stride = slot * 15u;
|
|
const std::array legacy_color_slots{
|
|
Pm4::CB_COLOR0_BASE + stride + 1u,
|
|
Pm4::CB_COLOR0_BASE + stride + 2u,
|
|
Pm4::CB_COLOR0_CMASK + stride + 1u,
|
|
Pm4::CB_COLOR0_FMASK + stride + 1u,
|
|
};
|
|
for (const auto offset : legacy_color_slots) {
|
|
legacy_slots_are_unhandled &= g_hw_ctx_func[offset] == nullptr &&
|
|
g_hw_ctx_indirect_func[offset] == nullptr;
|
|
}
|
|
}
|
|
// These offsets are the removed GCN DB_DEPTH_INFO, DB_DEPTH_SIZE, and
|
|
// DB_DEPTH_SLICE holes. Keep them numeric so deleting their legacy PM4 names
|
|
// does not weaken this dispatch-table regression check.
|
|
constexpr std::array<uint32_t, 3> legacy_depth_slots{0x000fu, 0x0016u,
|
|
0x0017u};
|
|
for (const auto offset : legacy_depth_slots) {
|
|
legacy_slots_are_unhandled &= g_hw_ctx_func[offset] == nullptr &&
|
|
g_hw_ctx_indirect_func[offset] == nullptr;
|
|
}
|
|
// Removed GCN PA_SC_NGG_MODE_CNTL.
|
|
legacy_slots_are_unhandled &= g_hw_ctx_func[0x0314u] == nullptr &&
|
|
g_hw_ctx_indirect_func[0x0314u] == nullptr;
|
|
|
|
// Removed GCN shader resource/checksum/queue registers. Numeric offsets keep
|
|
// this check independent of the deleted legacy names.
|
|
constexpr std::array<uint32_t, 19> legacy_shader_slots{
|
|
0x000u, 0x001u, 0x002u, 0x003u, 0x030u, 0x0b0u, 0x0bcu, 0x130u,
|
|
0x14au, 0x14bu, 0x20eu, 0x20fu, 0x210u, 0x211u, 0x216u, 0x217u,
|
|
0x219u, 0x21au, 0x27du,
|
|
};
|
|
for (const auto offset : legacy_shader_slots) {
|
|
legacy_slots_are_unhandled &= g_hw_sh_func[offset] == nullptr &&
|
|
g_hw_sh_indirect_func[offset] == nullptr;
|
|
}
|
|
for (uint32_t offset = 0x0cau; offset <= 0x0ebu; offset++) {
|
|
legacy_slots_are_unhandled &= g_hw_sh_func[offset] == nullptr &&
|
|
g_hw_sh_indirect_func[offset] == nullptr;
|
|
}
|
|
|
|
std::array<uint32_t, 3> index_size_packet{
|
|
KYTY_PM4(3, Pm4::IT_SET_UCONFIG_REG_INDEX, Pm4::R_ZERO),
|
|
0x20000000u | Pm4::VGT_INDEX_TYPE,
|
|
0x441u,
|
|
};
|
|
Pm4Execution index_size_execution;
|
|
const bool native_index_size_packet_complete =
|
|
packet.Process(index_size_execution, index_size_packet) ==
|
|
Pm4ProcessResult::Complete &&
|
|
g_hw_uc_func[Pm4::VGT_INDEX_TYPE] != nullptr &&
|
|
g_hw_uc_indirect_func[Pm4::VGT_INDEX_TYPE] != nullptr &&
|
|
g_hw_uc_func[Pm4::IA_MULTI_VGT_PARAM] != nullptr &&
|
|
g_hw_uc_indirect_func[Pm4::IA_MULTI_VGT_PARAM] != nullptr;
|
|
|
|
const bool native_pace_slots_are_handled =
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PACE_ID_PS] != nullptr &&
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PACE_ID_GS] != nullptr &&
|
|
g_hw_sh_indirect_func[Pm4::COMPUTE_PACE_ID] != nullptr;
|
|
Require("Pm4NativeTargetGeometry", "legacy register holes",
|
|
legacy_slots_are_unhandled && native_pace_slots_are_handled &&
|
|
native_index_size_packet_complete,
|
|
"a removed GCN register has a handler or a native packet is not handled");
|
|
std::printf("[host] %-32s ok\n", "Pm4NativeTargetGeometry");
|
|
}
|
|
|
|
void CheckPm4PrivateAgcShaderRegisters(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
std::array<uint32_t, 6> registers{
|
|
Pm4::SPI_SHADER_PGM_RSRC4_GS, 0x0badc0deu,
|
|
Pm4::SPI_SHADER_PGM_CHKSUM_HS, 0x12345678u,
|
|
Pm4::SPI_SHADER_PGM_RSRC4_HS, 0x87654321u,
|
|
};
|
|
const auto address = reinterpret_cast<uint64_t>(registers.data());
|
|
std::array<uint32_t, 5> command{
|
|
KYTY_PM4(5, Pm4::IT_SET_SH_REG_INDIRECT, Pm4::R_ZERO),
|
|
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
|
0x80000000u, 3u,
|
|
};
|
|
Pm4Execution execution;
|
|
const bool handlers_present =
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] != nullptr &&
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_CHKSUM_HS] != nullptr &&
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC4_HS] != nullptr;
|
|
Require("Pm4PrivateAgcShaderRegisters", "private shader register stream",
|
|
handlers_present &&
|
|
processor.Process(execution, command) == Pm4ProcessResult::Complete,
|
|
"private AGC shader registers were rejected by SET_SH_REG_INDIRECT");
|
|
std::printf("[host] %-32s ok\n", "Pm4PrivateAgcShaderRegisters");
|
|
}
|
|
|
|
void CheckPm4PrivateAgcUconfigRegisters(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
std::array<uint32_t, 2> registers{
|
|
Pm4::UC_PARAMETER_OVERSUBSCRIPTION, 0x12345678u,
|
|
};
|
|
const auto address = reinterpret_cast<uint64_t>(registers.data());
|
|
std::array<uint32_t, 5> command{
|
|
KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT, Pm4::R_ZERO),
|
|
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
|
0x80000000u, 1u,
|
|
};
|
|
Pm4Execution execution;
|
|
const bool handler_present =
|
|
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] != nullptr;
|
|
Require("Pm4PrivateAgcUconfigRegisters", "private UCONFIG register stream",
|
|
handler_present &&
|
|
processor.Process(execution, command) == Pm4ProcessResult::Complete,
|
|
"private AGC UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT");
|
|
std::printf("[host] %-32s ok\n", "Pm4PrivateAgcUconfigRegisters");
|
|
}
|
|
|
|
void CheckPm4DirectShaderRegisterFallback(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
constexpr uint64_t shader_address = 0x0000123456789a00ull;
|
|
std::array<uint32_t, 4> packet{
|
|
KYTY_PM4(4, Pm4::IT_SET_SH_REG, Pm4::R_ZERO),
|
|
Pm4::SPI_SHADER_PGM_LO_ES,
|
|
static_cast<uint32_t>(shader_address >> 8u),
|
|
static_cast<uint32_t>(shader_address >> 40u),
|
|
};
|
|
Pm4Execution execution;
|
|
Require("Pm4DirectShaderRegisterFallback", "ES program base",
|
|
processor.Process(execution, packet) == Pm4ProcessResult::Complete &&
|
|
processor.GetShCtx().GetVs().es_regs.data_addr == shader_address,
|
|
"direct SET_SH_REG did not route its ES base pair through the "
|
|
"register handlers");
|
|
std::printf("[host] %-32s ok\n", "Pm4DirectShaderRegisterFallback");
|
|
}
|
|
|
|
void CheckPm4GuardBandRegisterRanges(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
processor.GetCtx().SetGuardBands(2.0f, 3.0f, 4.0f, 5.0f);
|
|
|
|
std::array<uint32_t, 3> single{
|
|
KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::PA_CL_GB_VERT_CLIP_ADJ,
|
|
std::bit_cast<uint32_t>(6.0f),
|
|
};
|
|
Pm4Execution single_execution;
|
|
const auto single_result = processor.Process(single_execution, single);
|
|
const auto &single_viewport = processor.GetCtx().GetScreenViewport();
|
|
Require("Pm4GuardBandRegisterRanges", "single register",
|
|
single_result == Pm4ProcessResult::Complete &&
|
|
single_viewport.guard_band_horz_clip == 2.0f &&
|
|
single_viewport.guard_band_vert_clip == 6.0f &&
|
|
single_viewport.guard_band_horz_discard == 4.0f &&
|
|
single_viewport.guard_band_vert_discard == 5.0f,
|
|
"single guard-band write changed the wrong context state");
|
|
|
|
std::array<uint32_t, 6> range{
|
|
KYTY_PM4(6, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::PA_CL_GB_VERT_CLIP_ADJ,
|
|
std::bit_cast<uint32_t>(7.0f),
|
|
std::bit_cast<uint32_t>(8.0f),
|
|
std::bit_cast<uint32_t>(9.0f),
|
|
std::bit_cast<uint32_t>(10.0f),
|
|
};
|
|
Pm4Execution range_execution;
|
|
const auto range_result = processor.Process(range_execution, range);
|
|
const auto &range_viewport = processor.GetCtx().GetScreenViewport();
|
|
Require("Pm4GuardBandRegisterRanges", "full range",
|
|
range_result == Pm4ProcessResult::Complete &&
|
|
range_viewport.guard_band_horz_clip == 9.0f &&
|
|
range_viewport.guard_band_vert_clip == 7.0f &&
|
|
range_viewport.guard_band_horz_discard == 10.0f &&
|
|
range_viewport.guard_band_vert_discard == 8.0f,
|
|
"full guard-band range did not preserve hardware register order");
|
|
std::printf("[host] %-32s ok\n", "Pm4GuardBandRegisterRanges");
|
|
}
|
|
|
|
void CheckPm4BlendColorRegisterRanges(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
processor.GetCtx().SetBlendColor({1.0f, 2.0f, 3.0f, 4.0f});
|
|
|
|
constexpr std::array<std::pair<uint32_t, float>, 4> singleton_values{{
|
|
{Pm4::CB_BLEND_RED, 5.0f},
|
|
{Pm4::CB_BLEND_BLUE, 7.0f},
|
|
{Pm4::CB_BLEND_GREEN, 6.0f},
|
|
{Pm4::CB_BLEND_ALPHA, 8.0f},
|
|
}};
|
|
bool singletons_complete = true;
|
|
for (const auto &[offset, value] : singleton_values) {
|
|
std::array<uint32_t, 3> packet{
|
|
KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
offset,
|
|
std::bit_cast<uint32_t>(value),
|
|
};
|
|
Pm4Execution execution;
|
|
singletons_complete &=
|
|
processor.Process(execution, packet) == Pm4ProcessResult::Complete;
|
|
}
|
|
const auto &singletons = processor.GetCtx().GetBlendColor();
|
|
Require("Pm4BlendColorRegisterRanges", "single registers",
|
|
singletons_complete && singletons.red == 5.0f &&
|
|
singletons.green == 6.0f && singletons.blue == 7.0f &&
|
|
singletons.alpha == 8.0f,
|
|
"single blend-color writes did not follow native PS5 register order");
|
|
|
|
std::array<uint32_t, 4> partial{
|
|
KYTY_PM4(4, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::CB_BLEND_BLUE,
|
|
std::bit_cast<uint32_t>(9.0f),
|
|
std::bit_cast<uint32_t>(10.0f),
|
|
};
|
|
Pm4Execution partial_execution;
|
|
const auto partial_result = processor.Process(partial_execution, partial);
|
|
const auto &partial_color = processor.GetCtx().GetBlendColor();
|
|
Require("Pm4BlendColorRegisterRanges", "partial range",
|
|
partial_result == Pm4ProcessResult::Complete &&
|
|
partial_color.red == 5.0f && partial_color.green == 10.0f &&
|
|
partial_color.blue == 9.0f && partial_color.alpha == 8.0f,
|
|
"partial blend-color range changed the wrong channels");
|
|
|
|
std::array<uint32_t, 6> full{
|
|
KYTY_PM4(6, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::CB_BLEND_RED,
|
|
std::bit_cast<uint32_t>(11.0f),
|
|
std::bit_cast<uint32_t>(13.0f),
|
|
std::bit_cast<uint32_t>(12.0f),
|
|
std::bit_cast<uint32_t>(14.0f),
|
|
};
|
|
Pm4Execution full_execution;
|
|
const auto full_result = processor.Process(full_execution, full);
|
|
const auto &full_color = processor.GetCtx().GetBlendColor();
|
|
Require("Pm4BlendColorRegisterRanges", "full range",
|
|
full_result == Pm4ProcessResult::Complete &&
|
|
full_color.red == 11.0f && full_color.green == 12.0f &&
|
|
full_color.blue == 13.0f && full_color.alpha == 14.0f,
|
|
"full blend-color range did not preserve native PS5 register order");
|
|
std::printf("[host] %-32s ok\n", "Pm4BlendColorRegisterRanges");
|
|
}
|
|
|
|
void CheckPm4PolygonOffsetRegisters(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
const auto &defaults = processor.GetCtx().GetPolyOffset();
|
|
Require("Pm4PolygonOffset", "format defaults",
|
|
defaults.neg_num_db_bits == -23 && defaults.db_is_float_fmt,
|
|
"polygon offset Z format did not default to D32F");
|
|
|
|
constexpr std::array<uint32_t, 6> payload{0x000000f0u,
|
|
std::bit_cast<uint32_t>(2.5f),
|
|
std::bit_cast<uint32_t>(16.0f),
|
|
std::bit_cast<uint32_t>(-3.25f),
|
|
std::bit_cast<uint32_t>(32.0f),
|
|
std::bit_cast<uint32_t>(4.5f)};
|
|
const auto consumed = HwCtxSetPolyOffsetRegisters(
|
|
processor, KYTY_PM4(8, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL, payload.data(), 0);
|
|
const auto &direct = processor.GetCtx().GetPolyOffset();
|
|
Require("Pm4PolygonOffset", "direct registers",
|
|
consumed == payload.size() && direct.neg_num_db_bits == -16 &&
|
|
!direct.db_is_float_fmt && direct.clamp == 2.5f &&
|
|
direct.front_scale == 16.0f && direct.front_offset == -3.25f &&
|
|
direct.back_scale == 32.0f && direct.back_offset == 4.5f,
|
|
"direct polygon offset register packet was decoded incorrectly");
|
|
|
|
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL](
|
|
processor, Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL, 0x000001e9u);
|
|
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE](
|
|
processor, Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE,
|
|
std::bit_cast<uint32_t>(24.0f));
|
|
const auto &indirect = processor.GetCtx().GetPolyOffset();
|
|
Require("Pm4PolygonOffset", "indirect registers",
|
|
indirect.neg_num_db_bits == -23 && indirect.db_is_float_fmt &&
|
|
indirect.front_scale == 24.0f && indirect.front_offset == -3.25f,
|
|
"indirect polygon offset register write was decoded incorrectly");
|
|
std::printf("[host] %-32s ok\n", "Pm4PolygonOffset");
|
|
}
|
|
|
|
void CheckPm4DepthControlHighBits(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
|
|
std::array<uint32_t, 3> direct{
|
|
KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::DB_DEPTH_CONTROL,
|
|
0x80000072u,
|
|
};
|
|
Pm4Execution direct_execution;
|
|
const bool direct_complete =
|
|
processor.Process(direct_execution, direct) == Pm4ProcessResult::Complete;
|
|
const auto &direct_control = processor.GetCtx().GetDepthControl();
|
|
const bool direct_decoded =
|
|
!direct_control.stencil_enable && direct_control.z_enable &&
|
|
!direct_control.z_write_enable && !direct_control.depth_bounds_enable &&
|
|
direct_control.zfunc == 7u && !direct_control.backface_enable &&
|
|
direct_control.stencilfunc == 0u && direct_control.stencilfunc_bf == 0u;
|
|
|
|
std::array<uint32_t, 2> registers{
|
|
Pm4::DB_DEPTH_CONTROL,
|
|
0x4070078du,
|
|
};
|
|
const auto address = reinterpret_cast<uint64_t>(registers.data());
|
|
std::array<uint32_t, 5> indirect{
|
|
KYTY_PM4(5, Pm4::IT_SET_CONTEXT_REG_INDIRECT, Pm4::R_ZERO),
|
|
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
|
0x80000000u, 1u,
|
|
};
|
|
Pm4Execution indirect_execution;
|
|
const bool indirect_complete =
|
|
processor.Process(indirect_execution, indirect) ==
|
|
Pm4ProcessResult::Complete;
|
|
const auto &indirect_control = processor.GetCtx().GetDepthControl();
|
|
const bool indirect_decoded =
|
|
indirect_control.stencil_enable && !indirect_control.z_enable &&
|
|
indirect_control.z_write_enable && indirect_control.depth_bounds_enable &&
|
|
indirect_control.zfunc == 0u && indirect_control.backface_enable &&
|
|
indirect_control.stencilfunc == 7u &&
|
|
indirect_control.stencilfunc_bf == 7u;
|
|
|
|
Require("Pm4DepthControlHighBits", "preserved high bits",
|
|
direct_complete && direct_decoded && indirect_complete &&
|
|
indirect_decoded,
|
|
"DB_DEPTH_CONTROL high bits rejected or changed decoded PS5 state");
|
|
std::printf("[host] %-32s ok\n", "Pm4DepthControlHighBits");
|
|
}
|
|
|
|
void CheckPm4DepthRenderOverride(RenderContext &renderer) {
|
|
constexpr const char *name = "Pm4DepthRenderOverride";
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
struct Case {
|
|
uint32_t value;
|
|
HW::DepthRenderOverride expected;
|
|
};
|
|
constexpr std::array cases{Case{0x28000000u, {true, true, false, false}},
|
|
Case{0x50000000u, {false, false, true, true}},
|
|
Case{0x18000000u, {false, true, false, true}},
|
|
Case{0x60000000u, {true, false, true, false}},
|
|
Case{0xf8000000u, {true, true, true, true}},
|
|
Case{0x82001a0fu, {false, false, false, false}}};
|
|
for (const bool indirect : {false, true}) {
|
|
for (const auto &test : cases) {
|
|
std::array<uint32_t, 2> registers{Pm4::DB_RENDER_OVERRIDE, test.value};
|
|
const auto address = reinterpret_cast<uint64_t>(registers.data());
|
|
const std::array<uint32_t, 3> direct_packet{
|
|
KYTY_PM4(3, Pm4::IT_SET_CONTEXT_REG, Pm4::R_ZERO),
|
|
Pm4::DB_RENDER_OVERRIDE, test.value};
|
|
const std::array<uint32_t, 5> indirect_packet{
|
|
KYTY_PM4(5, Pm4::IT_SET_CONTEXT_REG_INDIRECT, Pm4::R_ZERO),
|
|
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
|
0x80000000u, 1u};
|
|
Pm4Execution execution;
|
|
const auto result = processor.Process(
|
|
execution, indirect ? std::span<const uint32_t>{indirect_packet}
|
|
: std::span<const uint32_t>{direct_packet});
|
|
const auto &actual = processor.GetCtx().GetDepthRenderOverride();
|
|
Require(
|
|
name, indirect ? "indirect register" : "direct register",
|
|
result == Pm4ProcessResult::Complete &&
|
|
actual.force_z_valid == test.expected.force_z_valid &&
|
|
actual.force_z_dirty == test.expected.force_z_dirty &&
|
|
actual.force_stencil_valid == test.expected.force_stencil_valid &&
|
|
actual.force_stencil_dirty == test.expected.force_stencil_dirty,
|
|
"DB_RENDER_OVERRIDE lost, exchanged, or retained force flags");
|
|
}
|
|
}
|
|
std::printf("[host] %-32s ok\n", name);
|
|
}
|
|
|
|
void CheckAgcShaderFusion() {
|
|
struct Case {
|
|
bool gs;
|
|
uint32_t front1, front2, back1, back2, fused1, fused2, older2;
|
|
};
|
|
// Register results from both exports in the supplied libSceAgc.sprx.
|
|
constexpr Case cases[]{
|
|
{true, 0x600c0007, 0x00030010, 0x600c0007, 0x008b0000,
|
|
0x600c0007, 0x008b0010, 0x008b0010}, // Captured volume ES + GS.
|
|
{true, 0x20ac0003, 0xf805004a, 0x40ec0007, 0x028b003e,
|
|
0x40ec0007, 0x1a8f000a, 0xfa8f000a},
|
|
{false, 0x20ac0003, 0xf805004a, 0x10ec0007, 0x028b003e,
|
|
0x20ec0007, 0x1a8b000a, 0xfa8b000a},
|
|
};
|
|
ShaderUserData user_data{};
|
|
ShaderSpecialRegs specials{};
|
|
for (const auto &c : cases) {
|
|
const auto checksum = c.gs ? Pm4::SPI_SHADER_PGM_CHKSUM_GS : Pm4::SPI_SHADER_PGM_CHKSUM_HS;
|
|
const auto rsrc1 = c.gs ? Pm4::SPI_SHADER_PGM_RSRC1_GS : Pm4::SPI_SHADER_PGM_RSRC1_HS;
|
|
const auto rsrc2 = c.gs ? Pm4::SPI_SHADER_PGM_RSRC2_GS : Pm4::SPI_SHADER_PGM_RSRC2_HS;
|
|
const auto lo = c.gs ? Pm4::SPI_SHADER_PGM_LO_ES : Pm4::SPI_SHADER_PGM_LO_LS;
|
|
std::array<ShaderRegister, 6> front_regs{{
|
|
{checksum, 0x1234}, {checksum, 0x5678}, {rsrc1, c.front1},
|
|
{rsrc2, c.front2}, {lo, 0}, {lo + 1u, 0}}};
|
|
const std::array<ShaderRegister, 6> back_regs{{
|
|
{checksum, 0}, {checksum, 0}, {rsrc1, c.back1},
|
|
{rsrc2, c.back2}, {lo, 0}, {lo + 1u, 0xabcdef00}}};
|
|
Shader front{};
|
|
front.user_data = &user_data;
|
|
front.code = reinterpret_cast<const void *>(0x12580e91c00ull);
|
|
front.sh_registers = front_regs.data();
|
|
front.specials = &specials;
|
|
front.type = static_cast<uint8_t>(c.gs ? Prospero::ShaderBinaryType::kGsFront
|
|
: Prospero::ShaderBinaryType::kHsFront);
|
|
front.num_sh_registers = front_regs.size();
|
|
for (const bool older : {false, true}) {
|
|
for (const bool in_place : {false, true}) {
|
|
auto registers = back_regs;
|
|
std::array<ShaderRegister, 6> scratch{};
|
|
Shader back = front;
|
|
back.sh_registers = registers.data();
|
|
back.type = static_cast<uint8_t>(c.gs ? Prospero::ShaderBinaryType::kGsBack
|
|
: Prospero::ShaderBinaryType::kHsBack);
|
|
Shader fused{};
|
|
const auto fuse = older ? Gen5::AgcUnknownNApJjpKNBl4 : Gen5::AgcUnknownFuseShaderHalves;
|
|
const auto result = fuse(&fused, &front, &back, in_place ? nullptr : scratch.data());
|
|
const auto *regs = fused.sh_registers;
|
|
Require("AgcShaderFusion", "native fused registers",
|
|
result == 0 && regs == (in_place ? registers.data() : scratch.data()) &&
|
|
regs[0].value == 0x1234 && regs[1].value == 0x5678 &&
|
|
regs[2].value == c.fused1 && regs[3].value == (older ? c.older2 : c.fused2) &&
|
|
regs[4].value == 0x2580e91c && regs[5].value == 0xabcdef01 &&
|
|
fused.type == static_cast<uint8_t>(c.gs ? Prospero::ShaderBinaryType::kGs
|
|
: Prospero::ShaderBinaryType::kHs) &&
|
|
fused.user_data == (older ? &user_data : nullptr) &&
|
|
(in_place || std::memcmp(registers.data(), back_regs.data(), sizeof(registers)) == 0),
|
|
"AGC lost front resources or changed the export's allocation/metadata behavior");
|
|
}
|
|
}
|
|
}
|
|
std::printf("[host] %-32s ok\n", "AgcShaderFusion");
|
|
}
|
|
|
|
struct AgcCommandBufferLayout {
|
|
using Callback = KYTY_SYSV_ABI bool (*)(Gen5::CommandBuffer *, uint32_t,
|
|
void *);
|
|
|
|
uint32_t *bottom;
|
|
uint32_t *top;
|
|
uint32_t *cursor_up;
|
|
uint32_t *cursor_down;
|
|
Callback callback;
|
|
void *user_data;
|
|
uint32_t reserved_dw;
|
|
};
|
|
static_assert(offsetof(AgcCommandBufferLayout, cursor_up) == 0x10);
|
|
static_assert(offsetof(AgcCommandBufferLayout, reserved_dw) == 0x30);
|
|
|
|
void CheckAgcWaitPackets(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
constexpr auto packet_mismatch = static_cast<int>(0x8a6c000cu);
|
|
const auto execute = [&](uint32_t *packet, uint32_t size_dw) {
|
|
Pm4Execution execution;
|
|
return processor.Process(execution, {packet, size_dw}) ==
|
|
Pm4ProcessResult::Complete;
|
|
};
|
|
|
|
alignas(8) uint32_t label32 = 0x11223344u;
|
|
std::array<uint32_t, 16> packet32{};
|
|
AgcCommandBufferLayout dcb32{packet32.data(),
|
|
packet32.data() + packet32.size(),
|
|
packet32.data(),
|
|
packet32.data() + packet32.size(),
|
|
nullptr,
|
|
nullptr,
|
|
0};
|
|
const auto address32 = reinterpret_cast<uint64_t>(&label32);
|
|
auto *emitted32 =
|
|
Gen5::AgcDcbWaitRegMem(reinterpret_cast<Gen5::CommandBuffer *>(&dcb32), 0,
|
|
3, 0, 0, &label32, label32, UINT32_MAX, 400);
|
|
Require("AgcWaitPackets", "native 32-bit packet",
|
|
Gen5::AgcDcbWaitOnAddressGetSize(0) == 56 &&
|
|
emitted32 == packet32.data() &&
|
|
dcb32.cursor_up == packet32.data() + 14 &&
|
|
packet32[0] == 0xc0027904u && packet32[1] == 0x342u &&
|
|
packet32[2] ==
|
|
(0xc8010000u |
|
|
(static_cast<uint32_t>(address32 >> 32u) & 0xffffu)) &&
|
|
packet32[3] == static_cast<uint32_t>(address32) &&
|
|
packet32[4] == 0xc0053c00u && packet32[5] == 0x13u &&
|
|
packet32[6] == (static_cast<uint32_t>(address32) & ~0x3u) &&
|
|
packet32[7] ==
|
|
(static_cast<uint32_t>(address32 >> 32u) & 0x3ffffu) &&
|
|
packet32[8] == label32 && packet32[9] == UINT32_MAX &&
|
|
packet32[10] == 25u && packet32[11] == 0xc0017904u &&
|
|
packet32[12] == 0x342u && packet32[13] == 0xc8000000u &&
|
|
execute(packet32.data(), 14),
|
|
"32-bit waitOnAddress stream differs from native AGC");
|
|
|
|
alignas(8) uint32_t patched_label32 = 0xaabbccddu;
|
|
const auto patched_address32 = reinterpret_cast<uint64_t>(&patched_label32);
|
|
packet32[6] |= 0x2u;
|
|
packet32[7] |= 0xa5a40000u;
|
|
Require(
|
|
"AgcWaitPackets", "native 32-bit patch",
|
|
Gen5::AgcWaitRegMemPatchAddress(packet32.data(), &patched_label32) == 0 &&
|
|
Gen5::AgcWaitRegMemPatchReference(packet32.data(), patched_label32) ==
|
|
0 &&
|
|
packet32[2] ==
|
|
(0xc8010000u |
|
|
(static_cast<uint32_t>(patched_address32 >> 32u) & 0xffffu)) &&
|
|
packet32[3] == static_cast<uint32_t>(patched_address32) &&
|
|
packet32[6] ==
|
|
((static_cast<uint32_t>(patched_address32) & ~0x3u) | 0x2u) &&
|
|
packet32[7] ==
|
|
((static_cast<uint32_t>(patched_address32 >> 32u) & 0x3ffffu) |
|
|
0xa5a40000u) &&
|
|
packet32[8] == patched_label32 && execute(packet32.data(), 14),
|
|
"32-bit native wait patch did not update metadata and hardware packet");
|
|
|
|
alignas(8) uint64_t label64 = 0x1122334455667788ull;
|
|
std::array<uint32_t, 16> packet64{};
|
|
AgcCommandBufferLayout dcb64{packet64.data(),
|
|
packet64.data() + packet64.size(),
|
|
packet64.data(),
|
|
packet64.data() + packet64.size(),
|
|
nullptr,
|
|
nullptr,
|
|
0};
|
|
const auto address64 = reinterpret_cast<uint64_t>(&label64);
|
|
auto *emitted64 =
|
|
Gen5::AgcDcbWaitRegMem(reinterpret_cast<Gen5::CommandBuffer *>(&dcb64), 1,
|
|
3, 0, 0, &label64, label64, UINT64_MAX, 400);
|
|
Require("AgcWaitPackets", "native 64-bit packet",
|
|
Gen5::AgcDcbWaitOnAddressGetSize(1) == 64 &&
|
|
Gen5::AgcDcbWaitOnAddressGetSize(2) == 0 &&
|
|
emitted64 == packet64.data() &&
|
|
dcb64.cursor_up == packet64.data() + packet64.size() &&
|
|
packet64[0] == 0xc0027904u && packet64[1] == 0x342u &&
|
|
packet64[2] ==
|
|
(0xc8020000u |
|
|
(static_cast<uint32_t>(address64 >> 32u) & 0xffffu)) &&
|
|
packet64[3] == static_cast<uint32_t>(address64) &&
|
|
packet64[4] == 0xc0079300u && packet64[5] == 0x13u &&
|
|
packet64[6] == (static_cast<uint32_t>(address64) & ~0x7u) &&
|
|
packet64[7] ==
|
|
(static_cast<uint32_t>(address64 >> 32u) & 0x3ffffu) &&
|
|
packet64[8] == static_cast<uint32_t>(label64) &&
|
|
packet64[9] == static_cast<uint32_t>(label64 >> 32u) &&
|
|
packet64[10] == UINT32_MAX && packet64[11] == UINT32_MAX &&
|
|
packet64[12] == 25u && packet64[13] == 0xc0017904u &&
|
|
packet64[14] == 0x342u && packet64[15] == 0xc8000000u &&
|
|
execute(packet64.data(), packet64.size()),
|
|
"64-bit waitOnAddress stream differs from native AGC");
|
|
|
|
alignas(8) uint64_t patched_label64 = 0x11223344aabbccddull;
|
|
const auto patched_address64 = reinterpret_cast<uint64_t>(&patched_label64);
|
|
packet64[6] |= 0x5u;
|
|
packet64[7] |= 0x5a580000u;
|
|
Require(
|
|
"AgcWaitPackets", "native 64-bit patch",
|
|
Gen5::AgcWaitRegMemPatchAddress(packet64.data(), &patched_label64) == 0 &&
|
|
Gen5::AgcWaitRegMemPatchReference(packet64.data(),
|
|
0xdeadbeefaabbccddull) == 0 &&
|
|
packet64[2] ==
|
|
(0xc8020000u |
|
|
(static_cast<uint32_t>(patched_address64 >> 32u) & 0xffffu)) &&
|
|
packet64[3] == static_cast<uint32_t>(patched_address64) &&
|
|
packet64[6] ==
|
|
((static_cast<uint32_t>(patched_address64) & ~0x7u) | 0x5u) &&
|
|
packet64[7] ==
|
|
((static_cast<uint32_t>(patched_address64 >> 32u) & 0x3ffffu) |
|
|
0x5a580000u) &&
|
|
packet64[8] == 0xaabbccddu && packet64[9] == 0x11223344u &&
|
|
execute(packet64.data(), packet64.size()),
|
|
"64-bit native wait patch did not preserve the native high reference "
|
|
"DWORD");
|
|
|
|
std::array<uint32_t, 16> invalid{};
|
|
const uint32_t short_payload = 0x342u;
|
|
Require(
|
|
"AgcWaitPackets", "invalid packet",
|
|
Gen5::AgcWaitRegMemPatchAddress(invalid.data(), &label32) ==
|
|
packet_mismatch &&
|
|
Gen5::AgcWaitRegMemPatchReference(invalid.data(), label32) ==
|
|
packet_mismatch &&
|
|
!Gen5::AgcIsInternalDataPacket(
|
|
KYTY_PM4(2, Pm4::IT_SET_UCONFIG_REG, 1), &short_payload),
|
|
"native wait patch accepted a packet without the AGC metadata prefix");
|
|
std::printf("[host] %-32s ok\n", "AgcWaitPackets");
|
|
}
|
|
|
|
void CheckAgcDrawIndirectMultiPacket(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
const auto execute = [&](uint32_t *packet, uint32_t size_dw) {
|
|
Pm4Execution execution;
|
|
return processor.Process(execution, {packet, size_dw}) ==
|
|
Pm4ProcessResult::Complete;
|
|
};
|
|
|
|
constexpr uint64_t modifier = 0x0000000069380b1dull;
|
|
constexpr auto packet_mismatch = static_cast<int>(0x8a6c000cu);
|
|
const auto *count_address = reinterpret_cast<const volatile void *>(
|
|
static_cast<uintptr_t>(0x123456789abcdef3ull));
|
|
constexpr std::array<uint32_t, 16> expected{
|
|
0xc0017904u, 0x00000342u, 0xc6000008u, 0xc0082c00u,
|
|
0x11223344u, 0x00000111u, 0x00000113u, 0xc8000115u,
|
|
0x55667788u, 0x9abcdef0u, 0x12345678u, 0xaabbccddu,
|
|
0x00000022u, 0xc0017904u, 0x00000342u, 0xc6000000u};
|
|
|
|
std::array<uint32_t, 16> packet{};
|
|
AgcCommandBufferLayout dcb{packet.data(),
|
|
packet.data() + packet.size(),
|
|
packet.data(),
|
|
packet.data() + packet.size(),
|
|
nullptr,
|
|
nullptr,
|
|
0};
|
|
auto *emitted = Gen5::AgcDcbDrawIndirectMulti(
|
|
reinterpret_cast<Gen5::CommandBuffer *>(&dcb), 0x11223344u, 1u,
|
|
0x55667788u, count_address, 0xaabbccddu, modifier);
|
|
|
|
const uint32_t invalid_payload[]{0x342u, 0xc6000010u};
|
|
Require("AgcDrawIndirectMulti", "native packet",
|
|
emitted == packet.data() &&
|
|
dcb.cursor_up == packet.data() + packet.size() &&
|
|
packet == expected && execute(packet.data(), 3u) &&
|
|
execute(packet.data() + 13u, 3u) &&
|
|
Gen5::AgcWaitRegMemPatchAddress(packet.data(), count_address) ==
|
|
packet_mismatch &&
|
|
Gen5::AgcWaitRegMemPatchReference(packet.data(), 0x12345678u) ==
|
|
packet_mismatch &&
|
|
!Gen5::AgcIsInternalDataPacket(0xc0017904u, invalid_payload),
|
|
"drawIndirectMulti stream differs from native AGC");
|
|
std::printf("[host] %-32s ok\n", "AgcDrawIndirectMulti");
|
|
}
|
|
|
|
void CheckPm4ContextStateOperations(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
constexpr auto primitive_type = static_cast<Prospero::PrimitiveType>(0x35u);
|
|
|
|
HW::RenderControl original_control{};
|
|
original_control.depth_clear_enable = true;
|
|
original_control.stencil_clear_enable = true;
|
|
processor.GetCtx().SetRenderControl(original_control);
|
|
processor.GetCtx().SetRenderTargetMask(0x1234abcd);
|
|
processor.GetUcfg().SetPrimitiveType(primitive_type);
|
|
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_LO_PS](
|
|
processor, Pm4::SPI_SHADER_PGM_LO_PS, 0x00123456);
|
|
const auto shader_address = processor.GetShCtx().GetPs().ps_regs.data_addr;
|
|
const auto invoke = [&](ContextStateOperation operation) {
|
|
std::array<uint32_t, 32> packet{};
|
|
std::array<uint32_t, 6> segment_sizes{};
|
|
AgcCommandBufferLayout dcb{packet.data(),
|
|
packet.data() + packet.size(),
|
|
packet.data(),
|
|
packet.data() + packet.size(),
|
|
nullptr,
|
|
nullptr,
|
|
0};
|
|
const auto operation_value = static_cast<uint32_t>(operation);
|
|
size_t segment_count = 0;
|
|
switch (operation) {
|
|
case ContextStateOperation::Clear:
|
|
segment_sizes = {5};
|
|
segment_count = 1;
|
|
break;
|
|
case ContextStateOperation::Push:
|
|
segment_sizes = {5, 8, 9, 3, 2};
|
|
segment_count = 5;
|
|
break;
|
|
case ContextStateOperation::Pop:
|
|
segment_sizes = {3, 5, 8, 9, 2};
|
|
segment_count = 5;
|
|
break;
|
|
case ContextStateOperation::PushClear:
|
|
segment_sizes = {5, 8, 9, 3, 2, 5};
|
|
segment_count = 6;
|
|
break;
|
|
}
|
|
const auto size_dw = static_cast<size_t>(
|
|
Gen5::AgcDcbContextStateOpGetSize(operation_value) / 4);
|
|
auto *emitted = Gen5::AgcDcbContextStateOp(
|
|
reinterpret_cast<Gen5::CommandBuffer *>(&dcb), operation_value);
|
|
bool packet_matches = true;
|
|
size_t segment_offset = 0;
|
|
for (size_t i = 0; i < segment_count; i++) {
|
|
const auto segment_size = segment_sizes[i];
|
|
const auto selector = (i == 0 ? Pm4::R_CONTEXT_STATE : Pm4::R_ZERO);
|
|
packet_matches &= packet[segment_offset] ==
|
|
KYTY_PM4(segment_size, Pm4::IT_NOP, selector);
|
|
for (size_t j = 1; j < segment_size; j++) {
|
|
packet_matches &= packet[segment_offset + j] ==
|
|
(i == 0 && j == 1 ? operation_value : 0u);
|
|
}
|
|
segment_offset += segment_size;
|
|
}
|
|
Require("Pm4ContextState", "HLE packet",
|
|
emitted == packet.data() &&
|
|
dcb.cursor_up == packet.data() + size_dw &&
|
|
segment_offset == size_dw && packet_matches,
|
|
"context-state HLE packet does not match its real allocation size");
|
|
Pm4Execution execution;
|
|
return processor.Process(execution, {packet.data(), size_dw});
|
|
};
|
|
|
|
Require("Pm4ContextState", "push-clear",
|
|
invoke(ContextStateOperation::PushClear) ==
|
|
Pm4ProcessResult::Complete &&
|
|
!processor.GetCtx().GetRenderControl().depth_clear_enable &&
|
|
!processor.GetCtx().GetRenderControl().stencil_clear_enable &&
|
|
processor.GetCtx().GetRenderTargetMask() ==
|
|
HW::Context{}.GetRenderTargetMask() &&
|
|
processor.GetUcfg().GetPrimType() == primitive_type &&
|
|
processor.GetShCtx().GetPs().ps_regs.data_addr == shader_address,
|
|
"push-clear did not save and clear only Cx state");
|
|
|
|
processor.GetCtx().SetRenderTargetMask(0x55aa55aa);
|
|
Require("Pm4ContextState", "pop",
|
|
invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderControl().depth_clear_enable &&
|
|
processor.GetCtx().GetRenderControl().stencil_clear_enable &&
|
|
processor.GetCtx().GetRenderTargetMask() == 0x1234abcd,
|
|
"pop did not restore the complete saved Cx state");
|
|
|
|
Require("Pm4ContextState", "push",
|
|
invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderTargetMask() == 0x1234abcd,
|
|
"push changed live Cx state");
|
|
processor.GetCtx().SetRenderTargetMask(0x01020304);
|
|
Require("Pm4ContextState", "push-pop restore",
|
|
invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderTargetMask() == 0x1234abcd,
|
|
"ordinary push/pop did not restore Cx state");
|
|
|
|
Require("Pm4ContextState", "clear",
|
|
invoke(ContextStateOperation::Clear) == Pm4ProcessResult::Complete &&
|
|
!processor.GetCtx().GetRenderControl().depth_clear_enable &&
|
|
processor.GetCtx().GetRenderTargetMask() ==
|
|
HW::Context{}.GetRenderTargetMask() &&
|
|
processor.GetUcfg().GetPrimType() == primitive_type &&
|
|
processor.GetShCtx().GetPs().ps_regs.data_addr == shader_address,
|
|
"clear reset state outside the Cx register domain");
|
|
|
|
processor.GetCtx().SetRenderTargetMask(0x0badc0de);
|
|
Require("Pm4ContextState", "push before clear-state packet",
|
|
invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete,
|
|
"push before CLEAR_STATE failed");
|
|
processor.GetCtx().SetRenderTargetMask(0xfeedface);
|
|
std::array<uint32_t, 2> clear_state_packet{0xc0001200, 0};
|
|
Pm4Execution clear_state_execution;
|
|
Require("Pm4ContextState", "clear-state packet",
|
|
processor.Process(clear_state_execution, clear_state_packet) ==
|
|
Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderTargetMask() ==
|
|
HW::Context{}.GetRenderTargetMask(),
|
|
"CLEAR_STATE did not clear the current Cx state");
|
|
Require("Pm4ContextState", "pop after clear-state packet",
|
|
invoke(ContextStateOperation::Pop) == Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderTargetMask() == 0x0badc0de,
|
|
"CLEAR_STATE discarded the pushed Cx state");
|
|
|
|
// AGC CLEAR_STATE restores the viewport, scissor, and guard-band register defaults.
|
|
processor.GetCtx().SetScreenScissor(1, 2, 3, 4);
|
|
processor.GetCtx().SetGuardBands(2.0f, 3.0f, 4.0f, 5.0f);
|
|
Pm4Execution scissor_reset;
|
|
Require("Pm4ContextState", "scissor reset packet",
|
|
processor.Process(scissor_reset, clear_state_packet) == Pm4ProcessResult::Complete,
|
|
"CLEAR_STATE could not reset the rasterization state");
|
|
const auto &viewport = processor.GetCtx().GetScreenViewport();
|
|
const auto defaults = calc_final_scissor(viewport, {}, {20000, 20000}, 0);
|
|
Require("Pm4ContextState", "Rasterization defaults",
|
|
defaults.left == 0 && defaults.top == 0 && defaults.right == 16384 &&
|
|
defaults.bottom == 16384 && viewport.guard_band_horz_clip == 1.0f &&
|
|
viewport.guard_band_vert_clip == 1.0f &&
|
|
viewport.guard_band_horz_discard == 1.0f &&
|
|
viewport.guard_band_vert_discard == 1.0f &&
|
|
std::ranges::all_of(viewport.viewports, [](const auto &slot) {
|
|
return slot.xscale == 1.0f && slot.yscale == 1.0f && slot.zscale == 1.0f &&
|
|
slot.viewport_scissor_right == 16384 &&
|
|
slot.viewport_scissor_bottom == 16384 &&
|
|
!slot.viewport_scissor_window_offset_enable;
|
|
}),
|
|
"CLEAR_STATE did not restore scissor and guard-band defaults");
|
|
const std::array scissor_registers{
|
|
std::pair{Pm4::PA_SC_SCREEN_SCISSOR_TL, Pm4::PA_SC_SCREEN_SCISSOR_BR},
|
|
std::pair{Pm4::PA_SC_WINDOW_SCISSOR_TL, Pm4::PA_SC_WINDOW_SCISSOR_BR},
|
|
std::pair{Pm4::PA_SC_GENERIC_SCISSOR_TL, Pm4::PA_SC_GENERIC_SCISSOR_BR},
|
|
std::pair{Pm4::PA_SC_VPORT_SCISSOR_0_TL, Pm4::PA_SC_VPORT_SCISSOR_0_BR}};
|
|
for (const auto &[tl, br] : scissor_registers) {
|
|
processor.GetCtx().Reset();
|
|
g_hw_ctx_indirect_func[tl](processor, tl, 0);
|
|
g_hw_ctx_indirect_func[br](processor, br, 0);
|
|
HW::ScanModeControl scan{};
|
|
scan.vport_scissor_enable = tl == Pm4::PA_SC_VPORT_SCISSOR_0_TL;
|
|
const auto empty = calc_final_scissor(processor.GetCtx().GetScreenViewport(),
|
|
scan, {3840, 2160}, 0);
|
|
Require("Pm4ContextState", "explicit empty scissor",
|
|
empty.right == empty.left && empty.bottom == empty.top,
|
|
"an explicitly empty scissor was expanded or ignored");
|
|
}
|
|
|
|
processor.GetCtx().SetRenderTargetMask(0xabcdef01);
|
|
Require("Pm4ContextState", "push before processor reset",
|
|
invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete,
|
|
"push before reset failed");
|
|
processor.Reset();
|
|
processor.GetCtx().SetRenderTargetMask(0x76543210);
|
|
Require("Pm4ContextState", "processor reset discards push",
|
|
invoke(ContextStateOperation::Push) == Pm4ProcessResult::Complete &&
|
|
invoke(ContextStateOperation::Pop) ==
|
|
Pm4ProcessResult::Complete &&
|
|
processor.GetCtx().GetRenderTargetMask() == 0x76543210,
|
|
"processor reset retained an invalid saved Cx state");
|
|
|
|
Require("Pm4ContextState", "HLE packet size",
|
|
Gen5::AgcDcbContextStateOpGetSize(0) == 20 &&
|
|
Gen5::AgcDcbContextStateOpGetSize(1) == 108 &&
|
|
Gen5::AgcDcbContextStateOpGetSize(2) == 108 &&
|
|
Gen5::AgcDcbContextStateOpGetSize(3) == 128 &&
|
|
Gen5::AgcDcbContextStateOpGetSize(4) == 0,
|
|
"context-state HLE sizes do not match libSceAgc");
|
|
std::printf("[host] %-32s ok\n", "Pm4ContextState");
|
|
}
|
|
|
|
void CheckPm4WaitResume(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
|
|
uint32_t label = 0;
|
|
uint32_t prefix = 0;
|
|
uint32_t child_observation = UINT32_MAX;
|
|
uint32_t suffix = 0;
|
|
const auto address = [](const void *value) {
|
|
return reinterpret_cast<uint64_t>(value);
|
|
};
|
|
|
|
std::array<uint32_t, 12> child{};
|
|
child[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
child[1] = 0;
|
|
child[2] = static_cast<uint32_t>(address(&child_observation));
|
|
child[3] = static_cast<uint32_t>(address(&child_observation) >> 32u);
|
|
child[4] = 0;
|
|
child[5] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
|
child[6] = 0x10u | 3u;
|
|
child[7] = static_cast<uint32_t>(address(&label));
|
|
child[8] = static_cast<uint32_t>(address(&label) >> 32u);
|
|
child[9] = 1;
|
|
child[10] = UINT32_MAX;
|
|
|
|
std::array<uint32_t, 4> nested{};
|
|
nested[0] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
|
nested[1] = static_cast<uint32_t>(address(child.data()));
|
|
nested[2] = static_cast<uint32_t>(address(child.data()) >> 32u);
|
|
nested[3] = 0x0f200000u | static_cast<uint32_t>(child.size());
|
|
|
|
std::array<uint32_t, 14> commands{};
|
|
commands[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[1] = 0;
|
|
commands[2] = static_cast<uint32_t>(address(&prefix));
|
|
commands[3] = static_cast<uint32_t>(address(&prefix) >> 32u);
|
|
commands[4] = 11;
|
|
commands[5] = KYTY_PM4(4, Pm4::IT_INDIRECT_BUFFER, 0);
|
|
commands[6] = static_cast<uint32_t>(address(nested.data()));
|
|
commands[7] = static_cast<uint32_t>(address(nested.data()) >> 32u);
|
|
commands[8] = 0x0f200000u | static_cast<uint32_t>(nested.size());
|
|
commands[9] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[10] = 0;
|
|
commands[11] = static_cast<uint32_t>(address(&suffix));
|
|
commands[12] = static_cast<uint32_t>(address(&suffix) >> 32u);
|
|
commands[13] = 22;
|
|
|
|
Pm4Execution execution;
|
|
Require("Pm4WaitResume", "suspend",
|
|
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
|
|
prefix == 11 && child_observation == 0 && suffix == 0,
|
|
"blocked indirect wait did not preserve its command position");
|
|
|
|
label = 1;
|
|
child[4] = 1;
|
|
Require(
|
|
"Pm4WaitResume", "resume",
|
|
processor.Process(execution, commands) == Pm4ProcessResult::Complete &&
|
|
prefix == 11 && child_observation == 0 && suffix == 22,
|
|
"resumed indirect wait replayed a child or did not finish its parent");
|
|
std::printf("[host] %-32s ok\n", "Pm4WaitResume");
|
|
}
|
|
|
|
void CheckPm4RewindResume(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
|
|
uint32_t suffix = 0;
|
|
const auto address = reinterpret_cast<uint64_t>(&suffix);
|
|
std::array<uint32_t, 7> commands{};
|
|
commands[0] = KYTY_PM4(2, Pm4::IT_REWIND, 0);
|
|
commands[1] = 0;
|
|
commands[2] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[3] = 0;
|
|
commands[4] = static_cast<uint32_t>(address);
|
|
commands[5] = static_cast<uint32_t>(address >> 32u);
|
|
commands[6] = 33;
|
|
|
|
Pm4Execution execution;
|
|
Require("Pm4RewindResume", "pending",
|
|
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
|
|
suffix == 0,
|
|
"pending rewind did not preserve its command position");
|
|
Require("Pm4RewindResume", "patch valid",
|
|
Gen5::AgcRewindPatchSetRewindState(commands.data(), 1) == 0 &&
|
|
commands[1] == 0x80000000u,
|
|
"rewind state patch did not set the valid bit");
|
|
Require("Pm4RewindResume", "resume",
|
|
processor.Process(execution, commands) ==
|
|
Pm4ProcessResult::Complete &&
|
|
suffix == 33,
|
|
"valid rewind did not resume at the following packet");
|
|
std::printf("[host] %-32s ok\n", "Pm4RewindResume");
|
|
}
|
|
|
|
void CheckPm4CeCompletion(RenderContext &renderer) {
|
|
GraphicsInitJmpTables();
|
|
CommandProcessor processor(renderer, 0);
|
|
uint32_t suffix = 0;
|
|
const auto address = reinterpret_cast<uint64_t>(&suffix);
|
|
|
|
std::array<uint32_t, 9> commands{};
|
|
commands[0] = 0xc0008600u;
|
|
commands[1] = 1;
|
|
commands[2] = 0xc0008500u;
|
|
commands[3] = 0;
|
|
commands[4] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
|
|
commands[5] = 0;
|
|
commands[6] = static_cast<uint32_t>(address);
|
|
commands[7] = static_cast<uint32_t>(address >> 32u);
|
|
commands[8] = 33;
|
|
|
|
processor.ResetDeCe();
|
|
Pm4Execution execution;
|
|
Require("Pm4CeCompletion", "wait",
|
|
processor.Process(execution, commands) == Pm4ProcessResult::Blocked &&
|
|
suffix == 0,
|
|
"DE did not wait for an active CE stream");
|
|
|
|
std::array<uint32_t, 2> ce_increment{0xc0008400u, 1};
|
|
Pm4Execution ce_execution;
|
|
Require("Pm4CeCompletion", "CE increment",
|
|
processor.Process(ce_execution, ce_increment) ==
|
|
Pm4ProcessResult::Complete,
|
|
"CE counter increment did not complete");
|
|
Require(
|
|
"Pm4CeCompletion", "counter gate",
|
|
processor.Process(execution, commands) == Pm4ProcessResult::Complete &&
|
|
suffix == 33,
|
|
"DE did not resume after CE advanced or failed to increment its counter");
|
|
|
|
suffix = 0;
|
|
Pm4Execution balanced_execution;
|
|
Require("Pm4CeCompletion", "balanced wait",
|
|
processor.Process(balanced_execution, commands) ==
|
|
Pm4ProcessResult::Blocked &&
|
|
suffix == 0,
|
|
"balanced CE/DE counters did not gate the next DE packet");
|
|
processor.SetCeComplete(true);
|
|
Require("Pm4CeCompletion", "stream complete",
|
|
processor.Process(balanced_execution, commands) ==
|
|
Pm4ProcessResult::Complete &&
|
|
suffix == 33,
|
|
"DE remained blocked after the CE stream completed");
|
|
std::printf("[host] %-32s ok\n", "Pm4CeCompletion");
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace Libs::Graphics
|
|
|
|
int main(int argc, char **argv) {
|
|
using namespace Libs::Graphics;
|
|
|
|
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
|
EnsureConfigInitialized();
|
|
CheckLeastRecentlyUsedCacheOrdering();
|
|
if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckPackedTextureComponents();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--error-dialog-only") == 0) {
|
|
CheckErrorDialogLifecycle();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--fmask-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, ImageLoadFmaskUsesNativeSampleMapping());
|
|
RunCase(&vulkan, ImageLoadR32UintUsesIntegerSampledImage());
|
|
return 0;
|
|
}
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
if (argc == 2 && std::strcmp(argv[1], "--shader-fatal-only") == 0) {
|
|
CheckShaderRecompilerFatalContracts();
|
|
return 0;
|
|
}
|
|
#endif
|
|
if (argc == 2 && std::strcmp(argv[1], "--wave64-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, Wave32VccMasksPreserveOtherHalf());
|
|
RunCase(&vulkan, DsBpermuteWave64UsesIndependentHalves());
|
|
RunCase(&vulkan, Wave64CrossHalfLaneAndLds());
|
|
RunCase(&vulkan, Wave64RawMasksAndScalarBranch());
|
|
RunCase(&vulkan, Wave64PartialMultidimensionalWorkgroup());
|
|
RunCase(&vulkan, Wave64AppendConsumeHighHalf());
|
|
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
|
|
RunCase(&vulkan, VectorVop3BCarryOutWritesSgprMask());
|
|
RunCase(&vulkan, VectorVop3BCarryOutUsesEncodedSdst());
|
|
RunCase(&vulkan, DispatcherIrreducibleControlFlow());
|
|
RunCase(&vulkan, ScalarSaveExecOps());
|
|
RunCase(&vulkan, ScalarOrn2SaveexecUsesSourceOrNotExec());
|
|
RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 32));
|
|
RunCase(&vulkan, ScalarOrn2SaveexecB32(64, 64));
|
|
RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 4));
|
|
RunCase(&vulkan, ScalarSubvectorLoops(32));
|
|
RunCase(&vulkan, ScalarSubvectorLoops(64));
|
|
RunCase(&vulkan, ScalarNotB64UpdatesScc());
|
|
RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance());
|
|
RunCase(&vulkan, ScalarConditionalMoveB64());
|
|
RunCase(&vulkan, ScalarConditionalMoveB64PreservesMasks());
|
|
RunCase(&vulkan, ScalarWqmB64SelectsSccDomain());
|
|
RunCase(&vulkan, ScalarWqmB64PreservesPartialMasks());
|
|
RunCase(&vulkan, ScalarWqmB32Masks(32));
|
|
RunCase(&vulkan, ScalarWqmB32Masks(64));
|
|
RunCase(&vulkan, ScalarMaskProvenanceOverlapAndMixedBinary());
|
|
RunCase(&vulkan, ScratchIsPrivatePerInvocation());
|
|
RunCase(&vulkan, Vop1MoveRelDestination());
|
|
RunCase(&vulkan, BranchVccnzUsesWaveMask());
|
|
RunCase(&vulkan, BranchVccnzUsesCarryProducedWaveMask());
|
|
RunCase(&vulkan, ImageSampleAndGather());
|
|
RunCase(&vulkan, SharedReturnKeepsSelectedValues());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--position-w-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunGraphicsCase(&vulkan, GraphicsPositionWExport());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {
|
|
CheckClipControlDepthClipState();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--reference-clock-only") == 0) {
|
|
CheckReferenceClockScale();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--scheduler-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckSchedulerTimeline();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--host-image-allocation-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckHostImageAllocation();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--occlusion-dump-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--descriptor-heap-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckDescriptorHeapLargeSet();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--push-constant-bank-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckGraphicsPushConstantBank();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--shader-data-storage-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, BufferOffsetsUsePackedLaneAndStorageFallback());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--buffer-cmpswap-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, BufferAtomicCmpSwapExactRaw());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--ds-atomics-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
for (const auto &test : MakeCases()) {
|
|
if (std::string_view(test.name).starts_with("Ds") ||
|
|
std::string_view(test.name).starts_with("BufferAtomic")) {
|
|
RunCase(&vulkan, test);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--mapped-range-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckGpuMappedRangeLifecycle();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--stream-buffer-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckStreamBufferRing();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--gpu-tiler-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckGpuTilerCpuParity();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--gpu-command-lane-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckGpuCommandLane();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--sdwa-ashr-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, Vop2SdwaAshrrevCapturedWord0SignExtends());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--sdwa-addc-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, VectorAddcWritesPerLaneCarryOut());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, Vop3CvtPkI16I32Captured());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--med3-i16-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, Vop3Med3I16Captured());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--waitcnt-depctr-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, ScalarWaitcntDepctrCapturedVmVsrc());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--sff1-b64-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, ScalarBitfieldPack());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--context-state-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckAgcShaderFusion();
|
|
CheckPm4NativeTargetGeometryRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4PrivateAgcShaderRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4PrivateAgcUconfigRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4BlendColorRegisterRanges(vulkan.RuntimeRenderer());
|
|
CheckPm4PolygonOffsetRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4DepthControlHighBits(vulkan.RuntimeRenderer());
|
|
CheckPm4DepthRenderOverride(vulkan.RuntimeRenderer());
|
|
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--agc-wait-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckAgcWaitPackets(vulkan.RuntimeRenderer());
|
|
CheckPm4WaitResume(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--agc-draw-multi-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckAgcDrawIndirectMultiPacket(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--rewind-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckPm4RewindResume(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
|
|
CheckDepthAttachmentWrites();
|
|
CheckDynamicRenderingState();
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckRasterization(false);
|
|
vulkan.CheckRenderExecutorColorDiscovery();
|
|
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
|
vulkan.CheckRenderExecutorDccFixedClearFloat();
|
|
vulkan.CheckSampledDccClear();
|
|
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
|
|
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
|
vulkan.CheckUnifiedTextureCacheFlow();
|
|
vulkan.CheckBgra16Readback();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--polygon-mode-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckRasterization(false);
|
|
vulkan.CheckRasterization(false, true);
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedTextureCacheFlow();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--compute-meta-clear-only") == 0) {
|
|
CheckDynamicRenderingState();
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckComputeMetaClearClassification();
|
|
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
|
vulkan.CheckRenderExecutorDccFixedClearFloat();
|
|
vulkan.CheckSampledDccClear();
|
|
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--layered-image-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedImageViewCache();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--image-view-cache-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedImageViewCache();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--storage-sampled-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedImageViewCache();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--depth-readback-only") == 0) {
|
|
CheckDepthTargetFootprints();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-range-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedTextureCacheFlow();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-gc-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckBufferCacheDirtyGarbageCollection();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--sampled-depth-resource-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckSampledDepthResource();
|
|
CheckDepthTextureEncoding();
|
|
vulkan.CheckComparisonDepthTexture();
|
|
vulkan.CheckRasterization(true);
|
|
RunCase(nullptr, ImageSampleA16CompareBiasRdna2AddressOrder());
|
|
return 0;
|
|
}
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) {
|
|
RunReverseRenderTargetDeathCase();
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-only") == 0) {
|
|
CheckRenderTargetFormatContract();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--standard64-rt-only") == 0) {
|
|
CheckStandard64RenderTargetTileRoundTrip();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--standard-tile-rt-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--image-view-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckSampledColorViews();
|
|
CheckSampledVideoOutView(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--image-transition-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
CheckImageTransitionState(vulkan.RuntimeRenderer());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--storage-bgra-only") == 0) {
|
|
CheckSampledColorViews();
|
|
CheckBasicStorageTextureDescriptor();
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckUnifiedImageViewCache();
|
|
RunCase(&vulkan, ImageStoreBgraUsesInverseSwizzle());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) {
|
|
CheckImageSamplerSpecialization();
|
|
CheckIndirectImageKeySwitch();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--storage-mip-host-only") == 0) {
|
|
VulkanHarness vulkan;
|
|
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--storage-mip-only") == 0) {
|
|
RunCase(nullptr, ImageStoreMipSelectsPpsa01340Descriptor());
|
|
return 0;
|
|
}
|
|
if (argc == 2 && std::strcmp(argv[1], "--storage-yzwx-only") == 0) {
|
|
CheckSampledColorViews();
|
|
CheckBasicStorageTextureDescriptor();
|
|
CheckStorageTextureGpuOwnedRebindState();
|
|
VulkanHarness vulkan;
|
|
RunCase(&vulkan, ImageStoreYzwxUsesInverseSwizzle());
|
|
return 0;
|
|
}
|
|
if (argc == 3 && std::strcmp(argv[1], "--image-view-death") == 0) {
|
|
RunImageViewDeathCase(argv[2]);
|
|
}
|
|
if (argc == 3 &&
|
|
std::strcmp(argv[1], "--storage-texture-descriptor-death") == 0) {
|
|
RunStorageTextureDescriptorDeathCase(argv[2]);
|
|
}
|
|
if (argc != 1) {
|
|
std::fprintf(stderr, "unknown test selector: %s\n", argv[1]);
|
|
return 2;
|
|
}
|
|
VulkanHarness vulkan;
|
|
CheckRenderTargetFormatContract();
|
|
CheckSampledColorViews();
|
|
CheckSampledVideoOutView(vulkan.RuntimeRenderer());
|
|
CheckImageTransitionState(vulkan.RuntimeRenderer());
|
|
CheckSampledDepthResource();
|
|
CheckDepthTextureEncoding();
|
|
vulkan.CheckComparisonDepthTexture();
|
|
vulkan.CheckRasterization(true);
|
|
CheckBasicStorageTextureDescriptor();
|
|
CheckStorageTextureLinearUploadLayout();
|
|
CheckStorageTextureDepthTileUploadLayout();
|
|
CheckStandard64RenderTargetTileRoundTrip();
|
|
CheckStorageTextureVolumeUploadLayout();
|
|
CheckStorageTextureVolumeMipRegions();
|
|
CheckStorageTextureGpuOwnedRebindState();
|
|
CheckNativeMsaaState();
|
|
CheckPs5DepthRegisterDecoding();
|
|
CheckDepthHtileStencilCompatibility();
|
|
CheckStencilAttachmentAccess();
|
|
CheckDepthAttachmentWrites();
|
|
CheckDynamicRenderingState();
|
|
CheckDepthTargetFootprints();
|
|
CheckSlotVectorLifetime();
|
|
#else
|
|
if (argc != 1) {
|
|
std::fprintf(stderr, "unknown test selector: %s\n", argv[1]);
|
|
return 2;
|
|
}
|
|
CheckShaderRecompilerFatalContracts();
|
|
VulkanHarness vulkan;
|
|
#endif
|
|
CheckImageSamplerSpecialization();
|
|
CheckNativeImageDescriptorTypes();
|
|
CheckClipControlDepthClipState();
|
|
CheckReferenceClockScale();
|
|
CheckErrorDialogLifecycle();
|
|
CheckVulkan13FeatureRequirements();
|
|
CheckPm4AcquireMemNoOp(vulkan.RuntimeRenderer());
|
|
CheckPm4SyntheticOcclusionCounterDump(vulkan.RuntimeRenderer());
|
|
CheckPm4StencilInfoValueLane(vulkan.RuntimeRenderer());
|
|
CheckPm4NativeTargetGeometryRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4PrivateAgcShaderRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4PrivateAgcUconfigRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4DirectShaderRegisterFallback(vulkan.RuntimeRenderer());
|
|
CheckPm4GuardBandRegisterRanges(vulkan.RuntimeRenderer());
|
|
CheckPm4BlendColorRegisterRanges(vulkan.RuntimeRenderer());
|
|
CheckPm4PolygonOffsetRegisters(vulkan.RuntimeRenderer());
|
|
CheckPm4DepthControlHighBits(vulkan.RuntimeRenderer());
|
|
CheckPm4DepthRenderOverride(vulkan.RuntimeRenderer());
|
|
CheckAgcShaderFusion();
|
|
CheckAgcWaitPackets(vulkan.RuntimeRenderer());
|
|
CheckAgcDrawIndirectMultiPacket(vulkan.RuntimeRenderer());
|
|
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
|
|
CheckPm4WaitResume(vulkan.RuntimeRenderer());
|
|
CheckPm4RewindResume(vulkan.RuntimeRenderer());
|
|
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
|
|
CheckEmbeddedFetchVertexOffset();
|
|
CheckEmbeddedFetchLaneSpill();
|
|
CheckRectListShaders();
|
|
CheckIndirectImageKeySwitch();
|
|
CheckPs5GameExampleImageClearRuntimeShape();
|
|
vulkan.CheckSchedulerTimeline();
|
|
vulkan.CheckHostImageAllocation();
|
|
vulkan.CheckDescriptorHeapLargeSet();
|
|
vulkan.CheckGraphicsPushConstantBank();
|
|
vulkan.CheckGpuMappedRangeLifecycle();
|
|
vulkan.CheckStreamBufferRing();
|
|
vulkan.CheckGpuTilerCpuParity();
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
vulkan.CheckRenderExecutorColorDiscovery();
|
|
vulkan.CheckRenderExecutorColorVolumeDiscovery();
|
|
vulkan.CheckRenderExecutorDccFixedClearFloat();
|
|
vulkan.CheckSampledDccClear();
|
|
vulkan.CheckRenderExecutorColorStandardTileDiscovery();
|
|
vulkan.CheckRenderExecutorColorDepthTileDiscovery();
|
|
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
|
vulkan.CheckUnifiedTextureCacheFlow();
|
|
vulkan.CheckBgra16Readback();
|
|
vulkan.CheckRasterization(false);
|
|
vulkan.CheckRasterization(false, true);
|
|
vulkan.CheckBufferCacheDirtyGarbageCollection();
|
|
#endif
|
|
vulkan.CheckUnifiedImageViewCache();
|
|
vulkan.CheckPackedTextureComponents();
|
|
const auto tests = MakeCases();
|
|
const auto graphics_tests = MakeGraphicsCases();
|
|
CheckOpcodeCoverage(tests, graphics_tests);
|
|
for (const auto &test : tests) {
|
|
RunCase(&vulkan, test);
|
|
}
|
|
for (const auto &test : graphics_tests) {
|
|
RunGraphicsCase(&vulkan, test);
|
|
}
|
|
vulkan.CheckGpuCommandLane();
|
|
std::printf("ShaderRecompilerComputeTests: all cases passed\n");
|
|
return 0;
|
|
}
|