mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-24 12:56:53 +02:00
graphics: add RDNA2 FLAT BDA buffer cache and faults
This commit is contained in:
@@ -263,6 +263,22 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
)
|
||||
list(APPEND gpu_tiler_shader_headers "${shader_header}")
|
||||
endforeach()
|
||||
|
||||
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
|
||||
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
|
||||
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${fault_buffer_shader_header}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
|
||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
||||
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
|
||||
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
|
||||
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${fault_buffer_shader_source}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||
|
||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
|
||||
@@ -53,6 +53,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() { m_ranges.clear(); }
|
||||
|
||||
template <typename Func>
|
||||
void ForEach(Func&& func) const {
|
||||
for (const auto& [begin, end]: m_ranges) {
|
||||
func(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
|
||||
std::vector<Range> result;
|
||||
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
|
||||
@@ -98,7 +107,7 @@ public:
|
||||
|
||||
private:
|
||||
static uint64_t End(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0 || size > UINT64_MAX - address) {
|
||||
if (size == 0 || size > UINT64_MAX - address) {
|
||||
EXIT("invalid range-set address or size\n");
|
||||
}
|
||||
return address + size;
|
||||
|
||||
+73
-38
@@ -51,41 +51,50 @@ struct BufferCache::DownloadCopy {
|
||||
};
|
||||
|
||||
void BufferCache::Register(BufferId id) {
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
PageTable::PageRange pages {};
|
||||
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
m_page_table[page] = id;
|
||||
}
|
||||
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
|
||||
(void)it;
|
||||
EXIT_IF(!inserted);
|
||||
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
|
||||
m_total_used_memory += buffer.Size();
|
||||
ChangeRegister<true>(id);
|
||||
}
|
||||
|
||||
void BufferCache::Unregister(BufferId id) {
|
||||
ChangeRegister<false>(id);
|
||||
}
|
||||
|
||||
template <bool insert>
|
||||
void BufferCache::ChangeRegister(BufferId id) {
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
if (buffer.is_deleted) {
|
||||
return;
|
||||
}
|
||||
PageTable::PageRange pages {};
|
||||
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
auto* owner = m_page_table.Find(page);
|
||||
if (owner != nullptr && *owner == id) {
|
||||
*owner = {};
|
||||
if constexpr (insert) {
|
||||
m_page_table[page] = id;
|
||||
} else {
|
||||
m_page_table[page] = {};
|
||||
}
|
||||
}
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
m_buffers.erase(found);
|
||||
if (buffer.Size() > m_total_used_memory) {
|
||||
EXIT("BufferCache: allocation accounting underflow\n");
|
||||
const auto size_pages = pages.last_exclusive - pages.first;
|
||||
if constexpr (insert) {
|
||||
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
|
||||
(void)it;
|
||||
EXIT_IF(!inserted);
|
||||
m_total_used_memory += buffer.Size();
|
||||
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
|
||||
std::vector<vk::DeviceAddress> addresses;
|
||||
addresses.reserve(size_pages);
|
||||
for (uint64_t i = 0; i < size_pages; ++i) {
|
||||
addresses.push_back(buffer.BufferDeviceAddress() + (i << CACHING_PAGEBITS));
|
||||
}
|
||||
WriteDataBuffer(m_bda_pagetable_buffer, pages.first * sizeof(vk::DeviceAddress),
|
||||
addresses.data(), addresses.size() * sizeof(vk::DeviceAddress));
|
||||
} else {
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
m_buffers.erase(found);
|
||||
EXIT_IF(buffer.Size() > m_total_used_memory);
|
||||
m_total_used_memory -= buffer.Size();
|
||||
m_lru_cache.Free(buffer.lru_id);
|
||||
m_bda_pagetable_buffer.Fill(pages.first * sizeof(vk::DeviceAddress),
|
||||
size_pages * sizeof(vk::DeviceAddress), 0);
|
||||
buffer.is_deleted = true;
|
||||
}
|
||||
m_total_used_memory -= buffer.Size();
|
||||
m_lru_cache.Free(buffer.lru_id);
|
||||
buffer.is_deleted = true;
|
||||
}
|
||||
|
||||
void BufferCache::TouchBuffer(const Buffer& buffer) {
|
||||
@@ -184,16 +193,21 @@ void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
|
||||
|
||||
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
PageManager& page_manager, TextureCache& texture_cache)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
BDA_PAGETABLE_SIZE),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
m_gds_buffer.Flush(0, m_gds_buffer.Size());
|
||||
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
|
||||
"BDA Page Table Buffer");
|
||||
const auto null_id =
|
||||
m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16);
|
||||
EXIT_IF(null_id != NULL_BUFFER_ID);
|
||||
@@ -316,8 +330,8 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
|
||||
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
auto& command = m_scheduler.Current();
|
||||
EXIT_IF(command.IsInvalid());
|
||||
auto begin = vaddr & ~(CACHING_PAGE_SIZE - 1);
|
||||
auto end = (vaddr + size + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
|
||||
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
|
||||
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
|
||||
auto first = m_buffers.lower_bound(begin);
|
||||
if (first != m_buffers.begin()) {
|
||||
const auto previous = std::prev(first);
|
||||
@@ -333,8 +347,9 @@ BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
end = std::max(end, buffer.CpuAddress() + buffer.Size());
|
||||
}
|
||||
|
||||
const auto id = m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
|
||||
AllFlags, end - begin);
|
||||
const auto id = m_slot_buffers.insert(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
|
||||
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
|
||||
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
|
||||
@@ -433,7 +448,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
EXIT("BufferCache: buffer request requires a recording command buffer\n");
|
||||
}
|
||||
|
||||
if (!is_written && size <= CACHING_PAGE_SIZE &&
|
||||
if (!is_written && size <= CACHING_PAGESIZE &&
|
||||
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
|
||||
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
|
||||
const auto alignment = std::max<uint64_t>(
|
||||
@@ -737,4 +752,24 @@ void BufferCache::RunGarbageCollector() {
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCache::ProcessFaultBuffer() {
|
||||
m_fault_manager.ProcessFaultBuffer();
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size) {
|
||||
const auto end = vaddr + size;
|
||||
auto it = m_buffers.upper_bound(vaddr);
|
||||
if (it != m_buffers.begin()) {
|
||||
--it;
|
||||
}
|
||||
for (; it != m_buffers.end() && it->first < end; ++it) {
|
||||
auto& buffer = m_slot_buffers[it->second];
|
||||
const auto start = std::max(buffer.CpuAddress(), vaddr);
|
||||
const auto finish = std::min(buffer.CpuAddress() + buffer.Size(), end);
|
||||
if (start < finish) {
|
||||
(void)SynchronizeBuffer(buffer, start, finish - start, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
+16
-3
@@ -7,6 +7,7 @@
|
||||
#include "common/slotVector.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/cache/faultManager.h"
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
@@ -26,7 +27,11 @@ inline constexpr BufferId NULL_BUFFER_ID {0};
|
||||
|
||||
class BufferCache {
|
||||
public:
|
||||
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
|
||||
static constexpr uint32_t CACHING_PAGEBITS = 14;
|
||||
static constexpr uint64_t CACHING_PAGESIZE = uint64_t {1} << CACHING_PAGEBITS;
|
||||
static constexpr uint64_t CACHING_NUMPAGES = uint64_t {1} << (40 - CACHING_PAGEBITS);
|
||||
static constexpr uint64_t BDA_PAGETABLE_SIZE =
|
||||
CACHING_NUMPAGES * sizeof(vk::DeviceAddress);
|
||||
|
||||
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
TextureCache& texture_cache);
|
||||
@@ -51,6 +56,8 @@ public:
|
||||
EXIT("BufferCache: invalid utility-buffer usage\n");
|
||||
}
|
||||
[[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { return &m_gds_buffer; }
|
||||
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { return &m_bda_pagetable_buffer; }
|
||||
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return m_fault_manager.GetFaultBuffer(); }
|
||||
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds);
|
||||
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
|
||||
@@ -60,14 +67,16 @@ public:
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void ProcessFaultBuffer();
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
struct DownloadCopy;
|
||||
using PageTable = MultiLevelPageTable<BufferId, 14, 40, 16>;
|
||||
static_assert(CACHING_PAGE_SIZE == (uint64_t {1} << PageTable::kPageBits));
|
||||
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
|
||||
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
|
||||
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
|
||||
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
|
||||
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
|
||||
@@ -78,6 +87,8 @@ private:
|
||||
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
|
||||
void Register(BufferId id);
|
||||
void Unregister(BufferId id);
|
||||
template <bool insert>
|
||||
void ChangeRegister(BufferId id);
|
||||
void DeleteBuffer(BufferId id);
|
||||
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
|
||||
bool is_written, bool is_texel_buffer);
|
||||
@@ -90,7 +101,9 @@ private:
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
FaultManager m_fault_manager;
|
||||
Buffer m_gds_buffer;
|
||||
Buffer m_bda_pagetable_buffer;
|
||||
Common::SlotVector<Buffer> m_slot_buffers;
|
||||
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
|
||||
std::map<uint64_t, BufferId> m_buffers;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "graphics/host_gpu/renderer/cache/faultManager.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "gpu_tiler_shaders/fault_buffer_process_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t MaxPageFaults = 1024;
|
||||
constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
|
||||
|
||||
} // namespace
|
||||
|
||||
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
BufferCache& buffer_cache, uint32_t caching_pagebits,
|
||||
uint64_t caching_num_pages)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
|
||||
m_caching_pagesize(uint64_t {1} << caching_pagebits),
|
||||
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
|
||||
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
m_fault_buffer_size),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
|
||||
MaxPendingFaults * PageFaultAreaSize) {
|
||||
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
|
||||
|
||||
const vk::DescriptorSetLayoutBinding bindings[] {
|
||||
{0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
|
||||
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
|
||||
};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
layout_info.bindingCount = std::size(bindings);
|
||||
layout_info.pBindings = bindings;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createDescriptorSetLayout(&layout_info, nullptr,
|
||||
&m_fault_process_desc_layout),
|
||||
"create fault-buffer descriptor layout");
|
||||
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create fault-buffer shader module");
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
|
||||
RequireVulkanSuccess(
|
||||
m_graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&m_fault_process_pipeline_layout),
|
||||
"create fault-buffer pipeline layout");
|
||||
|
||||
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 = m_fault_process_pipeline_layout;
|
||||
const auto result = m_graphics.device.createComputePipelines(
|
||||
nullptr, 1, &pipeline_info, nullptr, &m_fault_process_pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create fault-buffer pipeline");
|
||||
SetVulkanObjectNameF(m_graphics.device, m_fault_process_pipeline, "Fault Buffer Parser");
|
||||
}
|
||||
|
||||
FaultManager::~FaultManager() {
|
||||
m_graphics.device.destroyPipeline(m_fault_process_pipeline, nullptr);
|
||||
m_graphics.device.destroyPipelineLayout(m_fault_process_pipeline_layout, nullptr);
|
||||
m_graphics.device.destroyDescriptorSetLayout(m_fault_process_desc_layout, nullptr);
|
||||
}
|
||||
|
||||
void FaultManager::ProcessFaultBuffer() {
|
||||
if (const auto wait_tick = m_fault_areas[m_current_area]; wait_tick != 0) {
|
||||
m_scheduler.Wait(wait_tick);
|
||||
m_scheduler.PopPendingOperations();
|
||||
}
|
||||
|
||||
const auto offset = m_current_area * PageFaultAreaSize;
|
||||
auto* mapped = m_download_buffer.Mapped().data() + offset;
|
||||
std::memset(mapped, 0, PageFaultAreaSize);
|
||||
m_download_buffer.Flush(offset, PageFaultAreaSize);
|
||||
|
||||
vk::BufferMemoryBarrier2 pre_barrier {};
|
||||
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
|
||||
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
|
||||
pre_barrier.buffer = m_fault_buffer.Handle();
|
||||
pre_barrier.offset = 0;
|
||||
pre_barrier.size = m_fault_buffer_size;
|
||||
auto post_barrier = pre_barrier;
|
||||
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
post_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
|
||||
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); ++index) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.sType = vk::StructureType::eDependencyInfo;
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &pre_barrier;
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
|
||||
m_fault_process_pipeline_layout, 0, writes);
|
||||
const auto num_threads = m_caching_num_pages / 32;
|
||||
const auto num_workgroups = (num_threads + 63) / 64;
|
||||
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
|
||||
dependency.pBufferMemoryBarriers = &post_barrier;
|
||||
command.pipelineBarrier2(dependency);
|
||||
|
||||
const auto area = m_current_area;
|
||||
m_scheduler.DeferOperation([this, mapped, offset, area] {
|
||||
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
|
||||
m_fault_ranges.Clear();
|
||||
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
|
||||
const auto count = static_cast<uint32_t>(faults[0]);
|
||||
for (uint32_t index = 1; index <= count; ++index) {
|
||||
m_fault_ranges.Add(faults[index], m_caching_pagesize);
|
||||
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
|
||||
}
|
||||
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
|
||||
(void)m_buffer_cache.FindBuffer(start, end - start);
|
||||
});
|
||||
m_fault_areas[area] = 0;
|
||||
});
|
||||
|
||||
m_fault_areas[m_current_area++] = m_scheduler.CurrentTick();
|
||||
m_current_area %= MaxPendingFaults;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class BufferCache;
|
||||
|
||||
class FaultManager {
|
||||
static constexpr size_t MaxPendingFaults = 8;
|
||||
|
||||
public:
|
||||
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
|
||||
uint32_t caching_pagebits, uint64_t caching_num_pages);
|
||||
~FaultManager();
|
||||
KYTY_CLASS_NO_COPY(FaultManager);
|
||||
|
||||
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return &m_fault_buffer; }
|
||||
void ProcessFaultBuffer();
|
||||
|
||||
private:
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
BufferCache& m_buffer_cache;
|
||||
RangeSet m_fault_ranges;
|
||||
uint64_t m_caching_pagesize;
|
||||
uint64_t m_caching_num_pages;
|
||||
uint64_t m_fault_buffer_size;
|
||||
Buffer m_fault_buffer;
|
||||
Buffer m_download_buffer;
|
||||
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
|
||||
uint32_t m_current_area = 0;
|
||||
vk::DescriptorSetLayout m_fault_process_desc_layout = nullptr;
|
||||
vk::Pipeline m_fault_process_pipeline = nullptr;
|
||||
vk::PipelineLayout m_fault_process_pipeline_layout = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
@@ -76,7 +76,19 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
m_gpu->SendCommandSync(unmap);
|
||||
}
|
||||
|
||||
void GpuResourceManager::PrepareBda() {
|
||||
std::shared_lock lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
|
||||
});
|
||||
m_fault_process_pending = true;
|
||||
}
|
||||
|
||||
void GpuResourceManager::RunGarbageCollector() {
|
||||
if (m_fault_process_pending) {
|
||||
m_fault_process_pending = false;
|
||||
m_buffer_cache.ProcessFaultBuffer();
|
||||
}
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
void MapMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
void PrepareBda();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
@@ -40,6 +41,7 @@ private:
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
RangeSet m_mapped_ranges;
|
||||
GuestGpu* m_gpu = nullptr;
|
||||
bool m_fault_process_pending = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
+26
-11
@@ -73,8 +73,12 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
|
||||
buffer_info.usage = flags;
|
||||
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
||||
const VmaAllocationCreateFlags bda_flag =
|
||||
with_bda ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0;
|
||||
VmaAllocationCreateInfo allocation_info {};
|
||||
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
|
||||
allocation_info.flags =
|
||||
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | bda_flag | AllocationFlags(usage);
|
||||
allocation_info.usage = AllocationUsage(usage);
|
||||
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
|
||||
? VkMemoryPropertyFlags {}
|
||||
@@ -99,6 +103,13 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
|
||||
m_buffer->memory.type = allocation_result.memoryType;
|
||||
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
|
||||
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
|
||||
vk::BufferDeviceAddressInfo address_info {};
|
||||
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
|
||||
address_info.buffer = m_buffer->buffer;
|
||||
m_device_address = graphics.device.getBufferAddress(address_info);
|
||||
EXIT_IF(m_device_address == 0);
|
||||
}
|
||||
|
||||
VkMemoryPropertyFlags properties = 0;
|
||||
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
|
||||
@@ -122,6 +133,11 @@ vk::Buffer Buffer::Handle() const noexcept {
|
||||
return m_buffer->buffer;
|
||||
}
|
||||
|
||||
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
|
||||
EXIT_IF(m_device_address == 0);
|
||||
return m_device_address;
|
||||
}
|
||||
|
||||
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
|
||||
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
|
||||
}
|
||||
@@ -141,6 +157,15 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
const auto result =
|
||||
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const {
|
||||
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
||||
@@ -310,16 +335,6 @@ void StreamBuffer::Commit() {
|
||||
watch.tick = tick;
|
||||
}
|
||||
|
||||
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
|
||||
if (IsCoherent() || size == 0) {
|
||||
return;
|
||||
}
|
||||
const auto result = vmaInvalidateAllocation(Graphics().allocator,
|
||||
NativeBuffer().memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
|
||||
EXIT_IF(source == nullptr);
|
||||
const auto [data, offset] = Map(size, alignment);
|
||||
|
||||
+3
-3
@@ -48,12 +48,14 @@ public:
|
||||
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
|
||||
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
|
||||
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
|
||||
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
|
||||
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
|
||||
return address - m_cpu_address;
|
||||
}
|
||||
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
|
||||
void Write(uint64_t offset, const void* source, uint64_t size);
|
||||
void Flush(uint64_t offset, uint64_t size);
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size,
|
||||
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
|
||||
@@ -84,6 +86,7 @@ private:
|
||||
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
|
||||
uint64_t m_cpu_address = 0;
|
||||
uint64_t m_size = 0;
|
||||
vk::DeviceAddress m_device_address = 0;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
std::span<uint8_t> m_mapped;
|
||||
bool m_is_coherent = false;
|
||||
@@ -97,9 +100,6 @@ public:
|
||||
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
|
||||
bool allow_wait = true);
|
||||
void Commit();
|
||||
// Download mappings become visible to the CPU only after their GPU completion tick is free.
|
||||
// Call this from the scheduler's deferred completion operation before reading Mapped().
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
|
||||
|
||||
private:
|
||||
|
||||
@@ -196,8 +196,8 @@ void CommandScheduler::Finish() {
|
||||
Submit();
|
||||
}
|
||||
m_master.Wait(CurrentTick() - 1);
|
||||
PopPendingOperations();
|
||||
BeginNext();
|
||||
PopPendingOperations();
|
||||
}
|
||||
|
||||
void CommandScheduler::Wait(uint64_t tick) {
|
||||
|
||||
@@ -216,48 +216,6 @@ static BufferView NativeStorageBuffer(RenderContext&
|
||||
return result;
|
||||
}
|
||||
|
||||
static BufferView
|
||||
NativeAddressBuffer(RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
|
||||
ShaderType stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
|
||||
BufferView result;
|
||||
address_offset = 0;
|
||||
if (address.binding_base == 0) {
|
||||
BindNullStorageBuffer(context, result);
|
||||
return result;
|
||||
}
|
||||
if (resource.written) {
|
||||
EXIT("writable address resources are unsupported\n");
|
||||
}
|
||||
const auto limit =
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
|
||||
? ShaderRecompiler::IR::FlatAddressWindowSize
|
||||
: static_cast<uint64_t>(
|
||||
context.GetGraphics().GetPhysicalDeviceProperties().limits.maxStorageBufferRange);
|
||||
uint64_t size = 0;
|
||||
const auto access = HostMemoryAccess::Mapped;
|
||||
if (!HostMemoryQueryRange(address.binding_base, limit, access, size)) {
|
||||
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
|
||||
address.binding_base);
|
||||
}
|
||||
const auto& graphics = context.GetGraphics();
|
||||
const auto alignment = graphics.StorageMinAlignment();
|
||||
auto [buffer, offset] =
|
||||
context.GetBufferCache().ObtainBuffer(address.binding_base, size, false, false, id);
|
||||
const auto aligned_offset = offset - offset % alignment;
|
||||
const auto adjustment = offset - aligned_offset;
|
||||
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
|
||||
const auto visible_size = std::min(size, static_cast<uint64_t>(max_range - adjustment));
|
||||
address_offset = static_cast<uint32_t>(adjustment);
|
||||
result.buffer = buffer->Handle();
|
||||
result.offset = aligned_offset;
|
||||
result.range = static_cast<vk::DeviceSize>(visible_size + adjustment);
|
||||
SetVulkanObjectNameF(graphics.device, result.buffer,
|
||||
"Kyty.{}.AddressBuffer[slot={} guest=0x{:016x} size=0x{:x}]",
|
||||
ShaderStageResourceName(stage), slot, address.binding_base, visible_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
|
||||
bool supported_dimension = false;
|
||||
switch (resource.dimension) {
|
||||
@@ -1000,25 +958,6 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
|
||||
}
|
||||
|
||||
prepared.address_ids.clear();
|
||||
prepared.address_ids.reserve(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
const auto& resource = program.info.addresses[i];
|
||||
const auto& address = snapshot.addresses[i];
|
||||
if (address.binding_base == 0) {
|
||||
prepared.address_ids.emplace_back();
|
||||
continue;
|
||||
}
|
||||
EXIT_IF(resource.written);
|
||||
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
|
||||
? ShaderRecompiler::IR::FlatAddressWindowSize
|
||||
: static_cast<uint64_t>(m_context.GetGraphics()
|
||||
.GetPhysicalDeviceProperties()
|
||||
.limits.maxStorageBufferRange);
|
||||
uint64_t size = 0;
|
||||
EXIT_IF(!HostMemoryQueryRange(address.binding_base, limit, HostMemoryAccess::Mapped, size));
|
||||
prepared.address_ids.push_back(cache.FindBuffer(address.binding_base, size));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
@@ -1028,8 +967,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& resources = prepared.resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size() ||
|
||||
prepared.address_ids.size() != program.info.addresses.size());
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
|
||||
|
||||
resources.buffers.clear();
|
||||
resources.buffers.reserve(program.info.buffers.size());
|
||||
@@ -1049,15 +987,6 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
buffer_offset, prepared.buffer_ids[i]));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
}
|
||||
resources.addresses.clear();
|
||||
resources.addresses.reserve(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
uint32_t address_offset = 0;
|
||||
resources.addresses.push_back(NativeAddressBuffer(m_context, program.info.addresses[i],
|
||||
snapshot.addresses[i], program.stage, i,
|
||||
address_offset, prepared.address_ids[i]));
|
||||
pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i, address_offset);
|
||||
}
|
||||
if (!prepared.flattened_srt.empty()) {
|
||||
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
|
||||
}
|
||||
@@ -1129,6 +1058,10 @@ RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
|
||||
if (bindings.pixel) {
|
||||
FindBuffers(*bindings.pixel);
|
||||
}
|
||||
if (bindings.vertex.program->info.uses_dma ||
|
||||
(bindings.pixel && bindings.pixel->program->info.uses_dma)) {
|
||||
m_context.GetGpuResources().PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings.vertex);
|
||||
if (bindings.pixel) {
|
||||
RebindBuffers(*bindings.pixel);
|
||||
@@ -1241,13 +1174,15 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
}
|
||||
break;
|
||||
case BindingKind::AddressMemory:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& view = descriptors.addresses.at(resource);
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
|
||||
}
|
||||
case BindingKind::BdaPagetable:
|
||||
case BindingKind::FaultBuffer: {
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable
|
||||
? cache.GetBdaPageTableBuffer()
|
||||
: cache.GetFaultBuffer();
|
||||
m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0, bda_buffer->Size());
|
||||
break;
|
||||
}
|
||||
case BindingKind::FlattenedSrt:
|
||||
case BindingKind::UserData:
|
||||
case BindingKind::Gds: {
|
||||
|
||||
@@ -38,7 +38,6 @@ struct NativeDescriptors {
|
||||
std::vector<BufferView> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<vk::Sampler> samplers;
|
||||
std::vector<BufferView> addresses;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
@@ -49,7 +48,6 @@ struct PreparedBindings {
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
|
||||
NativeDescriptors resources;
|
||||
std::vector<BufferId> buffer_ids;
|
||||
std::vector<BufferId> address_ids;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
bool committed = false;
|
||||
|
||||
@@ -82,8 +82,8 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
|
||||
!program.info.images.empty() || !program.info.samplers.empty() ||
|
||||
!program.info.addresses.empty() || !resources.images.empty() ||
|
||||
!resources.samplers.empty() || !resources.addresses.empty()) {
|
||||
program.info.uses_dma || !resources.images.empty() ||
|
||||
!resources.samplers.empty()) {
|
||||
return false;
|
||||
}
|
||||
const auto& resource = program.info.buffers.front();
|
||||
@@ -337,6 +337,9 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
|
||||
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
|
||||
auto bindings = PrepareBindings(input_info.stage);
|
||||
FindBuffers(bindings);
|
||||
if (program.info.uses_dma) {
|
||||
m_context.GetGpuResources().PrepareBda();
|
||||
}
|
||||
RebindBuffers(bindings);
|
||||
RebindImages(bindings);
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_gpu_shader_int64 : enable
|
||||
|
||||
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout(std430, binding = 0) buffer input_buf {
|
||||
uint fault_buffer[];
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) buffer output_buf {
|
||||
uint64_t download_buffer[];
|
||||
};
|
||||
layout(std430, binding = 1) buffer output_buf32 {
|
||||
uint download_buffer32[];
|
||||
};
|
||||
|
||||
void main() {
|
||||
const uint id = gl_GlobalInvocationID.x;
|
||||
uint word = fault_buffer[id];
|
||||
fault_buffer[id] = 0u;
|
||||
const uint base_bit = id * 32u;
|
||||
while (word != 0u) {
|
||||
const uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
|
||||
if (store_index >= MAX_PAGE_FAULTS) {
|
||||
return;
|
||||
}
|
||||
const uint bit = findLSB(word);
|
||||
word &= word - 1;
|
||||
const uint page = base_bit + bit;
|
||||
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,10 @@ bool GraphicContext::CreateAllocator() {
|
||||
info.device = device;
|
||||
info.pVulkanFunctions = &functions;
|
||||
info.vulkanApiVersion = VULKAN_TARGET_API_VERSION;
|
||||
info.flags = memory_budget_ext_enabled ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0;
|
||||
info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
|
||||
if (memory_budget_ext_enabled) {
|
||||
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
|
||||
}
|
||||
|
||||
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
|
||||
if (result != vk::Result::eSuccess) {
|
||||
|
||||
@@ -594,20 +594,16 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
#if !defined(__APPLE__)
|
||||
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
|
||||
#endif
|
||||
features12.timelineSemaphore = VK_TRUE;
|
||||
features12.shaderOutputLayer = VK_TRUE;
|
||||
|
||||
if (Config::GpuAssistedValidationEnabled()) {
|
||||
if (supported_features12.bufferDeviceAddress != VK_TRUE) {
|
||||
EXIT("--gpu-assisted-validation requires bufferDeviceAddress, unsupported by this "
|
||||
"device\n");
|
||||
}
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
}
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
@@ -625,12 +621,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
device_features.shaderCullDistance = VK_TRUE;
|
||||
device_features.largePoints = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.vertexPipelineStoresAndAtomics =
|
||||
supported_features2.features.vertexPipelineStoresAndAtomics;
|
||||
if (Config::GpuAssistedValidationEnabled()) {
|
||||
device_features.shaderInt64 = supported_features2.features.shaderInt64;
|
||||
}
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
|
||||
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
|
||||
|
||||
@@ -665,7 +665,6 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
runtime.read_memory = ReadZeroMemory;
|
||||
}
|
||||
runtime.userdata = options.read_memory_data;
|
||||
runtime.flat_memory_base = options.flat_memory_base;
|
||||
if (!IR::MaterializeResources(ir, runtime, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -20,7 +19,6 @@ struct CompileOptions {
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
uint64_t shader_base = 0;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
|
||||
@@ -70,8 +70,9 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
if (uses_gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
|
||||
if (program.info.uses_dma) {
|
||||
Expect(Kind::BdaPagetable);
|
||||
Expect(Kind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
@@ -104,8 +105,7 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
|
||||
IR::NativePushConstantSize ||
|
||||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count !=
|
||||
program.info.buffers.size() + program.info.addresses.size() ||
|
||||
program.bindings.memory_offset_count != program.info.buffers.size() ||
|
||||
(!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
@@ -146,8 +146,8 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
if (planning_only_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.addresses.size()) {
|
||||
return Fail(error, "typed address handle has an invalid dense resource");
|
||||
if (inst.NumArgs() != 2 || !program.info.uses_dma) {
|
||||
return Fail(error, "typed address handle has invalid DMA metadata");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetScratchResource:
|
||||
@@ -188,7 +188,8 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
const auto MarkBallot = [&] { requirements.subgroup_ballot = true; };
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::AddressOpcodeInfoOf(inst.GetOpcode()).access != IR::AddressAccess::None) {
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (memory_index >= program.memory_info.size()) {
|
||||
return Fail(error, "address operation has invalid memory metadata");
|
||||
@@ -198,6 +199,8 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
|
||||
return Fail(error, "scratch operation has no per-thread storage");
|
||||
}
|
||||
requirements.function_scratch = true;
|
||||
} else if (address_access == IR::AddressAccess::Write) {
|
||||
return Fail(error, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
|
||||
}
|
||||
}
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) {
|
||||
|
||||
@@ -36,8 +36,10 @@ enum : uint32_t {
|
||||
ExecutionModeSignedZeroInfNanPreserve = 4461,
|
||||
ExecutionModeDerivativeGroupQuadsKHR = 5289,
|
||||
AddressingModelLogical = 0,
|
||||
AddressingModelPhysicalStorageBuffer64 = 5348,
|
||||
MemoryModelGLSL450 = 1,
|
||||
CapabilityShader = 1,
|
||||
CapabilityInt64 = 11,
|
||||
CapabilityImageGatherExtended = 25,
|
||||
CapabilityClipDistance = 32,
|
||||
CapabilityCullDistance = 33,
|
||||
@@ -52,6 +54,7 @@ enum : uint32_t {
|
||||
CapabilityShaderViewportIndexLayerEXT = 5254,
|
||||
CapabilityFragmentBarycentricKHR = 5284,
|
||||
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
|
||||
CapabilityPhysicalStorageBufferAddresses = 5347,
|
||||
StorageClassUniformConstant = 0,
|
||||
StorageClassInput = 1,
|
||||
StorageClassOutput = 3,
|
||||
@@ -60,6 +63,7 @@ enum : uint32_t {
|
||||
StorageClassPushConstant = 9,
|
||||
StorageClassImage = 11,
|
||||
StorageClassStorageBuffer = 12,
|
||||
StorageClassPhysicalStorageBuffer = 5349,
|
||||
FunctionControlNone = 0,
|
||||
SelectionControlNone = 0,
|
||||
LoopControlNone = 0,
|
||||
@@ -148,7 +152,9 @@ enum : uint32_t {
|
||||
OpConstantComposite = 44,
|
||||
OpUndef = 1,
|
||||
OpFunction = 54,
|
||||
OpFunctionParameter = 55,
|
||||
OpFunctionEnd = 56,
|
||||
OpFunctionCall = 57,
|
||||
OpVariable = 59,
|
||||
OpImageTexelPointer = 60,
|
||||
OpLoad = 61,
|
||||
@@ -178,6 +184,8 @@ enum : uint32_t {
|
||||
OpConvertFToS = 110,
|
||||
OpConvertSToF = 111,
|
||||
OpConvertUToF = 112,
|
||||
OpUConvert = 113,
|
||||
OpConvertUToPtr = 120,
|
||||
OpBitcast = 124,
|
||||
OpSNegate = 126,
|
||||
OpFNegate = 127,
|
||||
@@ -256,11 +264,16 @@ enum : uint32_t {
|
||||
OpSwitch = 251,
|
||||
OpKill = 252,
|
||||
OpReturn = 253,
|
||||
OpReturnValue = 254,
|
||||
OpGroupNonUniformBallot = 339,
|
||||
OpGroupNonUniformBallotFindLSB = 343,
|
||||
OpGroupNonUniformShuffle = 345,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
MemoryAccessAlignedMask = 0x2,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
GlslRoundEven = 2,
|
||||
GlslTrunc = 3,
|
||||
@@ -345,9 +358,10 @@ struct EmitterState {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers + IR::ShaderInfo::MaxAddresses>
|
||||
memory_byte_offsets {};
|
||||
uint32_t address_memory_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
@@ -384,6 +398,7 @@ uint32_t TypeBool(EmitterState& state);
|
||||
uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeDeviceAddress(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
@@ -397,6 +412,8 @@ uint32_t TypePointer(EmitterState& state, uint32_t storage_class, uint32_t point
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state);
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
uint32_t TypeU32ArrayPointer(EmitterState& state, uint32_t storage_class, uint32_t dwords);
|
||||
uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class);
|
||||
@@ -849,6 +866,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* error);
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
|
||||
// These templates accept local lambdas from several emitter translation units.
|
||||
template <typename Fn>
|
||||
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
@@ -95,60 +97,146 @@ uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t ad
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t low, uint32_t high) {
|
||||
uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t low,
|
||||
uint32_t high) {
|
||||
auto& state = ctx.state;
|
||||
auto immediate = static_cast<int32_t>(mem.offset);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
|
||||
}
|
||||
const auto immediate_low = ConstantU32(state, static_cast<uint32_t>(immediate));
|
||||
const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u);
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto valid = Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
const auto base = state.program.info.addresses[mem.resource].specialized_base;
|
||||
if (mem.kind == IR::ResourceKind::Flat ||
|
||||
(mem.kind == IR::ResourceKind::Global &&
|
||||
state.program.info.addresses[mem.resource].unbased)) {
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto base_low = ConstantU32(state, static_cast<uint32_t>(base));
|
||||
const auto base_high = ConstantU32(state, static_cast<uint32_t>(base >> 32u));
|
||||
const auto relative = Binary(state, OpISub, TypeU32(state), low, base_low);
|
||||
const auto borrow = Binary(state, OpULessThan, TypeBool(state), low, base_low);
|
||||
const auto relative_high = Binary(
|
||||
state, OpISub, TypeU32(state), Binary(state, OpISub, TypeU32(state), high, base_high),
|
||||
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0)));
|
||||
const auto valid =
|
||||
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
const auto initial = base + static_cast<uint64_t>(static_cast<int64_t>(immediate));
|
||||
auto relative_high = ConstantU32(state, static_cast<uint32_t>(initial >> 32u));
|
||||
const auto relative = AddU64Low(state, ConstantU32(state, static_cast<uint32_t>(initial)),
|
||||
relative_high, low, ConstantU32(state, 0), relative_high);
|
||||
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
|
||||
const auto valid =
|
||||
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
|
||||
Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
|
||||
}
|
||||
|
||||
uint32_t AddAddressMemoryOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto binding =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
|
||||
const auto offset =
|
||||
state.memory_byte_offsets[state.program.info.buffers.size() + binding.array_index];
|
||||
const auto corrected = Binary(state, OpIAdd, TypeU32(state), address, offset);
|
||||
const auto not_invalid =
|
||||
Binary(state, OpINotEqual, TypeBool(state), address, ConstantU32(state, UINT32_MAX));
|
||||
const auto no_overflow =
|
||||
Binary(state, OpUGreaterThanEqual, TypeBool(state), corrected, address);
|
||||
return Select(state, TypeU32(state), AndCondition(state, not_invalid, no_overflow), corrected,
|
||||
ConstantU32(state, UINT32_MAX));
|
||||
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
|
||||
return state.builder.Constant(OpConstant, TypeDeviceAddress(state),
|
||||
{static_cast<uint32_t>(value),
|
||||
static_cast<uint32_t>(value >> 32u)});
|
||||
}
|
||||
|
||||
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
const auto low64 = Unary(state, OpUConvert, TypeDeviceAddress(state), low);
|
||||
const auto high64 = Binary(state, OpShiftLeftLogical, TypeDeviceAddress(state),
|
||||
Unary(state, OpUConvert, TypeDeviceAddress(state), high),
|
||||
ConstantDeviceAddress(state, 32));
|
||||
return Binary(state, OpBitwiseOr, TypeDeviceAddress(state), low64, high64);
|
||||
}
|
||||
|
||||
uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
auto low = ctx.Arg(inst, 1);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
|
||||
}
|
||||
uint32_t address = 0;
|
||||
if (mem.address_is_full) {
|
||||
address = DeviceAddressFromWords(state, low, ctx.Arg(inst, 2));
|
||||
} else {
|
||||
const auto* handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetAddressResource ||
|
||||
handle->NumArgs() != 2) {
|
||||
ctx.Fail(inst, "has no address base pair");
|
||||
return ConstantDeviceAddress(state, 0);
|
||||
}
|
||||
const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), ctx.Arg(*handle, 1));
|
||||
address = Binary(state, OpIAdd, TypeDeviceAddress(state), base,
|
||||
Unary(state, OpUConvert, TypeDeviceAddress(state), low));
|
||||
}
|
||||
auto immediate = static_cast<int32_t>(mem.offset);
|
||||
if (mem.kind == IR::ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
}
|
||||
return immediate == 0
|
||||
? address
|
||||
: Binary(state, OpIAdd, TypeDeviceAddress(state), address,
|
||||
ConstantDeviceAddress(state,
|
||||
static_cast<uint64_t>(static_cast<int64_t>(immediate))));
|
||||
}
|
||||
|
||||
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.fault_buffer_variable, ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void RecordBdaFault(EmitterState& state, uint32_t page) {
|
||||
const auto word = Binary(state, OpShiftRightLogical, TypeU32(state), page,
|
||||
ConstantU32(state, 5));
|
||||
const auto bit = Binary(
|
||||
state, OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 1),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), page, ConstantU32(state, 31)));
|
||||
const auto pointer = FaultElementPointer(state, word);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
state.builder.AddFunction(
|
||||
{OpStore, pointer, Binary(state, OpBitwiseOr, TypeU32(state), value, bit)});
|
||||
}
|
||||
|
||||
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpFunctionCall, TypeDeviceAddress(state), result, state.bda_pointer_function, address});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
|
||||
auto& state = ctx.state;
|
||||
const auto bda = GetBdaPointer(ctx, address);
|
||||
const auto present = Binary(state, OpINotEqual, TypeBool(state), bda,
|
||||
ConstantDeviceAddress(state, 0));
|
||||
return EmitValueOrZeroIfCondition(state, present, [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, bda});
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpLoad, TypeU32(state), value, pointer, MemoryAccessAlignedMask, sizeof(uint32_t)});
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t bits) {
|
||||
auto& state = ctx.state;
|
||||
const auto address = GuestAddress(ctx, inst, mem);
|
||||
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
|
||||
return EmitValueOrZeroIfCondition(state, active, [&]() {
|
||||
const auto aligned = Binary(state, OpBitwiseAnd, TypeDeviceAddress(state), address,
|
||||
ConstantDeviceAddress(state, ~uint64_t {3}));
|
||||
const auto first = LoadBdaDword(ctx, aligned);
|
||||
const auto byte = Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
Unary(state, OpUConvert, TypeU32(state), address),
|
||||
ConstantU32(state, 3));
|
||||
const auto crosses = bits == 8u
|
||||
? ConstantBool(state, false)
|
||||
: Binary(state, bits == 16u ? OpUGreaterThan : OpINotEqual,
|
||||
TypeBool(state), byte,
|
||||
ConstantU32(state, bits == 16u ? 2u : 0u));
|
||||
const auto second = EmitValueOrZeroIfCondition(state, crosses, [&]() {
|
||||
return LoadBdaDword(
|
||||
ctx, Binary(state, OpIAdd, TypeDeviceAddress(state), aligned,
|
||||
ConstantDeviceAddress(state, sizeof(uint32_t))));
|
||||
});
|
||||
const auto shift = Binary(state, OpShiftLeftLogical, TypeU32(state), byte,
|
||||
ConstantU32(state, 3));
|
||||
const auto upper_shift = Binary(
|
||||
state, OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
Binary(state, OpISub, TypeU32(state), ConstantU32(state, 4), byte),
|
||||
ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
const auto merged = Binary(
|
||||
state, OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), first, shift),
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state), second, upper_shift));
|
||||
return bits == 32u
|
||||
? merged
|
||||
: Binary(state, OpBitwiseAnd, TypeU32(state), merged,
|
||||
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -163,11 +251,10 @@ uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
|
||||
return Binary(ctx.state, OpIAdd, TypeU32(ctx.state), ctx.Arg(inst, 0),
|
||||
ConstantU32(ctx.state, mem.offset));
|
||||
}
|
||||
const auto address = AddressByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
|
||||
if (mem.kind == IR::ResourceKind::Scratch) {
|
||||
return address;
|
||||
if (mem.kind != IR::ResourceKind::Scratch) {
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
}
|
||||
return AddAddressMemoryOffset(ctx, mem, address);
|
||||
return ScratchByteAddress(ctx, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
|
||||
}
|
||||
|
||||
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -923,6 +1010,58 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
|
||||
} // namespace
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state) {
|
||||
if (!state.program.info.uses_dma) {
|
||||
return;
|
||||
}
|
||||
const auto type = TypeDeviceAddress(state);
|
||||
const auto function_type = state.builder.Type(OpTypeFunction, {type, type});
|
||||
state.bda_pointer_function = state.builder.AllocateId();
|
||||
const auto address = state.builder.AllocateId();
|
||||
const auto entry_label = state.builder.AllocateId();
|
||||
state.builder.AddName(state.bda_pointer_function, "get_bda_pointer");
|
||||
state.builder.AddFunction(
|
||||
{OpFunction, type, state.bda_pointer_function, FunctionControlNone, function_type});
|
||||
state.builder.AddFunction({OpFunctionParameter, type, address});
|
||||
EmitLabel(state, entry_label);
|
||||
|
||||
const auto page64 = Binary(
|
||||
state, OpShiftRightLogical, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
|
||||
const auto page = Unary(state, OpUConvert, TypeU32(state), page64);
|
||||
const auto entry_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeDeviceAddressStoragePointer(state), entry_pointer,
|
||||
state.bda_pagetable_variable, ConstantU32(state, 0), page});
|
||||
const auto base = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, type, base, entry_pointer});
|
||||
const auto missing =
|
||||
Binary(state, OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
|
||||
const auto fault_label = state.builder.AllocateId();
|
||||
const auto available_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
|
||||
state.builder.AddFunction(
|
||||
{OpBranchConditional, missing, fault_label, available_label});
|
||||
|
||||
EmitLabel(state, fault_label);
|
||||
RecordBdaFault(state, page);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
|
||||
EmitLabel(state, available_label);
|
||||
const auto offset = Binary(
|
||||
state, OpBitwiseAnd, type, address,
|
||||
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
|
||||
const auto available = Binary(state, OpIAdd, type, base, offset);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
|
||||
EmitLabel(state, merge_label);
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpPhi, type, result, ConstantDeviceAddress(state, 0), fault_label,
|
||||
available, available_label});
|
||||
state.builder.AddFunction({OpReturnValue, result});
|
||||
state.builder.AddFunction({OpFunctionEnd});
|
||||
}
|
||||
|
||||
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto op = inst.GetOpcode();
|
||||
@@ -981,6 +1120,10 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
const auto address_info = IR::AddressOpcodeInfoOf(op);
|
||||
const bool load_address = address_info.access == IR::AddressAccess::Read;
|
||||
if (load_address && ctx.Memory(inst).kind != IR::ResourceKind::Scratch) {
|
||||
ctx.Define(inst, LoadBda(ctx, inst, ctx.Memory(inst), address_info.data_bits));
|
||||
return true;
|
||||
}
|
||||
const bool load_buffer = op == IR::ValueOpcode::LoadBufferU8 ||
|
||||
op == IR::ValueOpcode::LoadBufferU16 ||
|
||||
op == IR::ValueOpcode::LoadBufferU32;
|
||||
|
||||
@@ -108,20 +108,8 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
return access;
|
||||
case IR::ResourceKind::ScalarAddress:
|
||||
case IR::ResourceKind::Flat:
|
||||
case IR::ResourceKind::Global: {
|
||||
if (state.address_memory_variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
|
||||
mem.resource,
|
||||
"address memory binding was not emitted");
|
||||
}
|
||||
const auto binding = ResourceForDescriptor(
|
||||
state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferPointer(state),
|
||||
access.object_pointer, state.address_memory_variable,
|
||||
ConstantU32(state, binding.array_index)});
|
||||
break;
|
||||
}
|
||||
case IR::ResourceKind::Global:
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
if (state.storage_buffer_variable == 0) {
|
||||
|
||||
@@ -28,6 +28,10 @@ uint32_t TypeU64(EmitterState& state) {
|
||||
return TypeU32Vector(state, 2);
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddress(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeInt, {64, 0});
|
||||
}
|
||||
|
||||
uint32_t TypeU32Pair(EmitterState& state) {
|
||||
const auto element = TypeU32(state);
|
||||
return state.builder.Type(OpTypeStruct, {element, element});
|
||||
@@ -90,6 +94,14 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeDeviceAddress(state));
|
||||
}
|
||||
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassPhysicalStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassPushConstant, TypeU32(state));
|
||||
}
|
||||
@@ -130,6 +142,15 @@ uint32_t SampleMaskArrayType(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeArray, {TypeI32(state), ConstantU32(state, 1)});
|
||||
}
|
||||
|
||||
uint32_t BdaPagetableType(EmitterState& state) {
|
||||
const auto array = state.builder.DecoratedType(
|
||||
OpTypeRuntimeArray, {TypeDeviceAddress(state)},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint64_t)}}});
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {array},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}}, {OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
return state.builder.Type(OpTypeArray, {TypeF32(state), ConstantU32(state, count)});
|
||||
}
|
||||
@@ -143,13 +164,14 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
state.storage_buffer_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::AddressMemory) != nullptr) {
|
||||
const auto count =
|
||||
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::AddressMemory));
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {StorageBufferType(state), count});
|
||||
const auto pointer_type = TypePointer(state, StorageClassStorageBuffer, array_type);
|
||||
state.address_memory_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::BdaPagetable) != nullptr) {
|
||||
state.bda_pagetable_variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, StorageClassStorageBuffer, BdaPagetableType(state)),
|
||||
StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::FaultBuffer) != nullptr) {
|
||||
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (state.program.bindings.push_constant_size != 0) {
|
||||
const auto pointer_type =
|
||||
@@ -564,9 +586,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
if (state.storage_buffer_variable != 0) {
|
||||
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
|
||||
}
|
||||
if (state.address_memory_variable != 0) {
|
||||
Decorate(state.address_memory_variable, "address_memory",
|
||||
IR::DescriptorBindingKind::AddressMemory);
|
||||
if (state.bda_pagetable_variable != 0) {
|
||||
Decorate(state.bda_pagetable_variable, "bda_pagetable",
|
||||
IR::DescriptorBindingKind::BdaPagetable);
|
||||
}
|
||||
if (state.fault_buffer_variable != 0) {
|
||||
Decorate(state.fault_buffer_variable, "fault_buffer",
|
||||
IR::DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
constexpr const char* SampledNames[] = {"sampled_1d",
|
||||
"sampled_1d_array",
|
||||
@@ -644,6 +670,11 @@ void DefineModule(EmitterState& state) {
|
||||
|
||||
state.builder.RequireCapability(CapabilityShader);
|
||||
state.builder.RequireCapability(CapabilitySignedZeroInfNanPreserve);
|
||||
if (state.program.info.uses_dma) {
|
||||
state.builder.RequireCapability(CapabilityInt64);
|
||||
state.builder.RequireCapability(CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityClipDistance);
|
||||
}
|
||||
@@ -682,7 +713,10 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_float_controls");
|
||||
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
|
||||
state.builder.AddMemoryModel(
|
||||
{state.program.info.uses_dma ? AddressingModelPhysicalStorageBuffer64
|
||||
: AddressingModelLogical,
|
||||
MemoryModelGLSL450});
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
|
||||
state.interface_variables);
|
||||
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
|
||||
|
||||
@@ -566,6 +566,7 @@ bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* e
|
||||
SetError(error, ctx.error.c_str());
|
||||
return false;
|
||||
}
|
||||
DefineGetBdaPointer(state);
|
||||
for (const auto* block: program.blocks) {
|
||||
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
|
||||
|
||||
@@ -136,12 +136,10 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
|
||||
memory.offen = decoded.offen;
|
||||
memory.resource = ResourceIndexFromOperand(decoded.src1);
|
||||
memory.sampler = ResourceIndexFromOperand(decoded.src2);
|
||||
if (memory.kind == ResourceKind::ScalarAddress) {
|
||||
memory.resource = RawScalarLoadBase(decoded.src0);
|
||||
} else if (memory.kind == ResourceKind::ScalarBuffer) {
|
||||
if (memory.kind == ResourceKind::ScalarBuffer) {
|
||||
memory.resource = ResourceIndexFromOperand(decoded.src0);
|
||||
} else if (memory.kind == ResourceKind::Lds || memory.kind == ResourceKind::Gds ||
|
||||
memory.kind == ResourceKind::Scratch) {
|
||||
} else if (IsAddressResourceKind(memory.kind) || memory.kind == ResourceKind::Lds ||
|
||||
memory.kind == ResourceKind::Gds) {
|
||||
memory.resource = 0;
|
||||
memory.sampler = 0;
|
||||
}
|
||||
@@ -421,7 +419,7 @@ IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
|
||||
bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource =
|
||||
raw ? GetScalarAddressResource(memory.resource) : GetBufferResource(memory);
|
||||
raw ? GetScalarAddressResource(RawScalarLoadBase(inst.src0)) : GetBufferResource(memory);
|
||||
const auto offset = ReadU32(inst.src1);
|
||||
std::array<IR::Value, 16> loaded {};
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
|
||||
@@ -176,20 +176,6 @@ struct SampledResourcePair {
|
||||
bool operator==(const SampledResourcePair& other) const = default;
|
||||
};
|
||||
|
||||
struct AddressResource {
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::Flat;
|
||||
int32_t min_offset = 0;
|
||||
uint64_t specialized_base = 0;
|
||||
bool unbased = true;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
|
||||
bool operator==(const AddressResource& other) const = default;
|
||||
};
|
||||
|
||||
enum class StageInputKind {
|
||||
VertexIndex,
|
||||
InstanceIndex,
|
||||
@@ -321,7 +307,8 @@ enum class DescriptorBindingKind {
|
||||
StorageAtomic3D,
|
||||
Samplers,
|
||||
Gds,
|
||||
AddressMemory,
|
||||
BdaPagetable,
|
||||
FaultBuffer,
|
||||
FlattenedSrt,
|
||||
UserData,
|
||||
Count,
|
||||
@@ -431,13 +418,11 @@ struct BindingLayout {
|
||||
|
||||
struct ShaderInfo {
|
||||
static constexpr uint32_t MaxBuffers = 32;
|
||||
static constexpr uint32_t MaxAddresses = 32;
|
||||
static constexpr uint32_t MaxImages = 32;
|
||||
static constexpr uint32_t MaxSamplers = 32;
|
||||
static constexpr uint32_t MaxSampledPairs = 64;
|
||||
|
||||
std::vector<BufferResource> buffers;
|
||||
std::vector<AddressResource> addresses;
|
||||
std::vector<ImageResource> images;
|
||||
std::vector<SamplerResource> samplers;
|
||||
std::vector<SampledResourcePair> sampled_pairs;
|
||||
@@ -446,6 +431,7 @@ struct ShaderInfo {
|
||||
std::array<uint8_t, 32> vertex_fetch_components {};
|
||||
int32_t vertex_offset_sgpr = -1;
|
||||
bool has_bitwise_xor = false;
|
||||
bool uses_dma = false;
|
||||
|
||||
bool operator==(const ShaderInfo& other) const = default;
|
||||
};
|
||||
|
||||
@@ -93,8 +93,7 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
|
||||
return false;
|
||||
}
|
||||
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
|
||||
next.memory_offset_count =
|
||||
static_cast<uint32_t>(program.info.buffers.size() + program.info.addresses.size());
|
||||
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
|
||||
|
||||
if (!program.info.buffers.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.buffers.size());
|
||||
@@ -157,12 +156,9 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
|
||||
if (uses_gds) {
|
||||
AddBinding(next, DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.addresses.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources));
|
||||
if (program.info.uses_dma) {
|
||||
AddBinding(next, DescriptorBindingKind::BdaPagetable);
|
||||
AddBinding(next, DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
|
||||
@@ -267,13 +267,6 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t AddressSpecialization(const AddressResource& resource,
|
||||
const ResourceSnapshot::Address& snapshot) {
|
||||
return resource.kind == ResourceKind::Flat || resource.unbased
|
||||
? snapshot.binding_base
|
||||
: snapshot.guest_base - snapshot.binding_base;
|
||||
}
|
||||
|
||||
size_t FlattenedRuntimeDwords(const Program& program) {
|
||||
size_t size = program.srt_reads.size();
|
||||
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
|
||||
@@ -298,8 +291,7 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
}
|
||||
if (snapshot.buffers.size() != program.info.buffers.size() ||
|
||||
snapshot.images.size() != program.info.images.size() ||
|
||||
snapshot.samplers.size() != program.info.samplers.size() ||
|
||||
snapshot.addresses.size() != program.info.addresses.size()) {
|
||||
snapshot.samplers.size() != program.info.samplers.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "resource snapshot does not match dense shader topology";
|
||||
}
|
||||
@@ -343,14 +335,6 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
}
|
||||
return true;
|
||||
};
|
||||
for (uint32_t i = 0; i < snapshot.addresses.size(); i++) {
|
||||
if (snapshot.addresses[i].binding_base > snapshot.addresses[i].guest_base) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} binds above its guest base", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::unordered_set<uint32_t> indirect_resources;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto* source = table.resource < program.info.images.size()
|
||||
@@ -526,15 +510,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
if (program.info.addresses[i].specialized_base !=
|
||||
AddressSpecialization(program.info.addresses[i], snapshot.addresses[i])) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("address resource {} no longer matches specialization", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -550,7 +525,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
std::vector<DescriptorSourceRequest> requests;
|
||||
std::vector<uint8_t> clean_flat_slots(program.srt_reads.size());
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
|
||||
program.info.samplers.size() + program.info.addresses.size());
|
||||
program.info.samplers.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
requests.push_back({buffer.source, buffer.first_use_pc});
|
||||
}
|
||||
@@ -576,12 +551,6 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
requests.push_back({sampler.source, sampler.first_use_pc});
|
||||
}
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!address.unbased) {
|
||||
requests.push_back({address.source, address.first_use_pc});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, clean_flat_slots,
|
||||
@@ -686,37 +655,6 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
return false;
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
cursor += program.info.samplers.size();
|
||||
for (const auto& address: program.info.addresses) {
|
||||
if (!address.unbased) {
|
||||
const auto value = *cursor++;
|
||||
auto base = (static_cast<uint64_t>(value.dwords[0]) |
|
||||
static_cast<uint64_t>(value.dwords[1]) << 32u) &
|
||||
AddressMask;
|
||||
if (address.kind == ResourceKind::ScalarAddress) {
|
||||
base &= ~uint64_t {3};
|
||||
}
|
||||
const auto before = static_cast<uint64_t>(-static_cast<int64_t>(address.min_offset));
|
||||
uint64_t binding_base = 0;
|
||||
if (address.kind == ResourceKind::Flat) {
|
||||
binding_base = base & ~(FlatAddressWindowSize - 1u);
|
||||
} else if (base >= before) {
|
||||
binding_base = base - before;
|
||||
}
|
||||
next.addresses.push_back({base, binding_base});
|
||||
} else {
|
||||
if (!runtime.flat_memory_base.has_value()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("unbased {} address at pc 0x{:08x} requires runtime "
|
||||
"guest-address translation",
|
||||
address.kind == ResourceKind::Flat ? "FLAT" : "global",
|
||||
address.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.addresses.push_back({*runtime.flat_memory_base, *runtime.flat_memory_base});
|
||||
}
|
||||
}
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
if (!ValidateResourceSnapshot(program, next, error)) {
|
||||
return false;
|
||||
@@ -791,10 +729,6 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
|
||||
next.buffers[i].descriptor_format = descriptor.Format();
|
||||
next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW();
|
||||
}
|
||||
for (uint32_t i = 0; i < next.addresses.size(); i++) {
|
||||
next.addresses[i].specialized_base =
|
||||
AddressSpecialization(next.addresses[i], next_snapshot.addresses[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < next.images.size(); i++) {
|
||||
const auto& descriptor = next_snapshot.images[i];
|
||||
auto& image = next.images[i];
|
||||
|
||||
@@ -5,15 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
constexpr uint64_t FlatAddressWindowSize = 0x10000;
|
||||
|
||||
struct ResourceSnapshot {
|
||||
struct Address {
|
||||
uint64_t guest_base = 0;
|
||||
uint64_t binding_base = 0;
|
||||
|
||||
bool operator==(const Address& other) const = default;
|
||||
};
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
uint32_t capacity = 0;
|
||||
@@ -25,7 +17,6 @@ struct ResourceSnapshot {
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
std::vector<DescriptorValue> samplers;
|
||||
std::vector<Address> addresses;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
@@ -96,10 +95,10 @@ class Tracker {
|
||||
public:
|
||||
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
|
||||
m_info.buffers.clear();
|
||||
m_info.addresses.clear();
|
||||
m_info.images.clear();
|
||||
m_info.samplers.clear();
|
||||
m_info.sampled_pairs.clear();
|
||||
m_info.uses_dma = false;
|
||||
}
|
||||
|
||||
bool Run(std::string* error) {
|
||||
@@ -180,90 +179,6 @@ private:
|
||||
m_program.shader_hash, StageName(m_program.stage), pc, reason));
|
||||
}
|
||||
|
||||
struct AddressPart {
|
||||
Value value;
|
||||
bool rooted = false;
|
||||
};
|
||||
|
||||
AddressPart FindAddressPart(Value value, Value active,
|
||||
std::unordered_set<const Inst*>& visiting) const {
|
||||
value = value.Resolve();
|
||||
if (value.IsImmediate()) {
|
||||
return {value, false};
|
||||
}
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr || !visiting.insert(inst).second) {
|
||||
return {};
|
||||
}
|
||||
const auto finish = [&](AddressPart part) {
|
||||
visiting.erase(inst);
|
||||
return part;
|
||||
};
|
||||
std::string reason;
|
||||
if (ValidateRuntimeValue(m_program, value, reason)) {
|
||||
return finish({value, true});
|
||||
}
|
||||
const auto merge = [&](AddressPart left, AddressPart right, bool require_both) {
|
||||
if (left.value.IsEmpty() || right.value.IsEmpty()) {
|
||||
return require_both ? AddressPart {} : (left.value.IsEmpty() ? right : left);
|
||||
}
|
||||
return EquivalentValue(m_program, left.value, right.value)
|
||||
? AddressPart {left.value, left.rooted || right.rooted}
|
||||
: AddressPart {};
|
||||
};
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::SelectU32:
|
||||
if (EquivalentValue(m_program, inst->Arg(0), active)) {
|
||||
return finish(FindAddressPart(inst->Arg(1), active, visiting));
|
||||
}
|
||||
return finish(merge(FindAddressPart(inst->Arg(1), active, visiting),
|
||||
FindAddressPart(inst->Arg(2), active, visiting), true));
|
||||
case ValueOpcode::Phi: {
|
||||
const auto invariant = ResolveInvariantPhi(m_program, value);
|
||||
return finish(invariant.IsEmpty() ? AddressPart {}
|
||||
: FindAddressPart(invariant, active, visiting));
|
||||
}
|
||||
case ValueOpcode::IAdd32:
|
||||
case ValueOpcode::ISub32: {
|
||||
const auto left = FindAddressPart(inst->Arg(0), active, visiting);
|
||||
const auto right = FindAddressPart(inst->Arg(1), active, visiting);
|
||||
if (left.rooted == right.rooted ||
|
||||
(inst->GetOpcode() == ValueOpcode::ISub32 && right.rooted)) {
|
||||
return finish({});
|
||||
}
|
||||
return finish(left.rooted ? left : right);
|
||||
}
|
||||
case ValueOpcode::CompositeExtractU32x2: {
|
||||
const auto* source = inst->Arg(0).ResolveInstruction();
|
||||
if (source == nullptr || source->GetOpcode() != ValueOpcode::IAddCarry32) {
|
||||
return finish({});
|
||||
}
|
||||
const auto left = FindAddressPart(source->Arg(0), active, visiting);
|
||||
const auto right = FindAddressPart(source->Arg(1), active, visiting);
|
||||
if (left.rooted == right.rooted) {
|
||||
return finish({});
|
||||
}
|
||||
return finish(left.rooted ? left : right);
|
||||
}
|
||||
default: return finish({});
|
||||
}
|
||||
}
|
||||
|
||||
bool MakeFlatAddressSource(const Inst& handle, Value active,
|
||||
DescriptorSource& descriptor) const {
|
||||
std::unordered_set<const Inst*> visiting;
|
||||
auto low = FindAddressPart(handle.Arg(0), active, visiting);
|
||||
visiting.clear();
|
||||
auto high = FindAddressPart(handle.Arg(1), active, visiting);
|
||||
if ((!low.rooted && !high.rooted) || low.value.IsEmpty() || high.value.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
descriptor.dword_count = 2;
|
||||
descriptor.dwords[0] = low.value;
|
||||
descriptor.dwords[1] = high.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
||||
DescriptorSource& descriptor, uint32_t pc, std::string* error) const {
|
||||
if (handle.NumArgs() != width) {
|
||||
@@ -577,42 +492,14 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetAddressHandle(const Inst& memory_inst, Value value, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, bool& unbased, std::string* error) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
bool ValidateAddressHandle(Value value, uint32_t pc, std::string* error) const {
|
||||
const auto* handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) {
|
||||
return Fail(pc, error, "address operation requires GetAddressResource");
|
||||
}
|
||||
if (handle->NumArgs() != 2) {
|
||||
return Fail(pc, error, "GetAddressResource must have two address dwords");
|
||||
}
|
||||
DescriptorSource descriptor;
|
||||
const auto& memory = m_program.memory_info[memory_inst.Flags<MemoryFlags>().index];
|
||||
if (memory.address_is_full) {
|
||||
const auto active = memory_inst.Arg(memory_inst.NumArgs() - 1u);
|
||||
if (memory.kind != ResourceKind::Flat ||
|
||||
!MakeFlatAddressSource(*handle, active, descriptor)) {
|
||||
unbased = true;
|
||||
source = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
} else if (!MakeSource(*handle, 2, false, false, descriptor, pc, error)) {
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, reason, bad_dword)) {
|
||||
if (memory.address_is_full) {
|
||||
unbased = true;
|
||||
source = UINT32_MAX;
|
||||
return true;
|
||||
}
|
||||
return Fail(
|
||||
pc, error,
|
||||
fmt::format("scalar memory base dword {} is unresolved: {}", bad_dword, reason));
|
||||
}
|
||||
unbased = false;
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -706,39 +593,6 @@ private:
|
||||
return static_cast<uint32_t>(m_info.samplers.size() - 1);
|
||||
}
|
||||
|
||||
uint32_t AddAddress(uint32_t source, bool unbased, const MemoryInfo& memory,
|
||||
AddressAccess access, uint32_t pc) {
|
||||
auto immediate = static_cast<int32_t>(memory.offset);
|
||||
if (memory.kind == ResourceKind::ScalarAddress) {
|
||||
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
|
||||
}
|
||||
const auto min_offset = unbased ? 0 : std::min(immediate, 0);
|
||||
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
|
||||
auto& address = m_info.addresses[i];
|
||||
if (address.source == source && address.unbased == unbased &&
|
||||
address.kind == memory.kind) {
|
||||
address.first_use_pc = std::min(address.first_use_pc, pc);
|
||||
address.min_offset = std::min(address.min_offset, min_offset);
|
||||
address.read = address.read || access == AddressAccess::Read;
|
||||
address.written = address.written || access == AddressAccess::Write;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
AddressResource address;
|
||||
address.source = source;
|
||||
address.first_use_pc = pc;
|
||||
address.kind = memory.kind;
|
||||
address.min_offset = min_offset;
|
||||
address.unbased = unbased;
|
||||
address.read = access == AddressAccess::Read;
|
||||
address.written = access == AddressAccess::Write;
|
||||
m_info.addresses.push_back(address);
|
||||
return static_cast<uint32_t>(m_info.addresses.size() - 1);
|
||||
}
|
||||
|
||||
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
|
||||
for (auto& pair: m_info.sampled_pairs) {
|
||||
if (pair.image == image && pair.sampler == sampler) {
|
||||
@@ -839,16 +693,11 @@ private:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool unbased = false;
|
||||
if (!GetAddressHandle(inst, inst.Arg(0), flags.pc, handle, source, unbased, error)) {
|
||||
if (!ValidateAddressHandle(inst.Arg(0), flags.pc, error)) {
|
||||
return false;
|
||||
}
|
||||
resource = AddAddress(source, unbased, memory, address_info.access, flags.pc);
|
||||
if (resource == UINT32_MAX) {
|
||||
return Fail(flags.pc, error, "address resource limit exceeded");
|
||||
}
|
||||
return AddHandlePatch(handle, resource, flags.pc, error) &&
|
||||
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
|
||||
m_info.uses_dma = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
@@ -17,7 +16,6 @@ struct SrtRuntime {
|
||||
uint64_t shader_base = 0;
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -994,10 +994,10 @@ static void LogShaderProgramCacheHit(const char* stage, uint64_t shader_hash, ui
|
||||
|
||||
static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Program& program) {
|
||||
std::string ret = fmt::format(
|
||||
"push={} groups={} user={} buffers={} images={} samplers={} addresses={} srt={}",
|
||||
"push={} groups={} user={} buffers={} images={} samplers={} dma={} srt={}",
|
||||
program.bindings.push_constant_size, program.bindings.descriptors.size(),
|
||||
program.bindings.user_data_registers.size(), program.info.buffers.size(),
|
||||
program.info.images.size(), program.info.samplers.size(), program.info.addresses.size(),
|
||||
program.info.images.size(), program.info.samplers.size(), program.info.uses_dma,
|
||||
program.srt_reads.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
@@ -1014,9 +1014,6 @@ static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Prog
|
||||
static_cast<uint32_t>(image.conversion_format), image.shader_swizzle,
|
||||
image.indirect_root, image.indirect_mapping_offset, image.indirect_mapping_capacity);
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
ret += fmt::format(" a{}[base=0x{:x}]", i, program.info.addresses[i].specialized_base);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1056,11 +1053,6 @@ static void ShaderAppendNativeSpecialization(std::vector<uint32_t>&
|
||||
ids.push_back(static_cast<uint32_t>(image.indirect_resources.size()));
|
||||
ids.insert(ids.end(), image.indirect_resources.begin(), image.indirect_resources.end());
|
||||
}
|
||||
ids.push_back(static_cast<uint32_t>(program.info.addresses.size()));
|
||||
for (const auto& address: program.info.addresses) {
|
||||
ids.push_back(static_cast<uint32_t>(address.specialized_base));
|
||||
ids.push_back(static_cast<uint32_t>(address.specialized_base >> 32u));
|
||||
}
|
||||
}
|
||||
|
||||
static std::span<const uint32_t> AddShaderProgramPermutation(const char* stage,
|
||||
|
||||
@@ -295,7 +295,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
const auto same_snapshot = [](const ResourceSnapshot &lhs,
|
||||
const ResourceSnapshot &rhs) {
|
||||
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
|
||||
lhs.samplers == rhs.samplers && lhs.addresses == rhs.addresses &&
|
||||
lhs.samplers == rhs.samplers &&
|
||||
lhs.flattened_srt == rhs.flattened_srt &&
|
||||
lhs.user_data == rhs.user_data &&
|
||||
lhs.indirect_images.empty() == rhs.indirect_images.empty();
|
||||
@@ -848,7 +848,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
|
||||
Check(fixture.program.srt_reads.size() == 1,
|
||||
"shared typed scalar read did not receive one flat SRT slot");
|
||||
Check(fixture.program.info.buffers.size() == 1 &&
|
||||
fixture.program.info.addresses.empty(),
|
||||
!fixture.program.info.uses_dma,
|
||||
"planning-only scalar reads leaked into resource topology");
|
||||
Check(fixture.program.memory_info[0].planning_only,
|
||||
"canonical runtime scalar read was not marked planning-only");
|
||||
@@ -910,7 +910,7 @@ void TestDynamicSrtReadRemainsExplicit() {
|
||||
|
||||
Check(fixture.program.srt_reads.empty() &&
|
||||
fixture.program.dynamic_reads.size() == 1 &&
|
||||
fixture.program.info.addresses.size() == 1,
|
||||
fixture.program.info.uses_dma,
|
||||
"dynamic scalar read was incorrectly flattened or lost");
|
||||
std::array<uint32_t, 3> user_data{0x1000u, 0u, 4u};
|
||||
TestMemory memory;
|
||||
@@ -932,11 +932,13 @@ void TestDynamicSrtReadRemainsExplicit() {
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FlattenedSrt) == nullptr &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::AddressMemory) != nullptr,
|
||||
DescriptorBindingKind::BdaPagetable) != nullptr &&
|
||||
FindBinding(fixture.program.bindings,
|
||||
DescriptorBindingKind::FaultBuffer) != nullptr,
|
||||
"dynamic scalar read received the wrong resource bindings");
|
||||
Check(fixture.program.bindings.memory_offset_dword ==
|
||||
fixture.program.bindings.user_data_registers.size() &&
|
||||
fixture.program.bindings.memory_offset_count == 2u &&
|
||||
fixture.program.bindings.memory_offset_count == 1u &&
|
||||
fixture.program.bindings.ShaderDataDwords() ==
|
||||
fixture.program.bindings.memory_offset_dword + 1u,
|
||||
"unified memory-offset layout is inconsistent");
|
||||
@@ -1029,7 +1031,7 @@ void TestInvariantLoopPhi() {
|
||||
"loop-invariant descriptor phi was not evaluated through typed SSA");
|
||||
}
|
||||
|
||||
void TestAddressMaterializationAndSpecialization() {
|
||||
void TestDmaAddressMaterialization() {
|
||||
Fixture fixture;
|
||||
const auto based =
|
||||
fixture.Address(fixture.UserData(0), fixture.UserData(1), 4);
|
||||
@@ -1050,29 +1052,19 @@ void TestAddressMaterializationAndSpecialization() {
|
||||
fixture.AddMemory(flat, 8));
|
||||
fixture.PlanAndTrack();
|
||||
|
||||
Check(fixture.program.info.addresses.size() == 2 &&
|
||||
!fixture.program.info.addresses[0].unbased &&
|
||||
fixture.program.info.addresses[0].min_offset == -8 &&
|
||||
fixture.program.info.addresses[1].unbased,
|
||||
"typed based and unbased addresses were classified incorrectly");
|
||||
Check(fixture.program.info.uses_dma,
|
||||
"typed address operations did not enable DMA");
|
||||
std::array<uint32_t, 2> user_data{0x2008u, 0u};
|
||||
SrtRuntime runtime{.user_data = user_data, .flat_memory_base = 0x9000u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
|
||||
"address resources did not materialize");
|
||||
Check(snapshot.addresses.size() == 2 &&
|
||||
snapshot.addresses[0].guest_base == 0x2008u &&
|
||||
snapshot.addresses[0].binding_base == 0x2000u &&
|
||||
snapshot.addresses[1].binding_base == 0x9000u,
|
||||
"materialized address windows are incorrect");
|
||||
Check(SpecializeResources(fixture.program, snapshot, &error) &&
|
||||
fixture.program.info.addresses[0].specialized_base == 8u &&
|
||||
fixture.program.info.addresses[1].specialized_base == 0x9000u,
|
||||
"typed address specialization was not applied");
|
||||
"DMA shader resources did not materialize");
|
||||
Check(SpecializeResources(fixture.program, snapshot, &error),
|
||||
"DMA shader resources were not specialized");
|
||||
}
|
||||
|
||||
void TestExecMaskedFlatAddressProvenance() {
|
||||
void TestDynamicFlatAddressesUseDma() {
|
||||
Fixture fixture;
|
||||
const auto low_root = fixture.UserData(0);
|
||||
const auto high_root = fixture.UserData(1);
|
||||
@@ -1092,18 +1084,14 @@ void TestExecMaskedFlatAddressProvenance() {
|
||||
fixture.AddMemory(flat, 0xa4));
|
||||
fixture.PlanAndTrack();
|
||||
|
||||
Check(fixture.program.info.addresses.size() == 1 &&
|
||||
!fixture.program.info.addresses[0].unbased,
|
||||
"exec-masked FLAT address lost its active user-data root");
|
||||
Check(fixture.program.info.uses_dma,
|
||||
"exec-masked FLAT address did not enable DMA");
|
||||
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
|
||||
SrtRuntime runtime{.user_data = user_data};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error) &&
|
||||
snapshot.addresses.size() == 1 &&
|
||||
snapshot.addresses[0].guest_base == 0x0000000123456780ull &&
|
||||
snapshot.addresses[0].binding_base == 0x0000000123450000ull,
|
||||
"exec-masked FLAT address materialized the wrong user-data root");
|
||||
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
|
||||
"exec-masked FLAT shader resources did not materialize");
|
||||
|
||||
Fixture mismatch;
|
||||
const auto mismatch_active = mismatch.Emit(ValueOpcode::INotEqual32,
|
||||
@@ -1122,9 +1110,8 @@ void TestExecMaskedFlatAddressProvenance() {
|
||||
{mismatch_address, mismatch_low, mismatch_high, other_active},
|
||||
mismatch.AddMemory(flat, 0xa4));
|
||||
mismatch.PlanAndTrack();
|
||||
Check(mismatch.program.info.addresses.size() == 1 &&
|
||||
mismatch.program.info.addresses[0].unbased,
|
||||
"FLAT address used a select arm guarded by a different active mask");
|
||||
Check(mismatch.program.info.uses_dma,
|
||||
"dynamic FLAT address did not enable DMA");
|
||||
}
|
||||
|
||||
void TestBufferSwizzleSpecialization() {
|
||||
@@ -1393,8 +1380,8 @@ int main() {
|
||||
Run("phi validation", TestPhiValidation);
|
||||
Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue);
|
||||
Run("invariant loop phi", TestInvariantLoopPhi);
|
||||
Run("address materialization", TestAddressMaterializationAndSpecialization);
|
||||
Run("exec-masked FLAT address", TestExecMaskedFlatAddressProvenance);
|
||||
Run("DMA address materialization", TestDmaAddressMaterialization);
|
||||
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
|
||||
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
|
||||
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
|
||||
Run("graphics push constants", TestGraphicsPushConstantLayout);
|
||||
|
||||
@@ -919,6 +919,11 @@ void CheckLeastRecentlyUsedCacheOrdering() {
|
||||
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;
|
||||
@@ -959,15 +964,15 @@ struct TestCase {
|
||||
bool compile_only = false;
|
||||
size_t storage_buffer_range_dwords = 0;
|
||||
std::vector<u32> storage_buffer_offsets;
|
||||
std::vector<u32> address_memory_offsets;
|
||||
std::vector<BdaMapping> bda_mappings;
|
||||
bool expand_shader_data_storage = false;
|
||||
bool expected_force_point_sampler = false;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
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;
|
||||
std::string expected_compile_error;
|
||||
};
|
||||
|
||||
struct GraphicsCase {
|
||||
@@ -1168,7 +1173,6 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
options.user_data = user_data.data();
|
||||
options.read_memory = ReadTestMemory;
|
||||
options.read_memory_data = const_cast<std::vector<u32> *>(&test.initial);
|
||||
options.flat_memory_base = test.flat_memory_base;
|
||||
options.scratch_dwords = test.compute_info.scratch_size_dwords;
|
||||
if (test.has_compute_info) {
|
||||
options.wave_size = test.compute_info.wave_size;
|
||||
@@ -1177,8 +1181,14 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
if (!ShaderRecompiler::TryRecompile(test.code, options, result, &error)) {
|
||||
if (!test.expected_compile_error.empty() &&
|
||||
error.find(test.expected_compile_error) != std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
Fail(test.name, "decode/IR", error.c_str());
|
||||
}
|
||||
Require(test.name, "decode/IR", test.expected_compile_error.empty(),
|
||||
"shader unexpectedly compiled despite the required rejection");
|
||||
for (const auto &[text, expected] : test.decoded_counts) {
|
||||
const auto actual = CountText(result.decoded_dump, text);
|
||||
Require(test.name, "decoded RDNA2", actual == expected,
|
||||
@@ -1267,17 +1277,6 @@ CompiledShader CompileCase(const TestCase &test) {
|
||||
packed_user_data[result.program.bindings.memory_offset_dword + i / 4u] |=
|
||||
offset << ((i % 4u) * 8u);
|
||||
}
|
||||
for (u32 i = 0; i < result.program.info.addresses.size(); i++) {
|
||||
u32 offset = 0;
|
||||
if (i < test.address_memory_offsets.size()) {
|
||||
offset = test.address_memory_offsets[i];
|
||||
}
|
||||
Require(test.name, "shader data", offset < 256,
|
||||
"address-memory offset is not representable");
|
||||
const auto index = buffer_count + i;
|
||||
packed_user_data[result.program.bindings.memory_offset_dword +
|
||||
index / 4u] |= offset << ((index % 4u) * 8u);
|
||||
}
|
||||
return {std::move(result.spirv), std::move(result.program),
|
||||
std::move(result.resources.flattened_srt),
|
||||
std::move(packed_user_data)};
|
||||
@@ -1469,6 +1468,7 @@ public:
|
||||
struct Buffer {
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceMemory memory = nullptr;
|
||||
vk::DeviceAddress device_address = 0;
|
||||
vk::DeviceSize size = 0;
|
||||
bool coherent = false;
|
||||
};
|
||||
@@ -3157,7 +3157,7 @@ public:
|
||||
"chunked host copy lost bytes across its scratch boundary");
|
||||
|
||||
constexpr uint64_t index_offset = 0x180000;
|
||||
constexpr uint64_t index_page = BufferCache::CACHING_PAGE_SIZE;
|
||||
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",
|
||||
@@ -3386,7 +3386,7 @@ public:
|
||||
"Buffer ownership");
|
||||
|
||||
constexpr uint64_t starvation_offset = 0x100000;
|
||||
constexpr uint64_t starvation_stride = 2 * BufferCache::CACHING_PAGE_SIZE;
|
||||
constexpr uint64_t starvation_stride = 2 * BufferCache::CACHING_PAGESIZE;
|
||||
constexpr uint32_t starvation_count = 33;
|
||||
constexpr uint32_t starvation_value = 0x31415926u;
|
||||
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
||||
@@ -3443,7 +3443,7 @@ public:
|
||||
constexpr uint64_t lookup_only_offset = 0x218000;
|
||||
constexpr uint64_t obtained_offset = 0x220000;
|
||||
constexpr uint64_t residency_size =
|
||||
2 * BufferCache::CACHING_PAGE_SIZE;
|
||||
2 * BufferCache::CACHING_PAGESIZE;
|
||||
BufferCacheTestAccess::SetGarbageCollectionThresholds(
|
||||
cache, std::numeric_limits<uint64_t>::max(),
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
@@ -4199,7 +4199,7 @@ public:
|
||||
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_PAGE_SIZE &&
|
||||
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 "
|
||||
@@ -8946,7 +8946,7 @@ public:
|
||||
|
||||
Buffer CreateStorageBuffer(const char *shader_name,
|
||||
const std::vector<u32> &initial,
|
||||
size_t dword_count) {
|
||||
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));
|
||||
@@ -8955,6 +8955,9 @@ public:
|
||||
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),
|
||||
@@ -8979,12 +8982,26 @@ public:
|
||||
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++) {
|
||||
@@ -9366,7 +9383,9 @@ public:
|
||||
pool_sizes.push_back({type, count});
|
||||
};
|
||||
add_pool_size(vk::DescriptorType::eStorageBuffer,
|
||||
Count(Kind::Buffers) + Count(Kind::AddressMemory) +
|
||||
Count(Kind::Buffers) +
|
||||
(Binding(Kind::BdaPagetable) != nullptr ? 1u : 0u) +
|
||||
(Binding(Kind::FaultBuffer) != nullptr ? 1u : 0u) +
|
||||
Count(Kind::Gds) +
|
||||
(Binding(Kind::FlattenedSrt) != nullptr ? 1u : 0u) +
|
||||
(Binding(Kind::UserData) != nullptr ? 1u : 0u));
|
||||
@@ -9411,7 +9430,6 @@ public:
|
||||
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
std::vector<vk::DescriptorBufferInfo> buffer_infos;
|
||||
std::vector<vk::DescriptorBufferInfo> address_memory_infos;
|
||||
std::vector<vk::DescriptorImageInfo> sampled_infos;
|
||||
std::vector<vk::DescriptorImageInfo> storage_infos;
|
||||
std::vector<vk::DescriptorImageInfo> storage_uint_infos;
|
||||
@@ -9422,6 +9440,33 @@ public:
|
||||
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) {
|
||||
@@ -9450,21 +9495,6 @@ public:
|
||||
write.pBufferInfo = buffer_infos.data();
|
||||
writes.push_back(write);
|
||||
}
|
||||
if (const auto *address = Binding(Kind::AddressMemory);
|
||||
address != nullptr) {
|
||||
address_memory_infos.resize(address->resources.size());
|
||||
for (auto &info : address_memory_infos) {
|
||||
info = {buffer.buffer, 0, buffer.size};
|
||||
}
|
||||
vk::WriteDescriptorSet write{};
|
||||
write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
write.dstSet = descriptor_set;
|
||||
write.dstBinding = Native(Kind::AddressMemory);
|
||||
write.descriptorCount = static_cast<u32>(address_memory_infos.size());
|
||||
write.descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
write.pBufferInfo = address_memory_infos.data();
|
||||
writes.push_back(write);
|
||||
}
|
||||
if (const auto *flattened = Binding(Kind::FlattenedSrt);
|
||||
flattened != nullptr) {
|
||||
flattened_buffer = CreateStorageBuffer(test.name, compiled.flattened_srt,
|
||||
@@ -9590,6 +9620,52 @@ public:
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
std::array<vk::BufferMemoryBarrier, 2> barriers{};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
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::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);
|
||||
@@ -10951,6 +11027,7 @@ private:
|
||||
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)),
|
||||
@@ -11001,13 +11078,18 @@ private:
|
||||
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) {
|
||||
if (barycentric.fragmentShaderBarycentric != true ||
|
||||
features.features.shaderInt64 != true ||
|
||||
features12.bufferDeviceAddress != true) {
|
||||
continue;
|
||||
}
|
||||
m_physical_device = physical;
|
||||
@@ -11051,6 +11133,11 @@ private:
|
||||
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");
|
||||
|
||||
float priority = 1.0f;
|
||||
vk::DeviceQueueCreateInfo queue_info{};
|
||||
@@ -11067,6 +11154,7 @@ private:
|
||||
device_features12.sType =
|
||||
vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
device_features12.timelineSemaphore = true;
|
||||
device_features12.bufferDeviceAddress = true;
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR barycentric{};
|
||||
barycentric.sType = vk::StructureType::ePhysicalDeviceFragmentShaderBarycentricFeaturesKHR;
|
||||
barycentric.pNext = &device_features12;
|
||||
@@ -11081,6 +11169,7 @@ private:
|
||||
vk::PhysicalDeviceFeatures device_features{};
|
||||
device_features.shaderStorageImageWriteWithoutFormat = true;
|
||||
device_features.sampleRateShading = true;
|
||||
device_features.shaderInt64 = true;
|
||||
device_info.pEnabledFeatures = &device_features;
|
||||
constexpr const char *device_extensions[] = {
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
@@ -11105,6 +11194,8 @@ private:
|
||||
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);
|
||||
@@ -11276,6 +11367,53 @@ private:
|
||||
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) {
|
||||
@@ -11349,6 +11487,8 @@ private:
|
||||
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;
|
||||
};
|
||||
@@ -11392,6 +11532,10 @@ void CompareGraphicsWords(const GraphicsCase &test,
|
||||
|
||||
void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
||||
auto compiled = CompileCase(test);
|
||||
if (!test.expected_compile_error.empty()) {
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
return;
|
||||
}
|
||||
if (test.image_descriptor_swizzle != DstSel(4, 5, 6, 7)) {
|
||||
Require(test.name, "resource specialization",
|
||||
!compiled.program.info.images.empty() &&
|
||||
@@ -11410,15 +11554,15 @@ void RunCase(VulkanHarness *vulkan, const TestCase &test) {
|
||||
std::printf("[compute] %-32s ok\n", test.name);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
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;
|
||||
@@ -17341,16 +17485,16 @@ TestCase FlatLoadVariants() {
|
||||
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.flat_memory_base = 0;
|
||||
test.address_memory_offsets = {static_cast<u32>(byte_offset)};
|
||||
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, 0);
|
||||
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));
|
||||
@@ -17360,7 +17504,6 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
|
||||
AppendStoreVgpr(&code, 1, 1);
|
||||
AppendEnd(&code);
|
||||
|
||||
constexpr size_t byte_offset = 3;
|
||||
constexpr u32 guest_word = 0x80ff7f01u;
|
||||
std::vector<u32> host_memory(
|
||||
(byte_offset + sizeof(guest_word) + sizeof(u32) - 1u) / sizeof(u32));
|
||||
@@ -17372,8 +17515,7 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
|
||||
test.code = std::move(code);
|
||||
test.initial = std::move(host_memory);
|
||||
test.expected = {0x01u, 0x0000ff7fu};
|
||||
test.flat_memory_base = 0;
|
||||
test.address_memory_offsets = {static_cast<u32>(byte_offset)};
|
||||
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;
|
||||
@@ -17457,8 +17599,7 @@ TestCase FlatVirtualAddressRebasesGuestAllocation() {
|
||||
test.code = std::move(code);
|
||||
test.initial = {0xfeedfaceu, 0xcafebabeu, 0, 0x12345678u};
|
||||
test.expected = {0x12345678u, 0x12345678u, 0};
|
||||
test.flat_memory_base = GuestBase;
|
||||
test.address_memory_offsets = {8, 8, 8};
|
||||
test.bda_mappings = {{GuestBase, 8}};
|
||||
test.opcodes = {O::V_MOV_B32, O::FLAT_LOAD_DWORD, O::BUFFER_STORE_DWORD,
|
||||
O::S_ENDPGM};
|
||||
return test;
|
||||
@@ -17480,10 +17621,9 @@ TestCase GlobalSignedImmediateRebasesBeforeSaddr() {
|
||||
TestCase test;
|
||||
test.name = "GlobalSignedImmediateRebasesBeforeSaddr";
|
||||
test.code = std::move(code);
|
||||
test.initial = {0xfeedfaceu, 0xcafebabeu, 0x11111111u, 0x22222222u,
|
||||
0x12345678u};
|
||||
test.initial = {0xfeedfaceu, 0xcafebabeu, 0x11111111u, 0x12345678u};
|
||||
test.expected = {0x12345678u};
|
||||
test.address_memory_offsets = {8};
|
||||
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;
|
||||
@@ -17517,7 +17657,7 @@ TestCase FlatSegmentIgnoresSaddrAndMasksOffsetMsb() {
|
||||
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.flat_memory_base = 0;
|
||||
test.bda_mappings = {{0, 0}};
|
||||
return test;
|
||||
}
|
||||
|
||||
@@ -17596,7 +17736,8 @@ TestCase FlatStoreVariants() {
|
||||
{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}};
|
||||
test.flat_memory_base = 0;
|
||||
test.expected_compile_error =
|
||||
"writable FLAT/GLOBAL addresses require GPU ownership tracking";
|
||||
return test;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,12 +73,7 @@ UnbasedFlatProgram() {
|
||||
program.srt_plan_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
AddValueBlock(program);
|
||||
AddressResource address;
|
||||
address.source = UINT32_MAX;
|
||||
address.first_use_pc = 0x80;
|
||||
address.kind = ResourceKind::Flat;
|
||||
address.unbased = true;
|
||||
program.info.addresses.push_back(address);
|
||||
program.info.uses_dma = true;
|
||||
return std::make_shared<const Program>(std::move(program));
|
||||
}
|
||||
|
||||
@@ -102,7 +97,7 @@ void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
}
|
||||
|
||||
void TestUnbasedFlatCacheHitFailsClosed() {
|
||||
void TestUnbasedFlatCacheHitMaterializes() {
|
||||
using namespace Libs::Graphics;
|
||||
auto cached_program = UnbasedFlatProgram();
|
||||
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
|
||||
@@ -110,12 +105,9 @@ void TestUnbasedFlatCacheHitFailsClosed() {
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
ShaderStageRuntime stage{prior_program, prior_resources};
|
||||
std::string error;
|
||||
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error) &&
|
||||
error.find("requires runtime guest-address translation") !=
|
||||
std::string::npos,
|
||||
"unbased FLAT cache hit did not fail without a runtime translator");
|
||||
Check(stage.program == prior_program && stage.resources == prior_resources,
|
||||
"unbased FLAT cache rejection replaced the prior stage snapshot");
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
|
||||
Check(stage.program == cached_program && stage.resources != prior_resources,
|
||||
"unbased FLAT cache hit did not publish its BDA stage snapshot");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -130,7 +122,7 @@ void DbgExit(int) { std::abort(); }
|
||||
|
||||
int main() {
|
||||
TestMappedSrtUsesDirectReaderByDefault();
|
||||
TestUnbasedFlatCacheHitFailsClosed();
|
||||
TestUnbasedFlatCacheHitMaterializes();
|
||||
std::puts("ShaderStageRuntimeTests: all cases passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+48
-61
@@ -4319,14 +4319,14 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
|
||||
error.c_str());
|
||||
const auto *address_binding = ShaderRecompiler::IR::FindBinding(
|
||||
raw.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory);
|
||||
Check(raw.program.info.addresses.size() == 1u &&
|
||||
raw.program.info.addresses[0].kind ==
|
||||
ShaderRecompiler::IR::ResourceKind::ScalarAddress &&
|
||||
raw.program.info.addresses[0].read &&
|
||||
!raw.program.info.addresses[0].written &&
|
||||
raw.program.info.buffers.empty() && address_binding != nullptr &&
|
||||
address_binding->resources == std::vector<uint32_t>{0u} &&
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable);
|
||||
const auto *fault_binding = ShaderRecompiler::IR::FindBinding(
|
||||
raw.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer);
|
||||
Check(raw.program.info.uses_dma && raw.program.info.buffers.empty() &&
|
||||
address_binding != nullptr && fault_binding != nullptr &&
|
||||
address_binding->resources.empty() &&
|
||||
fault_binding->resources.empty() &&
|
||||
ShaderRecompiler::IR::FindBinding(
|
||||
raw.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::Buffers) ==
|
||||
@@ -4335,17 +4335,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
|
||||
raw.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
|
||||
nullptr &&
|
||||
raw.program.bindings.memory_offset_count == 1u,
|
||||
"raw scalar load did not use only the address-memory domain");
|
||||
raw.program.bindings.memory_offset_count == 0u,
|
||||
"raw scalar load did not use only the DMA domain");
|
||||
Check(count_live_memory_ops(
|
||||
raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32,
|
||||
ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 1u,
|
||||
"raw scalar load did not remain a live typed address operation");
|
||||
Check(raw.resources.addresses.size() == 1u &&
|
||||
raw.resources.addresses[0].guest_base == 0x1000u &&
|
||||
raw.resources.addresses[0].binding_base == 0x1000u &&
|
||||
raw.program.info.addresses[0].specialized_base == 0u,
|
||||
"raw scalar address specialization was incorrect");
|
||||
Check(SpirvContainsOpcode(raw.spirv, 199),
|
||||
"raw scalar SOFFSET alignment was not emitted");
|
||||
CheckSpirvBinaryValidates(raw.spirv);
|
||||
@@ -4377,12 +4372,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
|
||||
Check(buffer.program.info.buffers.size() == 1u &&
|
||||
buffer.program.info.buffers[0].scalar &&
|
||||
buffer.program.info.addresses.empty() &&
|
||||
!buffer.program.info.uses_dma &&
|
||||
buffer_binding != nullptr &&
|
||||
buffer_binding->resources == std::vector<uint32_t>{0u} &&
|
||||
ShaderRecompiler::IR::FindBinding(
|
||||
buffer.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory) ==
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) ==
|
||||
nullptr &&
|
||||
ShaderRecompiler::IR::FindBinding(
|
||||
buffer.program.bindings,
|
||||
@@ -6201,7 +6196,6 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
options.flat_memory_base = 0;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
@@ -6224,23 +6218,32 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerUnbasedFlatRequiresTranslator() {
|
||||
void TestNewShaderRecompilerUnbasedFlatUsesBda() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeFlat0(0x0c, 0, 0),
|
||||
EncodeFlat1(0, 0x7d, 0, 1),
|
||||
EncodeFlat0(0x1c, 0, 0),
|
||||
EncodeFlat1(0, 0x7d, 0, 2),
|
||||
0xbf810000u,
|
||||
EncodeExp0(0x00, 0x1),
|
||||
EncodeExp1(0, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
|
||||
Common::ContainsStr(error,
|
||||
"requires runtime guest-address translation"),
|
||||
"unbased FLAT compiled without an explicit translator");
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
|
||||
error.c_str());
|
||||
Check(result.program.info.uses_dma &&
|
||||
ShaderRecompiler::IR::FindBinding(
|
||||
result.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) !=
|
||||
nullptr &&
|
||||
ShaderRecompiler::IR::FindBinding(
|
||||
result.program.bindings,
|
||||
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer) !=
|
||||
nullptr,
|
||||
"unbased FLAT did not compile through BDA");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerFlatSignedLoadTranslation() {
|
||||
@@ -6256,7 +6259,6 @@ void TestNewShaderRecompilerFlatSignedLoadTranslation() {
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
options.flat_memory_base = 0;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
@@ -6311,7 +6313,6 @@ void TestNewShaderRecompilerFlatStoreTranslation() {
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.dump_ir = true;
|
||||
options.flat_memory_base = 0;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
@@ -11966,49 +11967,40 @@ void TestNewShaderRecompilerUnsupportedMemoryDecode() {
|
||||
"MTBUF", "opcode=0x08");
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerFlatUserPointerProvenance() {
|
||||
void TestNewShaderRecompilerFlatUserPointerUsesDma() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeVop2(0x25, 0, 0, 2), // v_add_nc_u32 v0, s0, v2
|
||||
EncodeVop1(0x01, 1, 1), // v_mov_b32 v1, s1
|
||||
EncodeFlat0(0x1c, 0, 0),
|
||||
EncodeFlat1(0, 0x7d, 3, 0), // flat_store_dword v3, v[0:1]
|
||||
0xbf810000u,
|
||||
EncodeFlat0(0x0c, 0, 0),
|
||||
EncodeFlat1(3, 0x7d, 0, 0), // flat_load_dword v3, v[0:1]
|
||||
EncodeExp0(0x00, 0x1),
|
||||
EncodeExp1(3, 0, 0, 0),
|
||||
EncodeSopp(0x01),
|
||||
};
|
||||
const uint32_t user_data[] = {0x34567000u, 0x00000012u};
|
||||
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
auto options = MakeCompileOptions(ShaderType::Pixel);
|
||||
options.user_data = user_data;
|
||||
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
|
||||
options.flat_memory_base = 0;
|
||||
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
const bool compiled =
|
||||
ShaderRecompiler::TryRecompile(shader, options, result, &error);
|
||||
Check(compiled, error.c_str());
|
||||
Check(result.program.info.addresses.size() == 1 &&
|
||||
!result.program.info.addresses[0].unbased &&
|
||||
result.program.info.addresses[0].source <
|
||||
result.program.descriptor_sources.size(),
|
||||
"FLAT user pointer provenance was not attached");
|
||||
Check(result.resources.addresses.size() == 1 &&
|
||||
result.resources.addresses[0].guest_base == 0x0000001234567000ull &&
|
||||
result.resources.addresses[0].binding_base ==
|
||||
0x0000001234560000ull &&
|
||||
result.program.info.addresses[0].specialized_base ==
|
||||
0x0000001234560000ull,
|
||||
"FLAT user pointer did not materialize its guest base");
|
||||
Check(result.program.info.uses_dma,
|
||||
"FLAT user pointer did not enable DMA");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
|
||||
void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
|
||||
const uint32_t segmented_shader[] = {
|
||||
EncodeVop2(0x25, 0, 0, 2),
|
||||
EncodeVop1(0x01, 1, 1),
|
||||
EncodeFlat0(0x1c, 2, 0),
|
||||
EncodeFlat1(0, 0x7d, 3, 0), // global_store_dword v3, v[0:1]
|
||||
EncodeFlat0(0x0c, 2, 0),
|
||||
EncodeFlat1(3, 0x7d, 0, 0), // global_load_dword v3, v[0:1]
|
||||
EncodeFlat0(0x1c, 1, 0),
|
||||
EncodeFlat1(0, 0x7d, 4, 0), // scratch_store_dword v4, v[0:1]
|
||||
EncodeFlat1(0, 0x7d, 3, 0), // scratch_store_dword v3, v[0:1]
|
||||
0xbf810000u,
|
||||
};
|
||||
const uint32_t user_data[] = {0x34567000u, 0u, 0x1000u};
|
||||
@@ -12016,19 +12008,14 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
|
||||
auto options = MakeCompileOptions(ShaderType::Compute);
|
||||
options.user_data = user_data;
|
||||
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
|
||||
options.flat_memory_base = 0;
|
||||
options.scratch_dwords = 1;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
const bool compiled =
|
||||
ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
|
||||
Check(compiled, error.c_str());
|
||||
Check(result.program.info.addresses.size() == 1 &&
|
||||
result.program.info.addresses[0].kind ==
|
||||
ShaderRecompiler::IR::ResourceKind::Global &&
|
||||
result.program.info.addresses[0].unbased &&
|
||||
result.program.info.addresses[0].source == UINT32_MAX,
|
||||
"GLOBAL null-SADDR did not remain an unbased guest address");
|
||||
Check(result.program.info.uses_dma,
|
||||
"GLOBAL null-SADDR did not enable DMA");
|
||||
Check(Common::ContainsStr(result.ir_dump, "GetScratchResource") &&
|
||||
result.program.scratch_dwords == 1,
|
||||
"SCRATCH incorrectly entered guest address tracking");
|
||||
@@ -12324,9 +12311,9 @@ int main() {
|
||||
TestNewShaderRecompilerRejectsDppOn64BitCompares();
|
||||
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
|
||||
TestPsInputCountRegisterDecode();
|
||||
TestNewShaderRecompilerUnbasedFlatRequiresTranslator();
|
||||
TestNewShaderRecompilerFlatUserPointerProvenance();
|
||||
TestNewShaderRecompilerFlatAddressProvenanceBoundaries();
|
||||
TestNewShaderRecompilerUnbasedFlatUsesBda();
|
||||
TestNewShaderRecompilerFlatUserPointerUsesDma();
|
||||
TestNewShaderRecompilerFlatAddressDomainsUseDma();
|
||||
TestNewShaderRecompilerCfgStraightLine();
|
||||
TestNewShaderRecompilerCfgIfElse();
|
||||
TestNewShaderRecompilerCfgConsecutiveNativePhis();
|
||||
|
||||
Reference in New Issue
Block a user