mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
format src and tests with clang-format
This commit is contained in:
@@ -175,9 +175,9 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
|
||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||
const auto* mc = uc->uc_mcontext;
|
||||
const auto& ss = mc->__ss;
|
||||
auto* uc = static_cast<ucontext_t*>(uctx);
|
||||
const auto* mc = uc->uc_mcontext;
|
||||
const auto& ss = mc->__ss;
|
||||
|
||||
ExceptionInfo info {};
|
||||
info.exception_address = ss.__rip;
|
||||
@@ -214,7 +214,7 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
const bool resolved = handler(info);
|
||||
const bool resolved = handler(info);
|
||||
g_in_exception_filter = false;
|
||||
|
||||
if (resolved) {
|
||||
@@ -255,8 +255,8 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ
|
||||
info.native_context = context;
|
||||
|
||||
if (signal_number == SIGSEGV || signal_number == SIGBUS) {
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
const auto error_code = static_cast<uint64_t>(gregs[REG_ERR]);
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
const auto error_code = static_cast<uint64_t>(gregs[REG_ERR]);
|
||||
if ((error_code & PAGE_FAULT_ERROR_INSTRUCTION) != 0) {
|
||||
info.access_violation_type = AccessViolationType::Execute;
|
||||
} else if ((error_code & PAGE_FAULT_ERROR_WRITE) != 0) {
|
||||
|
||||
@@ -19,10 +19,10 @@ class LeastRecentlyUsedCache {
|
||||
|
||||
public:
|
||||
[[nodiscard]] size_t Insert(Object object, Tick tick) {
|
||||
const auto id = Build();
|
||||
const auto id = Build();
|
||||
auto& item = m_items[id];
|
||||
item.object = std::move(object);
|
||||
item.tick = tick;
|
||||
item.object = std::move(object);
|
||||
item.tick = tick;
|
||||
Attach(item);
|
||||
return id;
|
||||
}
|
||||
@@ -49,8 +49,7 @@ public:
|
||||
|
||||
template <typename Function>
|
||||
void ForEachItemBelow(Tick tick, Function&& function) {
|
||||
constexpr bool ReturnsBool =
|
||||
std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
|
||||
constexpr bool ReturnsBool = std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
|
||||
for (auto* item = m_first; item != nullptr;) {
|
||||
if (item->tick > tick) {
|
||||
return;
|
||||
@@ -87,10 +86,10 @@ private:
|
||||
m_last = &item;
|
||||
return;
|
||||
}
|
||||
item.prev = m_last;
|
||||
item.prev = m_last;
|
||||
m_last->next = &item;
|
||||
item.next = nullptr;
|
||||
m_last = &item;
|
||||
item.next = nullptr;
|
||||
m_last = &item;
|
||||
}
|
||||
|
||||
void Detach(Item& item) {
|
||||
|
||||
@@ -31,10 +31,9 @@ static bool OnOwnStack() {
|
||||
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
|
||||
return false;
|
||||
}
|
||||
void* base = nullptr;
|
||||
size_t size = 0;
|
||||
const bool ok =
|
||||
pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
||||
void* base = nullptr;
|
||||
size_t size = 0;
|
||||
const bool ok = pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
|
||||
pthread_attr_destroy(&attr);
|
||||
if (!ok) {
|
||||
return false;
|
||||
|
||||
@@ -172,8 +172,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
@@ -218,8 +217,7 @@ sys_file_t* SysFileCreate() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
@@ -241,7 +239,7 @@ sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
|
||||
@@ -136,8 +136,8 @@ static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags)
|
||||
break;
|
||||
}
|
||||
const auto hint = (top - step) & ~(LOW_ARENA_GRAIN - 1);
|
||||
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect,
|
||||
flags | MAP_FIXED_NOREPLACE, -1, 0); // NOLINT
|
||||
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect, flags | MAP_FIXED_NOREPLACE,
|
||||
-1, 0); // NOLINT
|
||||
if (ptr != MAP_FAILED) {
|
||||
return ptr;
|
||||
}
|
||||
@@ -161,8 +161,8 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
@@ -194,8 +194,8 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = map_anonymous(addr, size + alignment, protect,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ptr =
|
||||
map_anonymous(addr, size + alignment, protect, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
#if defined(__APPLE__)
|
||||
@@ -251,8 +251,8 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
@@ -266,9 +266,9 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
|
||||
// the first mapped region at or above `region_addr`; if it begins before the end of the
|
||||
// requested range, the range overlaps an existing mapping.
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
|
||||
mach_vm_address_t region_addr = query_addr;
|
||||
mach_vm_size_t region_size = 0;
|
||||
auto query_addr = reinterpret_cast<mach_vm_address_t>(ptr);
|
||||
mach_vm_address_t region_addr = query_addr;
|
||||
mach_vm_size_t region_size = 0;
|
||||
vm_region_basic_info_data_64_t info {};
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name = MACH_PORT_NULL;
|
||||
@@ -337,8 +337,8 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
record_alloc(ret_addr, size);
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <cerrno>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ template <typename Result, typename... Args>
|
||||
class UniqueFunction {
|
||||
class CallableBase {
|
||||
public:
|
||||
virtual ~CallableBase() = default;
|
||||
virtual ~CallableBase() = default;
|
||||
virtual Result Invoke(Args&&... args) = 0;
|
||||
};
|
||||
|
||||
@@ -20,9 +20,7 @@ class UniqueFunction {
|
||||
public:
|
||||
explicit Callable(Function function): m_function(std::move(function)) {}
|
||||
|
||||
Result Invoke(Args&&... args) override {
|
||||
return m_function(std::forward<Args>(args)...);
|
||||
}
|
||||
Result Invoke(Args&&... args) override { return m_function(std::forward<Args>(args)...); }
|
||||
|
||||
private:
|
||||
Function m_function;
|
||||
|
||||
@@ -158,7 +158,7 @@ private:
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
@@ -170,9 +170,9 @@ private:
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
bool m_readback_active = false;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
@@ -962,9 +962,8 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
m_index_type_and_size, index_count, index_addr,
|
||||
flags, 1, m_num_instances);
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
|
||||
index_count, index_addr, flags, 1, m_num_instances);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
@@ -1190,8 +1189,8 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
}
|
||||
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(
|
||||
m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode);
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
}
|
||||
|
||||
constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
|
||||
@@ -1237,16 +1236,16 @@ void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t first_vertex, uint32_t first_instance) {
|
||||
CheckBuffer();
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawAuto(
|
||||
m_submit_id, CurrentBuffer(), index_count, flags, render_target_slice_offset,
|
||||
instance_count, first_vertex, first_instance);
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), index_count, flags,
|
||||
render_target_slice_offset, instance_count,
|
||||
first_vertex, first_instance);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
static_cast<int>(display_buffer_index));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -1317,8 +1316,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst,
|
||||
value & 0xffffu, value >> 16u);
|
||||
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
return;
|
||||
@@ -1486,8 +1485,7 @@ void CommandProcessor::EmitGlobalBarrier() {
|
||||
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
|
||||
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
barrier.dstAccessMask =
|
||||
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
|
||||
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.memoryBarrierCount = 1;
|
||||
|
||||
@@ -65,41 +65,41 @@ struct TileVolumeLayout {
|
||||
};
|
||||
|
||||
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
TileBlockLayout& layout);
|
||||
TileBlockLayout& layout);
|
||||
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t& byte_offset);
|
||||
uint32_t& byte_offset);
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t& byte_offset);
|
||||
uint32_t& byte_offset);
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t block_z, uint32_t& byte_offset);
|
||||
uint32_t block_z, uint32_t& byte_offset);
|
||||
bool TileIsStandard256BTextureSupported(uint32_t format);
|
||||
bool TileIsStandard4KBTextureSupported(uint32_t format);
|
||||
bool TileIsStandard64KBTextureSupported(uint32_t format);
|
||||
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
|
||||
|
||||
bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size);
|
||||
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size);
|
||||
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t levels, uint32_t tile, TileSizeAlign* total_size,
|
||||
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
|
||||
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
|
||||
TileSizeAlign& total_size);
|
||||
TileSizeAlign& total_size);
|
||||
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -60,19 +60,19 @@ struct VulkanImage {
|
||||
VulkanImage() = default;
|
||||
KYTY_CLASS_NO_COPY(VulkanImage);
|
||||
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageType image_type = vk::ImageType::e2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
vk::ImageUsageFlags usage = {};
|
||||
vk::ImageCreateFlags flags = {};
|
||||
vk::Image image = nullptr;
|
||||
VulkanImageState state;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageType image_type = vk::ImageType::e2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
vk::ImageUsageFlags usage = {};
|
||||
vk::ImageCreateFlags flags = {};
|
||||
vk::Image image = nullptr;
|
||||
VulkanImageState state;
|
||||
std::vector<VulkanImageState> subresource_states;
|
||||
Graphics::VulkanMemory memory;
|
||||
Graphics::VulkanMemory memory;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
|
||||
@@ -30,7 +30,7 @@ bool IsAccessible(DWORD protect, HostMemoryAccess access) {
|
||||
} // namespace
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t& accessible_size) {
|
||||
uint64_t& accessible_size) {
|
||||
accessible_size = 0;
|
||||
if (addr == 0 || requested_size == 0) {
|
||||
return false;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Libs::Graphics {
|
||||
enum class HostMemoryAccess { Read, Mapped };
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t& accessible_size);
|
||||
uint64_t& accessible_size);
|
||||
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t& readable_size);
|
||||
bool HostMemoryIsReadable(uint64_t addr);
|
||||
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size);
|
||||
|
||||
+10
-12
@@ -135,8 +135,8 @@ void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
|
||||
void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation,
|
||||
offset, size);
|
||||
const auto result =
|
||||
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
@@ -144,8 +144,8 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
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) {
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64
|
||||
" size=0x%016" PRIx64 " capacity=0x%016" PRIx64 "\n",
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
" capacity=0x%016" PRIx64 "\n",
|
||||
static_cast<const void*>(Handle()), offset, size, m_size);
|
||||
}
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
@@ -175,10 +175,9 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
|
||||
command.EndRendering();
|
||||
const vk::BufferMemoryBarrier before[] = {
|
||||
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
|
||||
Barrier(destination_offset, size, destination_before,
|
||||
vk::AccessFlagBits::eTransferWrite),
|
||||
Barrier(destination_offset, size, destination_before, vk::AccessFlagBits::eTransferWrite),
|
||||
};
|
||||
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
|
||||
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
|
||||
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
||||
if (static_cast<bool>((source_before | destination_before) & host_access)) {
|
||||
before_stage |= vk::PipelineStageFlagBits::eHost;
|
||||
@@ -214,9 +213,8 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
|
||||
0, nullptr, 1, &before, 0, nullptr);
|
||||
native.fillBuffer(Handle(), offset, size, value);
|
||||
const auto after =
|
||||
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
const auto after = Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
|
||||
@@ -250,8 +248,8 @@ std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignmen
|
||||
if (Mapped().empty()) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
uint64_t mapped_size = size;
|
||||
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
|
||||
uint64_t mapped_size = size;
|
||||
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
|
||||
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
+14
-15
@@ -54,16 +54,15 @@ public:
|
||||
[[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 CopyFrom(
|
||||
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
||||
uint64_t destination_offset, uint64_t size,
|
||||
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_before =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags source_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_after =
|
||||
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
||||
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,
|
||||
vk::AccessFlags destination_before = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags source_after = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite,
|
||||
vk::AccessFlags destination_after = vk::AccessFlagBits::eMemoryRead |
|
||||
vk::AccessFlagBits::eMemoryWrite);
|
||||
void Fill(uint64_t offset, uint64_t size, uint32_t value);
|
||||
|
||||
protected:
|
||||
@@ -107,13 +106,13 @@ private:
|
||||
uint64_t upper_bound = 0;
|
||||
};
|
||||
|
||||
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
|
||||
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
|
||||
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
||||
uint64_t& alignment);
|
||||
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
std::optional<size_t> invalidation_mark,
|
||||
uint64_t requested_upper_bound, bool allow_wait,
|
||||
size_t& wait_cursor, uint64_t& wait_bound);
|
||||
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
std::optional<size_t> invalidation_mark,
|
||||
uint64_t requested_upper_bound, bool allow_wait,
|
||||
size_t& wait_cursor, uint64_t& wait_bound);
|
||||
|
||||
uint64_t m_offset = 0;
|
||||
uint64_t m_mapped_size = 0;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -44,13 +44,13 @@ CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
|
||||
allocate.commandPool = m_pool;
|
||||
allocate.level = vk::CommandBufferLevel::ePrimary;
|
||||
allocate.commandBufferCount = 1;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
vk::CommandBuffer buffer = nullptr;
|
||||
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
|
||||
|
||||
vk::FenceCreateInfo fence_create {};
|
||||
fence_create.sType = vk::StructureType::eFenceCreateInfo;
|
||||
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
|
||||
vk::Fence fence = nullptr;
|
||||
vk::Fence fence = nullptr;
|
||||
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
|
||||
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
|
||||
EXIT("failed to create command-buffer fence\n");
|
||||
@@ -70,9 +70,9 @@ CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
|
||||
Create(graphics);
|
||||
}
|
||||
EXIT_IF(m_graphics != &graphics);
|
||||
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
|
||||
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
|
||||
slot->busy = true;
|
||||
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
|
||||
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
|
||||
slot->busy = true;
|
||||
slot->Reset();
|
||||
return slot;
|
||||
}
|
||||
@@ -331,8 +331,7 @@ void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
|
||||
EXIT_IF(g_deferred_callback_scheduler == this);
|
||||
std::unique_lock lock(m_operation_mutex);
|
||||
m_operation_available.wait(lock, [this, tick] {
|
||||
const bool active_before_or_at =
|
||||
m_priority_active && m_priority_active_tick <= tick;
|
||||
const bool active_before_or_at = m_priority_active && m_priority_active_tick <= tick;
|
||||
const bool queued_before_or_at =
|
||||
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
|
||||
return !active_before_or_at && !queued_before_or_at;
|
||||
|
||||
@@ -47,21 +47,21 @@ public:
|
||||
void FinishCurrent();
|
||||
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
|
||||
// the priority runner cannot join itself.
|
||||
void Shutdown();
|
||||
void Wait(uint64_t tick);
|
||||
void PopPendingOperations();
|
||||
void DrainPriorityOperations();
|
||||
void WaitPriorityOperations(uint64_t tick);
|
||||
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
void Shutdown();
|
||||
void Wait(uint64_t tick);
|
||||
void PopPendingOperations();
|
||||
void DrainPriorityOperations();
|
||||
void WaitPriorityOperations(uint64_t tick);
|
||||
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
|
||||
void CheckActive() const;
|
||||
RenderCommandBuffer& Current() const;
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
|
||||
void CheckActive() const;
|
||||
RenderCommandBuffer& Current() const;
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick);
|
||||
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
|
||||
|
||||
private:
|
||||
@@ -91,11 +91,11 @@ private:
|
||||
uint64_t tick = 0;
|
||||
};
|
||||
|
||||
void BindCurrent() const;
|
||||
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
||||
void BeginNext();
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
void BindCurrent() const;
|
||||
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
|
||||
void BeginNext();
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
|
||||
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
|
||||
|
||||
@@ -109,14 +109,14 @@ private:
|
||||
std::mutex m_operation_mutex;
|
||||
std::condition_variable m_operation_available;
|
||||
std::jthread m_priority_thread;
|
||||
bool m_priority_active = false;
|
||||
bool m_priority_active = false;
|
||||
uint64_t m_priority_active_tick = 0;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
int m_current = -1;
|
||||
bool m_recording = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
int m_current = -1;
|
||||
bool m_recording = false;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
std::atomic<uint64_t> m_submit_sequence = 0;
|
||||
|
||||
friend class CommandBuffer;
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
@@ -270,30 +270,30 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
colors[i].imageView = attachment.image_view;
|
||||
colors[i].imageLayout = attachment.image_layout;
|
||||
colors[i].loadOp = attachment.is_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colors[i].clearValue.color.uint32 = attachment.clear_value;
|
||||
colors[i].loadOp =
|
||||
attachment.is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
colors[i].clearValue.color.uint32 = attachment.clear_value;
|
||||
}
|
||||
|
||||
const auto& depth_stencil = state.depth_stencil_attachment;
|
||||
const auto& depth_stencil = state.depth_stencil_attachment;
|
||||
vk::RenderingAttachmentInfo depth {};
|
||||
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth.imageView = depth_stencil.image_view;
|
||||
depth.imageLayout = depth_stencil.image_layout;
|
||||
depth.loadOp = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
depth.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth.loadOp =
|
||||
depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
depth.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
|
||||
|
||||
vk::RenderingAttachmentInfo stencil {};
|
||||
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
stencil.imageView = depth_stencil.image_view;
|
||||
stencil.imageLayout = depth_stencil.image_layout;
|
||||
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
|
||||
: vk::AttachmentLoadOp::eLoad;
|
||||
stencil.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
stencil.loadOp =
|
||||
depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
|
||||
stencil.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -182,8 +182,8 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
|
||||
command);
|
||||
destination.Transit(ColorToMsDepthLayout,
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, command);
|
||||
destination.Transit(ColorToMsDepthLayout, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {},
|
||||
command);
|
||||
|
||||
vk::RenderingAttachmentInfo depth_attachment {};
|
||||
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
|
||||
@@ -19,10 +19,9 @@ struct GuestRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE &&
|
||||
size <= TRACKER_ADDRESS_SIZE - address;
|
||||
[[nodiscard]] constexpr bool Empty() const noexcept { return address == 0 || size == 0; }
|
||||
[[nodiscard]] constexpr bool Valid() const noexcept {
|
||||
return !Empty() && address < TRACKER_ADDRESS_SIZE && size <= TRACKER_ADDRESS_SIZE - address;
|
||||
}
|
||||
[[nodiscard]] constexpr uint64_t End() const noexcept { return address + size; }
|
||||
auto operator<=>(const GuestRange&) const = default;
|
||||
@@ -47,10 +46,10 @@ struct ImageSubresources {
|
||||
};
|
||||
|
||||
struct ImageSubresourceRange {
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 1;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 1;
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 1;
|
||||
auto operator<=>(const ImageSubresourceRange&) const = default;
|
||||
};
|
||||
|
||||
@@ -67,10 +66,10 @@ struct ImageInfo {
|
||||
GuestRange stencil;
|
||||
ImageMetadataInfo metadata;
|
||||
uint32_t htile_clear_mask = UINT32_MAX;
|
||||
vk::Format pixel_format = vk::Format::eUndefined;
|
||||
uint32_t guest_format = 0;
|
||||
Prospero::ImageType type = Prospero::ImageType::kColor2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
vk::Format pixel_format = vk::Format::eUndefined;
|
||||
uint32_t guest_format = 0;
|
||||
Prospero::ImageType type = Prospero::ImageType::kColor2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
ImageSubresources resources;
|
||||
uint32_t pitch = 0;
|
||||
uint32_t bytes_per_block = 0;
|
||||
@@ -352,8 +351,7 @@ inline bool ImageInfo::IsDepth() const noexcept {
|
||||
}
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(info.pixel_format);
|
||||
return transfer_bytes == info.bytes_per_block ||
|
||||
(info.bytes_per_block == sizeof(uint16_t) &&
|
||||
transfer_bytes == sizeof(uint32_t));
|
||||
(info.bytes_per_block == sizeof(uint16_t) && transfer_bytes == sizeof(uint32_t));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline VideoOutCompression
|
||||
@@ -470,18 +468,13 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
|
||||
vk::ClearColorValue& clear) {
|
||||
vk::ClearColorValue next {};
|
||||
const auto unorm8 = [](uint32_t value) { return static_cast<float>(value & 0xffu) / 255.0f; };
|
||||
const auto srgb8 = [](uint32_t value) {
|
||||
const auto srgb8 = [](uint32_t value) {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
return encoded <= 0.04045f ? encoded / 12.92f
|
||||
: std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
switch (format) {
|
||||
case vk::Format::eR32Uint:
|
||||
next.uint32[0] = packed;
|
||||
break;
|
||||
case vk::Format::eR32Sint:
|
||||
next.int32[0] = static_cast<int32_t>(packed);
|
||||
break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
next.float32[0] = srgb8(packed);
|
||||
next.float32[1] = srgb8(packed >> 8u);
|
||||
|
||||
@@ -70,15 +70,14 @@ namespace {
|
||||
}
|
||||
case vk::ImageType::e3D:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e3D:
|
||||
return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e3D: return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2D:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
return static_cast<bool>(image.flags &
|
||||
vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1;
|
||||
default: return false;
|
||||
}
|
||||
@@ -325,11 +324,10 @@ bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
|
||||
} // namespace ImageViewOps
|
||||
|
||||
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
const auto& image = backing;
|
||||
const auto& image = backing;
|
||||
auto normalized = view_info;
|
||||
const bool is_storage =
|
||||
static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
normalized.aspect = FullAspectMask(image.format);
|
||||
const bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
|
||||
normalized.aspect = FullAspectMask(image.format);
|
||||
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
|
||||
IsDepthViewFormat(normalized.format)) {
|
||||
normalized.format = image.format;
|
||||
@@ -340,28 +338,26 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
normalized.format = image.format;
|
||||
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
|
||||
}
|
||||
normalized.usage =
|
||||
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
|
||||
IsCompatibleViewFormat(image.format, normalized.format);
|
||||
const bool slice_view = image.image_type == vk::ImageType::e3D &&
|
||||
(normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool slice_view =
|
||||
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool levels_valid = normalized.level_count != 0 &&
|
||||
normalized.base_level < image.mip_levels &&
|
||||
normalized.level_count <= image.mip_levels - normalized.base_level;
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid &&
|
||||
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid && normalized.layer_count != 0 &&
|
||||
normalized.base_layer < view_layers &&
|
||||
normalized.layer_count <= view_layers - normalized.base_layer;
|
||||
const bool mapping_valid =
|
||||
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
|
||||
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
|
||||
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
|
||||
!IsValidViewType(image, normalized) ||
|
||||
!IsValidAspect(image, normalized.aspect)) {
|
||||
!IsValidViewType(image, normalized) || !IsValidAspect(image, normalized.aspect)) {
|
||||
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
|
||||
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
|
||||
static_cast<int>(image.format), static_cast<int>(normalized.format),
|
||||
|
||||
@@ -397,10 +397,10 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
|
||||
return layout;
|
||||
}
|
||||
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture) {
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels,
|
||||
bool array_texture, bool volume_texture) {
|
||||
uint32_t mip_width = width;
|
||||
uint32_t mip_height = height;
|
||||
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
|
||||
@@ -416,14 +416,13 @@ TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint3
|
||||
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
|
||||
|
||||
for (uint32_t z = 0; z < mip_depth; z++) {
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
const auto slice_offset = z * layout.slice_stride;
|
||||
vk::BufferImageCopy region {};
|
||||
region.bufferOffset =
|
||||
layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
|
||||
array_texture ? z : 0, 1};
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
region.bufferOffset = layout.level_sizes[i].offset + slice_offset;
|
||||
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, array_texture ? z : 0,
|
||||
1};
|
||||
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
|
||||
region.imageExtent = {mip_width, mip_height, 1};
|
||||
const bool linear =
|
||||
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
|
||||
if (linear) {
|
||||
@@ -433,9 +432,8 @@ TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint3
|
||||
const auto align = [](uint32_t value, uint32_t block) {
|
||||
return ((value + block - 1u) / block) * block;
|
||||
};
|
||||
const auto pitch = align(mip_pitch, layout.texel_block);
|
||||
region.bufferRowLength =
|
||||
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
const auto pitch = align(mip_pitch, layout.texel_block);
|
||||
region.bufferRowLength = pitch > align(mip_width, layout.texel_block) ? pitch : 0;
|
||||
}
|
||||
regions.push_back(region);
|
||||
}
|
||||
@@ -480,8 +478,7 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
|
||||
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
|
||||
@@ -522,13 +519,12 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
|
||||
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
|
||||
const auto& region = regions[region_base + z];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
const auto pitch =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
info.linear_offset = region.bufferOffset;
|
||||
@@ -544,20 +540,17 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
return false;
|
||||
}
|
||||
info.linear_slice_stride = linear_stride;
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.depth = copy_depth;
|
||||
info.surface_z = block.block_depth == 1
|
||||
? static_cast<uint32_t>(region.imageOffset.z)
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail_x = tail ? volume.tail_x[level] : 0;
|
||||
info.tail_y = tail ? volume.tail_y[level] : 0;
|
||||
info.tail = tail;
|
||||
info.tiled_width = volume.level_widths[level];
|
||||
info.width =
|
||||
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.depth = copy_depth;
|
||||
info.surface_z =
|
||||
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
|
||||
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail_x = tail ? volume.tail_x[level] : 0;
|
||||
info.tail_y = tail ? volume.tail_y[level] : 0;
|
||||
info.tail = tail;
|
||||
info.tiled_width = volume.level_widths[level];
|
||||
info.tiled_height = volume.level_heights[level];
|
||||
infos.push_back(info);
|
||||
}
|
||||
@@ -581,12 +574,11 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
|
||||
for (uint32_t z = 0; z < level_depth; z++) {
|
||||
const auto& region = regions[region_index++];
|
||||
const auto pitch = region.bufferRowLength != 0
|
||||
? region.bufferRowLength
|
||||
: region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
const auto pitch =
|
||||
region.bufferRowLength != 0 ? region.bufferRowLength : region.imageExtent.width;
|
||||
const auto logical_height = region.bufferImageHeight != 0
|
||||
? region.bufferImageHeight
|
||||
: region.imageExtent.height;
|
||||
GpuTileInfo info {};
|
||||
info.family = block.family;
|
||||
info.bytes_per_element = block.bytes_per_element;
|
||||
@@ -597,16 +589,14 @@ bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
info.tiled_size)) {
|
||||
return false;
|
||||
}
|
||||
info.width = std::max(
|
||||
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max(
|
||||
(logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.width =
|
||||
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
|
||||
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
|
||||
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
|
||||
base_family == TileBlockFamily::Depth64KB
|
||||
? region.imageSubresource.baseArrayLayer
|
||||
: 0;
|
||||
info.pitch =
|
||||
std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
|
||||
info.tail = tail;
|
||||
info.tail_x = tail ? level_size.x : 0;
|
||||
info.tail_y = tail ? level_size.y : 0;
|
||||
|
||||
@@ -32,20 +32,19 @@ struct TextureUploadLayout {
|
||||
TilePaddedSize padded_sizes[16] = {};
|
||||
};
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
vk::Format TextureGetFormat(uint32_t fmt);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
std::vector<vk::BufferImageCopy>
|
||||
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size,
|
||||
const std::vector<vk::BufferImageCopy>& regions,
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile, bool volume_texture,
|
||||
const char* owner);
|
||||
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels,
|
||||
bool array_texture, bool volume_texture);
|
||||
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<vk::BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
|
||||
uint64_t levels, std::vector<GpuTileInfo>& infos);
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
@@ -26,8 +26,8 @@ MasterSemaphore::~MasterSemaphore() {
|
||||
}
|
||||
|
||||
void MasterSemaphore::Refresh() {
|
||||
uint64_t counter = 0;
|
||||
const auto result = m_graphics.device.getSemaphoreCounterValue(m_semaphore, &counter);
|
||||
uint64_t counter = 0;
|
||||
const auto result = m_graphics.device.getSemaphoreCounterValue(m_semaphore, &counter);
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
auto known = m_gpu_tick.load(std::memory_order_acquire);
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
[[nodiscard]] uint64_t KnownGpuTick() const noexcept {
|
||||
return m_gpu_tick.load(std::memory_order_acquire);
|
||||
}
|
||||
[[nodiscard]] bool IsFree(uint64_t tick) const noexcept { return KnownGpuTick() >= tick; }
|
||||
[[nodiscard]] bool IsFree(uint64_t tick) const noexcept { return KnownGpuTick() >= tick; }
|
||||
[[nodiscard]] uint64_t NextTick() noexcept {
|
||||
return m_current_tick.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ private:
|
||||
};
|
||||
|
||||
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
|
||||
void CreatePool();
|
||||
void CreatePool();
|
||||
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
vk::DescriptorSetLayout
|
||||
GetDescriptorSetLayoutInternal(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
|
||||
@@ -36,7 +36,7 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const Image& image);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
|
||||
const Image& image);
|
||||
const Image& image);
|
||||
[[nodiscard]] bool
|
||||
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, const Image& image);
|
||||
|
||||
@@ -88,12 +88,12 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
|
||||
PipelineStaticParameters static_params {};
|
||||
GraphicsPipeline p {};
|
||||
p.ps_shader_id = ps_id;
|
||||
p.vs_shader_id = vs_id;
|
||||
p.ps_shader_id = ps_id;
|
||||
p.vs_shader_id = vs_id;
|
||||
|
||||
static_params.color_count = color_count;
|
||||
PipelineRenderingState rendering {};
|
||||
rendering.color_count = color_count;
|
||||
rendering.color_count = color_count;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
|
||||
@@ -116,8 +116,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, depth.samples);
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
|
||||
depth.samples);
|
||||
}
|
||||
}
|
||||
EXIT_IF(attachment_samples == 0 ||
|
||||
@@ -179,10 +179,10 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
NormalizeStaticParamsForDynamicState(static_params);
|
||||
|
||||
GraphicsPipelineKey key {};
|
||||
key.rendering = rendering;
|
||||
key.vs_shader_id = p.vs_shader_id;
|
||||
key.ps_shader_id = p.ps_shader_id;
|
||||
key.static_params = static_params;
|
||||
key.rendering = rendering;
|
||||
key.vs_shader_id = p.vs_shader_id;
|
||||
key.ps_shader_id = p.ps_shader_id;
|
||||
key.static_params = static_params;
|
||||
|
||||
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
|
||||
return *iter->second;
|
||||
@@ -203,9 +203,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
|
||||
vs_spirv, ps_input_info,
|
||||
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32, ps_active);
|
||||
vs_spirv, ps_input_info, ps_spirv, static_params, vs_id.hash0,
|
||||
vs_id.crc32, ps_id.hash0, ps_id.crc32, ps_active);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
|
||||
ps_id.crc32);
|
||||
|
||||
|
||||
@@ -88,9 +88,9 @@ static_assert(sizeof(PipelineStaticParameters) ==
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
vk::Format depth_format = vk::Format::eUndefined;
|
||||
vk::Format stencil_format = vk::Format::eUndefined;
|
||||
uint32_t color_count = 0;
|
||||
vk::Format depth_format = vk::Format::eUndefined;
|
||||
vk::Format stencil_format = vk::Format::eUndefined;
|
||||
uint32_t color_count = 0;
|
||||
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
@@ -118,11 +118,12 @@ public:
|
||||
ShaderId cs_shader_id;
|
||||
};
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
|
||||
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
|
||||
GraphicsPipeline&
|
||||
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
|
||||
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
|
||||
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool ps_active, std::span<const uint32_t> vs_spirv,
|
||||
std::span<const uint32_t> ps_spirv);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const HW::ComputeShaderInfo& cs_regs,
|
||||
std::span<const uint32_t> cs_spirv);
|
||||
@@ -199,7 +200,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
GraphicContext& m_graphics;
|
||||
DescriptorCache& m_descriptor_cache;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
|
||||
GraphicsPipelineKeyHash>
|
||||
@@ -211,16 +212,13 @@ private:
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
|
||||
uint32_t ps_crc32);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
@@ -385,9 +385,8 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
return vk::BlendOp::eAdd;
|
||||
}
|
||||
|
||||
static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts,
|
||||
uint32_t& set_layouts_num,
|
||||
static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
|
||||
std::span<vk::PushConstantRange> push_constant_info,
|
||||
uint32_t& push_constant_info_num,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
@@ -412,12 +411,11 @@ static void CreateLayout(DescriptorCache& descriptor_cache,
|
||||
}
|
||||
}
|
||||
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics,
|
||||
vk::ShaderStageFlagBits vk_stage,
|
||||
static void ConfigureSubgroupSize(const GraphicContext& graphics, vk::ShaderStageFlagBits vk_stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
|
||||
vk::PipelineShaderStageCreateInfo& stage) {
|
||||
const auto config =
|
||||
const auto config =
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
|
||||
switch (config.mode) {
|
||||
case ShaderSubgroupMode::Natural: return;
|
||||
@@ -456,16 +454,13 @@ static void ConfigureSubgroupSize(const GraphicContext&
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
|
||||
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
|
||||
bool ps_active) {
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, DescriptorCache& descriptor_cache,
|
||||
PipelineCache::GraphicsPipeline& pipeline, const PipelineRenderingState& rendering,
|
||||
const ShaderVertexInputInfo& vs_input_info, std::span<const uint32_t> vs_shader,
|
||||
const ShaderPixelInputInfo* ps_input_info, std::span<const uint32_t> ps_shader,
|
||||
const PipelineStaticParameters& static_params, uint32_t vs_hash0, uint32_t vs_crc32,
|
||||
uint32_t ps_hash0, uint32_t ps_crc32, bool ps_active) {
|
||||
EXIT_IF(ps_active && ps_input_info == nullptr);
|
||||
|
||||
vk::ShaderModule vert_shader_module = nullptr;
|
||||
@@ -511,8 +506,7 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
vert_shader_stage_info.pName = "main";
|
||||
vert_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex,
|
||||
*vs_input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
|
||||
vert_subgroup_size, vert_shader_stage_info);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
@@ -527,8 +521,8 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
|
||||
*ps_input_info->stage.program,
|
||||
frag_subgroup_size, frag_shader_stage_info);
|
||||
*ps_input_info->stage.program, frag_subgroup_size,
|
||||
frag_shader_stage_info);
|
||||
}
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[] = {vert_shader_stage_info,
|
||||
@@ -728,13 +722,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer {};
|
||||
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
|
||||
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
|
||||
#if defined(__APPLE__)
|
||||
rasterizer.pNext = nullptr;
|
||||
rasterizer.pNext = nullptr;
|
||||
#else
|
||||
rasterizer.pNext = &clip_ext;
|
||||
rasterizer.pNext = &clip_ext;
|
||||
#endif
|
||||
rasterizer.flags = {};
|
||||
rasterizer.depthClampEnable = VK_FALSE;
|
||||
@@ -812,13 +806,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
color_write.pColorWriteEnables = color_write_enable;
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending {};
|
||||
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
|
||||
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
|
||||
#if defined(__APPLE__)
|
||||
color_blending.pNext = nullptr;
|
||||
color_blending.pNext = nullptr;
|
||||
#else
|
||||
color_blending.pNext = &color_write;
|
||||
color_blending.pNext = &color_write;
|
||||
#endif
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
@@ -838,15 +832,13 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
|
||||
DescriptorCache::Stage::Vertex);
|
||||
push_constant_info_num, *vs_input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
|
||||
DescriptorCache::Stage::Pixel);
|
||||
push_constant_info_num, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
|
||||
}
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
@@ -923,32 +915,32 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
dynamic_state.dynamicStateCount = dynamic_states_count;
|
||||
dynamic_state.pDynamicStates = dynamic_states;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info {};
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info {};
|
||||
vk::PipelineRenderingCreateInfo rendering_info {};
|
||||
rendering_info.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering_info.colorAttachmentCount = rendering.color_count;
|
||||
rendering_info.pColorAttachmentFormats = rendering.color_formats.data();
|
||||
rendering_info.depthAttachmentFormat = rendering.depth_format;
|
||||
rendering_info.stencilAttachmentFormat = rendering.stencil_format;
|
||||
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
pipeline_info.pNext = &rendering_info;
|
||||
pipeline_info.flags = {};
|
||||
pipeline_info.stageCount = shader_stage_count;
|
||||
pipeline_info.pStages = shader_stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_info.pTessellationState = nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr);
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
pipeline_info.layout = pipeline.pipeline_layout;
|
||||
pipeline_info.renderPass = nullptr;
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
pipeline_info.pNext = &rendering_info;
|
||||
pipeline_info.flags = {};
|
||||
pipeline_info.stageCount = shader_stage_count;
|
||||
pipeline_info.pStages = shader_stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_info.pTessellationState = nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr);
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
pipeline_info.layout = pipeline.pipeline_layout;
|
||||
pipeline_info.renderPass = nullptr;
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
@@ -1012,8 +1004,7 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
comp_shader_stage_info.pName = "main";
|
||||
comp_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
EXIT_IF(!input_info.stage);
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute,
|
||||
*input_info.stage.program,
|
||||
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
|
||||
comp_subgroup_size, comp_shader_stage_info);
|
||||
|
||||
vk::DescriptorSetLayout set_layouts[1] = {};
|
||||
@@ -1024,9 +1015,8 @@ void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descripto
|
||||
|
||||
EXIT_IF(!input_info.stage);
|
||||
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
|
||||
push_constant_info_num,
|
||||
*input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
|
||||
DescriptorCache::Stage::Compute);
|
||||
push_constant_info_num, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
@@ -14,8 +14,7 @@ namespace Libs::Graphics {
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
|
||||
m_sampler_cache(graphics),
|
||||
m_gpu_resources(graphics, m_command_scheduler) {
|
||||
m_sampler_cache(graphics), m_gpu_resources(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -27,7 +26,7 @@ RenderContext::~RenderContext() {
|
||||
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
|
||||
EXIT_IF(m_gpu != nullptr);
|
||||
m_video_out = video_out;
|
||||
m_gpu = std::make_unique<Gpu>(*this);
|
||||
m_gpu = std::make_unique<Gpu>(*this);
|
||||
m_gpu_resources.SetGpu(m_gpu.get());
|
||||
}
|
||||
|
||||
@@ -99,8 +98,7 @@ void RenderContext::TriggerEopEvent(uint32_t context_id) {
|
||||
registration.eq, static_cast<uintptr_t>(registration.id),
|
||||
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF ||
|
||||
result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
if (result == LibKernel::KERNEL_ERROR_EBADF || result == LibKernel::KERNEL_ERROR_ENOENT) {
|
||||
DeleteEopEq(registration.eq, registration.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/gpuResourceManager.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include <memory>
|
||||
@@ -32,10 +32,10 @@ public:
|
||||
~RenderContext();
|
||||
KYTY_CLASS_NO_COPY(RenderContext);
|
||||
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
|
||||
void ShutdownGpu();
|
||||
[[nodiscard]] Gpu& GetGpu() const;
|
||||
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
|
||||
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
|
||||
void ShutdownGpu();
|
||||
[[nodiscard]] Gpu& GetGpu() const;
|
||||
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
|
||||
|
||||
Common::Mutex& GetMutex() { return m_mutex; }
|
||||
@@ -56,18 +56,18 @@ private:
|
||||
struct EopEqRegistration {
|
||||
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
LibKernel::EventQueue::KernelEqueueRef queue;
|
||||
int id = 0;
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
RenderExecutor m_render_executor;
|
||||
CommandScheduler m_command_scheduler;
|
||||
DescriptorCache m_descriptor_cache;
|
||||
PipelineCache m_pipeline_cache;
|
||||
SamplerCache m_sampler_cache;
|
||||
GpuResourceManager m_gpu_resources;
|
||||
std::unique_ptr<Gpu> m_gpu;
|
||||
GraphicContext& m_graphics;
|
||||
Common::Mutex m_mutex;
|
||||
RenderExecutor m_render_executor;
|
||||
CommandScheduler m_command_scheduler;
|
||||
DescriptorCache m_descriptor_cache;
|
||||
PipelineCache m_pipeline_cache;
|
||||
SamplerCache m_sampler_cache;
|
||||
GpuResourceManager m_gpu_resources;
|
||||
std::unique_ptr<Gpu> m_gpu;
|
||||
VideoOut::VideoOutDriver* m_video_out = nullptr;
|
||||
|
||||
Common::Mutex m_eop_mutex;
|
||||
|
||||
@@ -12,13 +12,13 @@ namespace Libs::Graphics {
|
||||
static constexpr uint32_t RENDER_COLOR_ATTACHMENTS_MAX = 8;
|
||||
|
||||
struct RenderAttachment {
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::ImageLayout image_layout = vk::ImageLayout::eUndefined;
|
||||
std::array<uint32_t, 4> clear_value = {};
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::ImageLayout image_layout = vk::ImageLayout::eUndefined;
|
||||
std::array<uint32_t, 4> clear_value = {};
|
||||
bool is_clear = false;
|
||||
bool has_depth = false;
|
||||
bool depth_clear = false;
|
||||
bool has_stencil = false;
|
||||
bool has_depth = false;
|
||||
bool depth_clear = false;
|
||||
bool has_stencil = false;
|
||||
bool stencil_clear = false;
|
||||
|
||||
bool operator==(const RenderAttachment&) const = default;
|
||||
|
||||
@@ -251,9 +251,9 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
|
||||
int64_t flip_arg) {
|
||||
for (;;) {
|
||||
uint64_t request_id = 0;
|
||||
auto& video_out = buffer.GetContext().GetVideoOut();
|
||||
const auto result = video_out.SubmitFlipFromGpu(
|
||||
buffer, handle, index, flip_mode, flip_arg, request_id);
|
||||
auto& video_out = buffer.GetContext().GetVideoOut();
|
||||
const auto result =
|
||||
video_out.SubmitFlipFromGpu(buffer, handle, index, flip_mode, flip_arg, request_id);
|
||||
if (result == OK) {
|
||||
EXIT_IF(request_id == 0);
|
||||
return request_id;
|
||||
|
||||
@@ -122,9 +122,9 @@ uint64_t GraphicContext::GetDeviceMemoryUsage() const {
|
||||
physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
|
||||
uint64_t usage = 0;
|
||||
for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) {
|
||||
const bool device_local = static_cast<bool>(
|
||||
physical_device_memory_properties.memoryHeaps[heap].flags &
|
||||
vk::MemoryHeapFlagBits::eDeviceLocal);
|
||||
const bool device_local =
|
||||
static_cast<bool>(physical_device_memory_properties.memoryHeaps[heap].flags &
|
||||
vk::MemoryHeapFlagBits::eDeviceLocal);
|
||||
if (!discrete || device_local) {
|
||||
usage += budgets[heap].usage;
|
||||
}
|
||||
@@ -144,7 +144,7 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
|
||||
uint64_t local = 0;
|
||||
uint64_t usage = 0;
|
||||
for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) {
|
||||
const auto& properties = physical_device_memory_properties.memoryHeaps[heap];
|
||||
const auto& properties = physical_device_memory_properties.memoryHeaps[heap];
|
||||
const bool device_local =
|
||||
static_cast<bool>(properties.flags & vk::MemoryHeapFlagBits::eDeviceLocal);
|
||||
if (device_local) {
|
||||
@@ -159,9 +159,8 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
|
||||
return budget - std::min<uint64_t>(budget / 8, 1024ull * 1024 * 1024);
|
||||
}
|
||||
constexpr uint64_t system_reserve = 8ull * 1024 * 1024 * 1024;
|
||||
const auto available = budget > usage ? budget - usage : uint64_t {0};
|
||||
return std::max(local,
|
||||
available > system_reserve ? available - system_reserve : uint64_t {0});
|
||||
const auto available = budget > usage ? budget - usage : uint64_t {0};
|
||||
return std::max(local, available > system_reserve ? available - system_reserve : uint64_t {0});
|
||||
}
|
||||
|
||||
void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
|
||||
|
||||
@@ -20,14 +20,14 @@ public:
|
||||
~Presenter();
|
||||
KYTY_CLASS_NO_COPY(Presenter);
|
||||
|
||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
|
||||
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] Frame* PrepareLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
|
||||
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] Frame* PrepareLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||
void Present(Frame& frame, bool reuse = false);
|
||||
void Discard(Frame& frame);
|
||||
void Present(Frame& frame, bool reuse = false);
|
||||
void Discard(Frame& frame);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
||||
@@ -69,8 +69,8 @@ enum class FlipRequestSource { Cpu, GpuEop };
|
||||
struct VideoOutEventState;
|
||||
|
||||
struct VideoOutEventRegistration {
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<VideoOutEventState> state;
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<VideoOutEventState> state;
|
||||
uint64_t generation = 0;
|
||||
VideoOutEventKind kind = VideoOutEventKind::Flip;
|
||||
};
|
||||
@@ -170,13 +170,13 @@ struct BufferAttributeGroup {
|
||||
struct VideoOutConfig {
|
||||
Common::Mutex mutex;
|
||||
Common::CondVar vblank_cond;
|
||||
std::shared_ptr<VideoOutEventState> events = std::make_shared<VideoOutEventState>();
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint64_t generation = 0;
|
||||
bool opened = false;
|
||||
bool closing = false;
|
||||
int flip_rate = 0;
|
||||
std::shared_ptr<VideoOutEventState> events = std::make_shared<VideoOutEventState>();
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint64_t generation = 0;
|
||||
bool opened = false;
|
||||
bool closing = false;
|
||||
int flip_rate = 0;
|
||||
uint64_t output_mode = VIDEO_OUT_OUTPUT_MODE_DEFAULT;
|
||||
float gamma = 1.0f;
|
||||
VideoOutFlipStatus flip_status;
|
||||
@@ -250,8 +250,8 @@ public:
|
||||
VideoOutConfig* Get(int handle, uint64_t& generation);
|
||||
bool IsOpened(int handle);
|
||||
|
||||
void Init(uint32_t width, uint32_t height);
|
||||
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
||||
void Init(uint32_t width, uint32_t height);
|
||||
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
||||
Graphics::RenderContext& Renderer() const noexcept { return m_renderer; }
|
||||
|
||||
void VblankBegin();
|
||||
@@ -259,12 +259,12 @@ public:
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
private:
|
||||
Common::Mutex m_mutex;
|
||||
VideoOutConfig m_video_out_ctx[VIDEO_OUT_NUM_MAX];
|
||||
Common::Mutex m_mutex;
|
||||
VideoOutConfig m_video_out_ctx[VIDEO_OUT_NUM_MAX];
|
||||
Graphics::RenderContext& m_renderer;
|
||||
Graphics::Presenter& m_presenter;
|
||||
FlipQueue m_flip_queue;
|
||||
std::jthread m_present_thread;
|
||||
Graphics::Presenter& m_presenter;
|
||||
FlipQueue m_flip_queue;
|
||||
std::jthread m_present_thread;
|
||||
};
|
||||
|
||||
static std::unique_ptr<VideoOutDriver> g_video_out_driver;
|
||||
@@ -279,7 +279,7 @@ static uintptr_t VideoOutEventId(VideoOutEventKind kind) {
|
||||
}
|
||||
|
||||
static VideoOutEventQueues& VideoOutEventQueuesFor(VideoOutEventState& state,
|
||||
VideoOutEventKind kind) {
|
||||
VideoOutEventKind kind) {
|
||||
switch (kind) {
|
||||
case VideoOutEventKind::Flip: return state.flip;
|
||||
case VideoOutEventKind::Vblank: return state.vblank;
|
||||
@@ -359,9 +359,9 @@ static void TriggerVideoOutEvents(VideoOutConfig& video_out, VideoOutEventKind k
|
||||
if (!registration || registration->generation != video_out.generation) {
|
||||
continue;
|
||||
}
|
||||
const auto result = EventQueue::KernelTriggerEvent(
|
||||
registration->handle, VideoOutEventId(kind), EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
trigger_data);
|
||||
const auto result =
|
||||
EventQueue::KernelTriggerEvent(registration->handle, VideoOutEventId(kind),
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT, trigger_data);
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_EBADF &&
|
||||
result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
@@ -372,9 +372,8 @@ static void DeleteVideoOutEvents(const VideoOutEventQueues& queues, VideoOutEven
|
||||
if (!registration) {
|
||||
continue;
|
||||
}
|
||||
const auto result =
|
||||
EventQueue::KernelDeleteEvent(registration->handle, VideoOutEventId(kind),
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT);
|
||||
const auto result = EventQueue::KernelDeleteEvent(
|
||||
registration->handle, VideoOutEventId(kind), EventQueue::KERNEL_EVFILT_VIDEO_OUT);
|
||||
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_EBADF &&
|
||||
result != LibKernel::KERNEL_ERROR_ENOENT);
|
||||
}
|
||||
@@ -383,7 +382,7 @@ static void DeleteVideoOutEvents(const VideoOutEventQueues& queues, VideoOutEven
|
||||
static int RegisterVideoOutEvent(int handle, EventQueue::KernelEqueue eq, VideoOutEventKind kind,
|
||||
void* udata) {
|
||||
uint64_t generation = 0;
|
||||
auto* video_out = DriverState().Get(handle, generation);
|
||||
auto* video_out = DriverState().Get(handle, generation);
|
||||
if (video_out == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -425,27 +424,25 @@ static int RegisterVideoOutEvent(int handle, EventQueue::KernelEqueue eq, VideoO
|
||||
bool add_queue = false;
|
||||
{
|
||||
Common::LockGuard event_lock(event_state->mutex);
|
||||
const auto existing = std::find_if(queues.begin(), queues.end(), [&](const auto& candidate) {
|
||||
return candidate->handle == eq && candidate->generation == generation;
|
||||
});
|
||||
const auto existing =
|
||||
std::find_if(queues.begin(), queues.end(), [&](const auto& candidate) {
|
||||
return candidate->handle == eq && candidate->generation == generation;
|
||||
});
|
||||
if (existing != queues.end()) {
|
||||
registration = *existing;
|
||||
} else {
|
||||
registration = std::make_shared<VideoOutEventRegistration>(
|
||||
VideoOutEventRegistration {.handle = eq,
|
||||
.state = event_state,
|
||||
.generation = generation,
|
||||
.kind = kind});
|
||||
registration = std::make_shared<VideoOutEventRegistration>(VideoOutEventRegistration {
|
||||
.handle = eq, .state = event_state, .generation = generation, .kind = kind});
|
||||
queues.push_back(registration);
|
||||
add_queue = true;
|
||||
}
|
||||
}
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
const int result = EventQueue::KernelAddEvent(eq, event);
|
||||
const int result = EventQueue::KernelAddEvent(eq, event);
|
||||
if (result != OK && add_queue) {
|
||||
Common::LockGuard event_lock(event_state->mutex);
|
||||
const auto added = std::find(queues.begin(), queues.end(), registration);
|
||||
const auto added = std::find(queues.begin(), queues.end(), registration);
|
||||
if (added != queues.end()) {
|
||||
queues.erase(added);
|
||||
}
|
||||
@@ -455,7 +452,7 @@ static int RegisterVideoOutEvent(int handle, EventQueue::KernelEqueue eq, VideoO
|
||||
|
||||
static int DeleteVideoOutEvent(int handle, EventQueue::KernelEqueue eq, VideoOutEventKind kind) {
|
||||
uint64_t generation = 0;
|
||||
auto* video_out = DriverState().Get(handle, generation);
|
||||
auto* video_out = DriverState().Get(handle, generation);
|
||||
if (video_out == nullptr) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -814,8 +811,8 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
m_presenter.Present(*frame, true);
|
||||
}
|
||||
const auto frame_end = Common::Timer::QueryPerformanceCounter();
|
||||
total_wait += static_cast<int64_t>(period) -
|
||||
static_cast<int64_t>(frame_end - frame_begin);
|
||||
total_wait +=
|
||||
static_cast<int64_t>(period) - static_cast<int64_t>(frame_end - frame_begin);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -841,8 +838,7 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
VblankEnd();
|
||||
|
||||
const auto frame_end = Common::Timer::QueryPerformanceCounter();
|
||||
total_wait += static_cast<int64_t>(period) -
|
||||
static_cast<int64_t>(frame_end - frame_begin);
|
||||
total_wait += static_cast<int64_t>(period) - static_cast<int64_t>(frame_end - frame_begin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,8 +996,8 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
}
|
||||
Graphics::Presenter::Frame* frame = nullptr;
|
||||
if (special) {
|
||||
frame = &m_presenter.PrepareBlankFrame(width, height,
|
||||
index == VIDEO_OUT_BUFFER_INDEX_BLACK, &buffer);
|
||||
frame = &m_presenter.PrepareBlankFrame(width, height, index == VIDEO_OUT_BUFFER_INDEX_BLACK,
|
||||
&buffer);
|
||||
} else {
|
||||
frame = &m_presenter.PrepareFrame(buffer, source_info);
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@ public:
|
||||
~VideoOutDriver();
|
||||
KYTY_CLASS_NO_COPY(VideoOutDriver);
|
||||
|
||||
int SubmitFlipFromGpu(Graphics::CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t& request_id);
|
||||
void PrepareFlip(uint64_t request_id, Graphics::CommandBuffer& buffer);
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot();
|
||||
void WaitFlipDone(int handle, int index);
|
||||
int SubmitFlipFromGpu(Graphics::CommandBuffer& buffer, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t& request_id);
|
||||
void PrepareFlip(uint64_t request_id, Graphics::CommandBuffer& buffer);
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot();
|
||||
void WaitFlipDone(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Libs::Graphics {
|
||||
struct Presenter::Frame {
|
||||
VulkanImage image;
|
||||
std::unique_ptr<CommandBuffer> present_commands;
|
||||
bool busy = false;
|
||||
bool busy = false;
|
||||
bool reusing_last = false;
|
||||
|
||||
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
||||
@@ -155,7 +155,7 @@ public:
|
||||
EXIT("last submitted frame is not available for reuse\n");
|
||||
}
|
||||
m_free.erase(free);
|
||||
m_last_frame = nullptr;
|
||||
m_last_frame = nullptr;
|
||||
frame->busy = true;
|
||||
frame->reusing_last = true;
|
||||
m_mutex.Unlock();
|
||||
@@ -197,30 +197,27 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
WindowContext& m_window;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_available;
|
||||
WindowContext& m_window;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_available;
|
||||
std::vector<std::unique_ptr<Presenter::Frame>> m_frames;
|
||||
std::deque<Presenter::Frame*> m_free;
|
||||
Presenter::Frame* m_last_frame = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
};
|
||||
|
||||
void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
vk::Format format) {
|
||||
void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format) {
|
||||
if (extent.width == 0 || extent.height == 0 || format == vk::Format::eUndefined) {
|
||||
EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height,
|
||||
static_cast<int>(format));
|
||||
}
|
||||
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
|
||||
const auto required = vk::FormatFeatureFlagBits::eBlitSrc |
|
||||
vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferSrc |
|
||||
vk::FormatFeatureFlagBits::eTransferDst;
|
||||
const auto required =
|
||||
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst;
|
||||
if ((features & required) != required) {
|
||||
EXIT("prepared presentation format lacks optimal blit support: format=%d features=0x%x\n",
|
||||
static_cast<int>(format),
|
||||
static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
}
|
||||
|
||||
auto& dst = image;
|
||||
@@ -234,11 +231,11 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
dst.memory = {};
|
||||
}
|
||||
|
||||
dst.extent = {extent.width, extent.height, 1};
|
||||
dst.format = format;
|
||||
dst.layers = 1;
|
||||
dst.mip_levels = 1;
|
||||
dst.state = {};
|
||||
dst.extent = {extent.width, extent.height, 1};
|
||||
dst.format = format;
|
||||
dst.layers = 1;
|
||||
dst.mip_levels = 1;
|
||||
dst.state = {};
|
||||
dst.subresource_states.clear();
|
||||
dst.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
|
||||
@@ -262,13 +259,12 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
|
||||
void Presenter::Frame::Transit(vk::CommandBuffer command, vk::ImageLayout layout,
|
||||
vk::AccessFlags2 access) {
|
||||
const auto stage = access == vk::AccessFlagBits2::eTransferRead ||
|
||||
access == vk::AccessFlagBits2::eTransferWrite
|
||||
? vk::PipelineStageFlagBits2::eTransfer
|
||||
: vk::PipelineStageFlagBits2::eAllCommands;
|
||||
const auto stage = access == vk::AccessFlagBits2::eTransferRead ||
|
||||
access == vk::AccessFlagBits2::eTransferWrite
|
||||
? vk::PipelineStageFlagBits2::eTransfer
|
||||
: vk::PipelineStageFlagBits2::eAllCommands;
|
||||
constexpr auto writes = vk::AccessFlagBits2::eTransferWrite |
|
||||
vk::AccessFlagBits2::eShaderWrite |
|
||||
vk::AccessFlagBits2::eMemoryWrite;
|
||||
vk::AccessFlagBits2::eShaderWrite | vk::AccessFlagBits2::eMemoryWrite;
|
||||
if (image.state.layout == layout && image.state.access_mask == access &&
|
||||
!static_cast<bool>(image.state.access_mask & writes)) {
|
||||
return;
|
||||
@@ -299,35 +295,27 @@ void Presenter::Frame::Transit(vk::CommandBuffer command, vk::ImageLayout layout
|
||||
void Presenter::Frame::CopyFrom(CommandBuffer& command_buffer, Image& source) {
|
||||
command_buffer.EndRendering();
|
||||
auto command = command_buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead, {}, command);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite);
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite);
|
||||
vk::ImageCopy copy {};
|
||||
copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0,
|
||||
source.backing.layers};
|
||||
copy.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, source.backing.layers};
|
||||
copy.dstSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, image.layers};
|
||||
copy.extent = {std::min(source.backing.extent.width, image.extent.width),
|
||||
std::min(source.backing.extent.height, image.extent.height), 1};
|
||||
copy.extent = {std::min(source.backing.extent.width, image.extent.width),
|
||||
std::min(source.backing.extent.height, image.extent.height), 1};
|
||||
EXIT_IF(copy.srcSubresource.layerCount != copy.dstSubresource.layerCount);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
image.image, vk::ImageLayout::eTransferDstOptimal, copy);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, image.image,
|
||||
vk::ImageLayout::eTransferDstOptimal, copy);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
void Presenter::Frame::Clear(CommandBuffer& command_buffer,
|
||||
const vk::ClearColorValue& color) {
|
||||
void Presenter::Frame::Clear(CommandBuffer& command_buffer, const vk::ClearColorValue& color) {
|
||||
command_buffer.EndRendering();
|
||||
auto command = command_buffer.Handle();
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::AccessFlagBits2::eTransferWrite);
|
||||
const vk::ImageSubresourceRange range {
|
||||
vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1,
|
||||
&range);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead);
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite);
|
||||
const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1, &range);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
class Swapchain final {
|
||||
@@ -338,8 +326,8 @@ public:
|
||||
~Swapchain();
|
||||
KYTY_CLASS_NO_COPY(Swapchain);
|
||||
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source);
|
||||
void Submit(CommandBuffer& command);
|
||||
@@ -395,17 +383,17 @@ struct Presenter::Impl {
|
||||
desc.view_info.usage = vk::ImageUsageFlagBits::eTransferSrc;
|
||||
desc.type = TextureCache::BindingType::VideoOut;
|
||||
|
||||
auto& cache = renderer.GetTextureCache();
|
||||
auto& image = cache.GetImage(cache.FindImage(desc));
|
||||
auto& cache = renderer.GetTextureCache();
|
||||
auto& image = cache.GetImage(cache.FindImage(desc));
|
||||
image.usage.video_out = true;
|
||||
return image;
|
||||
}
|
||||
|
||||
RenderContext& renderer;
|
||||
WindowContext& window;
|
||||
Swapchain swapchain;
|
||||
RenderContext& renderer;
|
||||
WindowContext& window;
|
||||
Swapchain swapchain;
|
||||
CommandScheduler present_scheduler;
|
||||
FramePool frames;
|
||||
FramePool frames;
|
||||
};
|
||||
|
||||
void Swapchain::Create() {
|
||||
@@ -441,25 +429,20 @@ void Swapchain::Create() {
|
||||
? vk::CompositeAlphaFlagBitsKHR::eOpaque
|
||||
: vk::CompositeAlphaFlagBitsKHR::eInherit;
|
||||
|
||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm,
|
||||
vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
if (surface.formats.size() != 1 ||
|
||||
surface.formats.front().format != vk::Format::eUndefined) {
|
||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||
if (surface.formats.size() != 1 || surface.formats.front().format != vk::Format::eUndefined) {
|
||||
const auto it = std::find_if(surface.formats.begin(), surface.formats.end(),
|
||||
[](const vk::SurfaceFormatKHR& candidate) {
|
||||
return candidate.format ==
|
||||
vk::Format::eB8G8R8A8Unorm ||
|
||||
candidate.format ==
|
||||
vk::Format::eR8G8B8A8Unorm;
|
||||
return candidate.format == vk::Format::eB8G8R8A8Unorm ||
|
||||
candidate.format == vk::Format::eR8G8B8A8Unorm;
|
||||
});
|
||||
if (it == surface.formats.end()) {
|
||||
EXIT("no supported UNORM swapchain format\n");
|
||||
}
|
||||
format = *it;
|
||||
}
|
||||
m_format = format.format;
|
||||
const auto swapchain_features =
|
||||
graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||
m_format = format.format;
|
||||
const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||
if (!static_cast<bool>(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||
EXIT("swapchain format cannot be a blit destination: format=%d\n",
|
||||
static_cast<int>(m_format));
|
||||
@@ -503,9 +486,8 @@ void Swapchain::Create() {
|
||||
view.subresourceRange.baseMipLevel = 0;
|
||||
view.subresourceRange.layerCount = 1;
|
||||
view.subresourceRange.levelCount = 1;
|
||||
RequireVulkanSuccess(
|
||||
graphics.device.createImageView(&view, nullptr, &m_image_views[i]),
|
||||
"vkCreateImageView");
|
||||
RequireVulkanSuccess(graphics.device.createImageView(&view, nullptr, &m_image_views[i]),
|
||||
"vkCreateImageView");
|
||||
EXIT_IF(m_image_views[i] == nullptr);
|
||||
}
|
||||
|
||||
@@ -600,7 +582,7 @@ void Swapchain::Recreate(bool surface_lost) {
|
||||
|
||||
Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
EXIT_IF(m_handle == nullptr || m_frame_index >= m_image_acquired.size());
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
const auto result = m_window.graphic_ctx.device.acquireNextImageKHR(
|
||||
m_handle, std::numeric_limits<uint64_t>::max(), m_image_acquired[m_frame_index], nullptr,
|
||||
&m_image_index);
|
||||
@@ -683,10 +665,9 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
to_present.subresourceRange.levelCount = 1;
|
||||
to_present.subresourceRange.baseArrayLayer = 0;
|
||||
to_present.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0,
|
||||
nullptr, 0, nullptr, 1, &to_present);
|
||||
vk_command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present);
|
||||
command.End();
|
||||
}
|
||||
|
||||
@@ -700,7 +681,7 @@ void Swapchain::Submit(CommandBuffer& command) {
|
||||
|
||||
Swapchain::Status Swapchain::Present() {
|
||||
EXIT_IF(m_image_index >= m_render_complete.size());
|
||||
const auto ready = m_render_complete[m_image_index];
|
||||
const auto ready = m_render_complete[m_image_index];
|
||||
vk::PresentInfoKHR present {};
|
||||
present.sType = vk::StructureType::ePresentInfoKHR;
|
||||
present.swapchainCount = 1;
|
||||
@@ -738,7 +719,7 @@ Presenter::~Presenter() = default;
|
||||
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
auto* frame = m_impl->frames.Acquire();
|
||||
auto* frame = m_impl->frames.Acquire();
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& image = m_impl->ResolveSurface(info);
|
||||
if (image.backing.format == vk::Format::eUndefined) {
|
||||
@@ -752,14 +733,13 @@ Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo
|
||||
default: break;
|
||||
}
|
||||
frame->Configure(m_impl->window.graphic_ctx,
|
||||
{image.backing.extent.width, image.backing.extent.height},
|
||||
frame_format);
|
||||
{image.backing.extent.width, image.backing.extent.height}, frame_format);
|
||||
frame->CopyFrom(buffer, image);
|
||||
return *frame;
|
||||
}
|
||||
|
||||
Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||
CommandBuffer* producer) {
|
||||
CommandBuffer* producer) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto format = m_impl->frames.GetFormat();
|
||||
auto* frame = m_impl->frames.Acquire();
|
||||
@@ -772,8 +752,7 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
frame->Clear(*producer, clear);
|
||||
} else {
|
||||
if (frame->present_commands == nullptr) {
|
||||
frame->present_commands =
|
||||
std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
frame->present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
auto& command = *frame->present_commands;
|
||||
command.WaitForFenceAndReset();
|
||||
@@ -830,8 +809,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
continue;
|
||||
}
|
||||
if (frame.present_commands == nullptr) {
|
||||
frame.present_commands =
|
||||
std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
frame.present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
|
||||
}
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
|
||||
@@ -32,11 +32,11 @@
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "libs/controller.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
@@ -475,9 +475,9 @@ static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device,
|
||||
}
|
||||
|
||||
static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const VulkanExtensions& r,
|
||||
uint32_t queue_family,
|
||||
uint32_t queue_family,
|
||||
const std::vector<const char*>& device_extensions,
|
||||
GraphicContext& graphics) {
|
||||
GraphicContext& graphics) {
|
||||
EXIT_IF(physical_device == nullptr);
|
||||
EXIT_IF(queue_family == static_cast<uint32_t>(-1));
|
||||
|
||||
@@ -551,19 +551,19 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
features12.timelineSemaphore = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
device_features.robustBufferAccess = VK_TRUE;
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
device_features.robustBufferAccess = VK_TRUE;
|
||||
#if !defined(__APPLE__)
|
||||
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
|
||||
device_features.depthBounds = VK_TRUE; // unsupported by MoltenVK
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageReadWithoutFormat = VK_TRUE;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.vertexPipelineStoresAndAtomics =
|
||||
supported_features2.features.vertexPipelineStoresAndAtomics;
|
||||
|
||||
@@ -909,10 +909,9 @@ void WindowContext::CreateVulkan() {
|
||||
}
|
||||
surface = native_surface;
|
||||
|
||||
std::vector<const char*> device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
"VK_KHR_maintenance1"};
|
||||
std::vector<const char*> device_extensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME,
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer
|
||||
@@ -932,8 +931,8 @@ void WindowContext::CreateVulkan() {
|
||||
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
|
||||
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions,
|
||||
surface_capabilities, graphic_ctx.physical_device, queue_family);
|
||||
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions, surface_capabilities,
|
||||
graphic_ctx.physical_device, queue_family);
|
||||
|
||||
if (graphic_ctx.physical_device == nullptr) {
|
||||
EXIT("Could not find suitable device");
|
||||
@@ -949,9 +948,8 @@ void WindowContext::CreateVulkan() {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
"vkEnumerateDeviceExtensionProperties",
|
||||
[&](uint32_t* count, vk::ExtensionProperties* values) {
|
||||
return graphic_ctx.physical_device.enumerateDeviceExtensionProperties(nullptr,
|
||||
count,
|
||||
values);
|
||||
return graphic_ctx.physical_device.enumerateDeviceExtensionProperties(
|
||||
nullptr, count, values);
|
||||
});
|
||||
|
||||
if (HasExtension(available_extensions, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) {
|
||||
@@ -985,7 +983,7 @@ void WindowContext::CreateVulkan() {
|
||||
|
||||
render_context = std::make_unique<RenderContext>(graphic_ctx);
|
||||
LibKernel::Memory::InstallGpuResources(&render_context->GetGpuResources());
|
||||
presenter = std::make_unique<Presenter>(*this);
|
||||
presenter = std::make_unique<Presenter>(*this);
|
||||
RenderDocSetActiveWindow(graphic_ctx.instance, window);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_error.h"
|
||||
#include "SDL_events.h"
|
||||
@@ -40,6 +38,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -59,7 +58,7 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr int KEYBOARD_CONTROLLER_ID = -1000;
|
||||
constexpr int KEYBOARD_CONTROLLER_ID = -1000;
|
||||
|
||||
struct EventKeyboard {
|
||||
bool down;
|
||||
@@ -251,9 +250,7 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
|
||||
if (key.down) {
|
||||
switch (key.key_code) {
|
||||
case SDLK_ESCAPE: game.need_exit = true; break;
|
||||
case SDLK_SPACE:
|
||||
SetPause(game, !game.paused.load(std::memory_order_acquire));
|
||||
break;
|
||||
case SDLK_SPACE: SetPause(game, !game.paused.load(std::memory_order_acquire)); break;
|
||||
case SDLK_F1:
|
||||
if (!key.repeat) {
|
||||
RenderDocRequestCapture();
|
||||
@@ -390,7 +387,9 @@ void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
|
||||
void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
|
||||
const auto& window_event = event;
|
||||
switch (window_event.event) {
|
||||
case SDL_WINDOWEVENT_SHOWN: LOGF("Window %" PRIu32 " shown\n", window_event.windowID); break;
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
LOGF("Window %" PRIu32 " shown\n", window_event.windowID);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
|
||||
@@ -401,13 +400,13 @@ void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n",
|
||||
window_event.windowID, window_event.data1, window_event.data2);
|
||||
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window_event.windowID,
|
||||
window_event.data1, window_event.data2);
|
||||
|
||||
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
|
||||
Resize(window_event.data1, window_event.data2);
|
||||
@@ -807,9 +806,8 @@ static void WindowCreate(WindowContext& context) {
|
||||
window_flags |= static_cast<uint32_t>(SDL_WINDOW_BORDERLESS);
|
||||
}
|
||||
#endif
|
||||
context.window =
|
||||
SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
|
||||
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
|
||||
|
||||
context.window_hidden = true;
|
||||
|
||||
@@ -832,7 +830,7 @@ Presenter& WindowInit(uint32_t width, uint32_t height) {
|
||||
WindowCreate(*window);
|
||||
window->CreateVulkan();
|
||||
auto& presenter = *window->presenter;
|
||||
g_window = std::move(window);
|
||||
g_window = std::move(window);
|
||||
return presenter;
|
||||
}
|
||||
|
||||
@@ -934,9 +932,9 @@ void WindowContext::UpdateTitle() {
|
||||
Loader::SystemContentParamSfoGetString("TITLE_ID", title_id, sizeof(title_id));
|
||||
static bool has_app_ver =
|
||||
Loader::SystemContentParamSfoGetString("APP_VER", app_ver, sizeof(app_ver));
|
||||
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
|
||||
static uint64_t frame_num = 0;
|
||||
static uint64_t fps_frames = 0;
|
||||
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
|
||||
static uint64_t frame_num = 0;
|
||||
static uint64_t fps_frames = 0;
|
||||
static double current_fps = 0.0;
|
||||
|
||||
const auto now = Common::Timer::QueryPerformanceCounter();
|
||||
@@ -946,15 +944,15 @@ void WindowContext::UpdateTitle() {
|
||||
if (now - fps_start >= frequency) {
|
||||
current_fps = static_cast<double>(fps_frames) * static_cast<double>(frequency) /
|
||||
static_cast<double>(now - fps_start);
|
||||
fps_start = now;
|
||||
fps_frames = 0;
|
||||
fps_start = now;
|
||||
fps_frames = 0;
|
||||
}
|
||||
|
||||
auto fps = fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
|
||||
(has_title ? ", " : ""), (has_title_id ? title_id : ""),
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""),
|
||||
(has_app_ver ? " " : ""), device_name, processor_name,
|
||||
frame_num, current_fps);
|
||||
auto fps =
|
||||
fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
|
||||
(has_title ? ", " : ""), (has_title_id ? title_id : ""),
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""),
|
||||
(has_app_ver ? " " : ""), device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
|
||||
|
||||
@@ -28,8 +28,8 @@ struct SurfaceCapabilities {
|
||||
};
|
||||
|
||||
struct WindowLoopState {
|
||||
SDL_Event event {};
|
||||
bool need_exit = false;
|
||||
SDL_Event event {};
|
||||
bool need_exit = false;
|
||||
std::atomic_bool paused = false;
|
||||
};
|
||||
|
||||
@@ -38,14 +38,13 @@ struct WindowContext {
|
||||
~WindowContext();
|
||||
KYTY_CLASS_NO_COPY(WindowContext);
|
||||
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features
|
||||
RequiredVulkan13Features() noexcept;
|
||||
void CreateVulkan();
|
||||
void RecreateSurface();
|
||||
void RefreshSurfaceCapabilities();
|
||||
void UpdateIcon();
|
||||
void UpdateTitle();
|
||||
void Resize(uint32_t width, uint32_t height);
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
|
||||
void CreateVulkan();
|
||||
void RecreateSurface();
|
||||
void RefreshSurfaceCapabilities();
|
||||
void UpdateIcon();
|
||||
void UpdateTitle();
|
||||
void Resize(uint32_t width, uint32_t height);
|
||||
void ProcessWindowEvent(const SDL_WindowEvent& event);
|
||||
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
|
||||
void ProcessEvent(double time_seconds);
|
||||
@@ -59,14 +58,14 @@ struct WindowContext {
|
||||
void DrainMainThreadTasks();
|
||||
#endif
|
||||
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
bool window_hidden = true;
|
||||
vk::SurfaceKHR surface = nullptr;
|
||||
SurfaceCapabilities surface_capabilities;
|
||||
GraphicContext graphic_ctx;
|
||||
SDL_Window* window = nullptr;
|
||||
bool window_hidden = true;
|
||||
vk::SurfaceKHR surface = nullptr;
|
||||
SurfaceCapabilities surface_capabilities;
|
||||
std::unique_ptr<RenderContext> render_context;
|
||||
std::unique_ptr<Presenter> presenter;
|
||||
WindowLoopState loop;
|
||||
std::unique_ptr<Presenter> presenter;
|
||||
WindowLoopState loop;
|
||||
|
||||
char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0};
|
||||
char processor_name[64] = {0};
|
||||
@@ -76,7 +75,7 @@ struct WindowContext {
|
||||
#if defined(__APPLE__)
|
||||
Common::Mutex main_task_mutex;
|
||||
Common::CondVar main_task_done;
|
||||
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
|
||||
std::vector<std::function<void()>> main_tasks; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
|
||||
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
|
||||
#endif
|
||||
|
||||
@@ -44,7 +44,7 @@ struct CompileResult {
|
||||
};
|
||||
|
||||
bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
CompileResult& result, std::string* error);
|
||||
CompileResult& result, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
|
||||
|
||||
@@ -196,10 +196,10 @@ bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
case Opcode::SMovkI32: return DecodeScalarDestination(sdst, pc, inst.dst, error);
|
||||
case Opcode::SWaitcnt: {
|
||||
const uint32_t waitcnt = word & 0xffffu;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
inst.src0.signed_val = static_cast<int32_t>(waitcnt);
|
||||
inst.src0.value = waitcnt;
|
||||
inst.src_count = 1;
|
||||
inst.dst.kind = OperandKind::Null;
|
||||
inst.src0.signed_val = static_cast<int32_t>(waitcnt);
|
||||
inst.src0.value = waitcnt;
|
||||
inst.src_count = 1;
|
||||
return true;
|
||||
}
|
||||
case Opcode::SSetregB32:
|
||||
@@ -266,10 +266,10 @@ bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
inst.src0.value = simm;
|
||||
inst.src0.signed_val = static_cast<int16_t>(simm);
|
||||
inst.src_count = (inst.opcode == Opcode::SNop || inst.opcode == Opcode::SWaitcnt ||
|
||||
inst.opcode == Opcode::SSleep || inst.opcode == Opcode::SSendmsg ||
|
||||
inst.opcode == Opcode::STtraceData || inst.opcode == Opcode::SInstPrefetch)
|
||||
? 1
|
||||
: 0;
|
||||
inst.opcode == Opcode::SSleep || inst.opcode == Opcode::SSendmsg ||
|
||||
inst.opcode == Opcode::STtraceData || inst.opcode == Opcode::SInstPrefetch)
|
||||
? 1
|
||||
: 0;
|
||||
inst.branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
|
||||
inst.branch_target = pc + 4u + static_cast<uint32_t>(inst.branch_offset);
|
||||
SetRawWords(inst, code, word_index, 1);
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program);
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>& spirv,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>& spirv,
|
||||
std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -31,7 +31,7 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
// Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot.
|
||||
// On failure the destination is unchanged.
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, std::string* error);
|
||||
ResourceSnapshot& snapshot, std::string* error);
|
||||
|
||||
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
|
||||
// emission. On failure the program is unchanged.
|
||||
|
||||
@@ -84,7 +84,7 @@ uint32_t ByteExtent(const Instruction& inst) {
|
||||
}
|
||||
|
||||
bool ContainsUnknown(const ScalarProvenance& provenance, uint32_t id, std::vector<uint8_t>& visited,
|
||||
std::vector<uint32_t>& path) {
|
||||
std::vector<uint32_t>& path) {
|
||||
path.push_back(id);
|
||||
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
|
||||
return true;
|
||||
@@ -115,7 +115,7 @@ bool ContainsUnknown(const ScalarProvenance& provenance, uint32_t id, std::vecto
|
||||
}
|
||||
|
||||
bool IsLoopInvariantValue(const ScalarProvenance& provenance, uint32_t id,
|
||||
std::vector<uint8_t>& visiting) {
|
||||
std::vector<uint8_t>& visiting) {
|
||||
if (id <= ScalarProvenance::Unknown || id >= provenance.values.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -530,8 +530,8 @@ bool BuildSrtPlan(Program& program, std::string* error) {
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
std::string* error) {
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
std::string* error) {
|
||||
const DescriptorSourceRequest request {source, use_pc};
|
||||
std::vector<DescriptorValue> results;
|
||||
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results, error)) {
|
||||
@@ -542,11 +542,11 @@ bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t
|
||||
}
|
||||
|
||||
static bool EvaluateRuntimeSourcesImpl(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime,
|
||||
std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::string* error) {
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime,
|
||||
std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::string* error) {
|
||||
if (!program.srt_plan_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "SRT plan is not ready");
|
||||
@@ -602,22 +602,22 @@ static bool EvaluateRuntimeSourcesImpl(const Program&
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error) {
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error) {
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, error);
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error) {
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, error);
|
||||
}
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error) {
|
||||
std::string* error) {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, error);
|
||||
}
|
||||
|
||||
@@ -30,22 +30,22 @@ bool FoldScalarConstant(const ScalarProvenance& provenance, uint32_t value, uint
|
||||
bool BuildSrtPlan(Program& program, std::string* error);
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, uint32_t use_pc,
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
const SrtRuntime& runtime, DescriptorValue& result,
|
||||
std::string* error);
|
||||
|
||||
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
|
||||
// several descriptors are memoized once across the batch.
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error);
|
||||
|
||||
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
|
||||
// On failure neither destination is changed.
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error);
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error);
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error);
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/shaderVertexMetadata.h"
|
||||
#include "libs/errno.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
@@ -828,11 +828,10 @@ static void ShaderGetStaticInputInfoPS(
|
||||
vs_info.stage.program != nullptr && !vs_info.stage.program->bindings.descriptors.empty()
|
||||
? 1
|
||||
: 0;
|
||||
ps_info.push_constant_offset =
|
||||
vs_info.stage.program != nullptr
|
||||
? vs_info.stage.program->bindings.push_constant_offset +
|
||||
vs_info.stage.program->bindings.push_constant_size
|
||||
: 0;
|
||||
ps_info.push_constant_offset = vs_info.stage.program != nullptr
|
||||
? vs_info.stage.program->bindings.push_constant_offset +
|
||||
vs_info.stage.program->bindings.push_constant_size
|
||||
: 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
ps_info.target_output_mode[i] = sh.target_output_mode[i];
|
||||
@@ -1294,9 +1293,8 @@ static void DumpShaderRecompilerSpirv(const char* type, uint64_t shader_hash,
|
||||
|
||||
static std::atomic_int id = 0;
|
||||
|
||||
const auto base_name =
|
||||
Config::GetShaderLogFolder() /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
const auto base_name = Config::GetShaderLogFolder() /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
Common::File::CreateDirectories(base_name.parent_path());
|
||||
|
||||
Common::File spv_file;
|
||||
@@ -1345,9 +1343,8 @@ static void DumpShaderRecompilerOriginal(const char* type, uint64_t shader_hash,
|
||||
|
||||
static std::atomic_int id = 0;
|
||||
|
||||
const auto base_name =
|
||||
Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
const auto base_name = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
Common::File::CreateDirectories(base_name.parent_path());
|
||||
|
||||
Common::File bin_file;
|
||||
|
||||
@@ -42,8 +42,8 @@ struct ShaderStageRuntime {
|
||||
// Resolves an immutable native shader plan against current user data. The prior stage is preserved
|
||||
// if any ReadConst, snapshot, or specialization check fails.
|
||||
bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::Program> program,
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base,
|
||||
ShaderStageRuntime& stage, std::string* error);
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base,
|
||||
ShaderStageRuntime& stage, std::string* error);
|
||||
|
||||
struct ShaderId {
|
||||
uint32_t hash0 = 0;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::Program> program,
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base,
|
||||
ShaderStageRuntime& stage, std::string* error) {
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base,
|
||||
ShaderStageRuntime& stage, std::string* error) {
|
||||
if (program == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "missing native shader plan";
|
||||
|
||||
@@ -17,7 +17,7 @@ bool Fail(std::string* error, const char* message) {
|
||||
} // namespace
|
||||
|
||||
bool ShaderReadVertexMetadata(const ShaderMappedData& data, uint32_t max_user_sgprs,
|
||||
ShaderVertexMetadata& metadata, std::string* error) {
|
||||
ShaderVertexMetadata& metadata, std::string* error) {
|
||||
if (data.user_data == nullptr) {
|
||||
return Fail(error, "missing AGC user-data header");
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShaderVertexMetadata {
|
||||
|
||||
// Copies the small AGC metadata subset used by the vertex path after validating every guest range.
|
||||
bool ShaderReadVertexMetadata(const ShaderMappedData& data, uint32_t max_user_sgprs,
|
||||
ShaderVertexMetadata& metadata, std::string* error);
|
||||
ShaderVertexMetadata& metadata, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ static uint64_t MonotonicTimeNs() {
|
||||
}
|
||||
|
||||
static std::unordered_map<KernelEqueue, KernelEqueueRef> g_equeues;
|
||||
static Common::Mutex g_equeues_mutex;
|
||||
static uint64_t g_next_equeue = 1;
|
||||
static Common::Mutex g_equeues_mutex;
|
||||
static uint64_t g_next_equeue = 1;
|
||||
|
||||
class KernelEqueuePrivate {
|
||||
public:
|
||||
@@ -459,8 +459,7 @@ int KYTY_SYSV_ABI KernelAddUserEvent(KernelEqueue eq, int id) {
|
||||
int KYTY_SYSV_ABI KernelAddUserEventEdge(KernelEqueue eq, int id) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t user event edge add: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq),
|
||||
id);
|
||||
LOGF("\t user event edge add: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq), id);
|
||||
|
||||
KernelEqueueEvent event {};
|
||||
event.event.ident = static_cast<uintptr_t>(id);
|
||||
@@ -485,7 +484,7 @@ int KYTY_SYSV_ABI KernelTriggerUserEvent(KernelEqueue eq, int id, void* udata) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI KernelTriggerUserEventForAll(int id, void* udata) {
|
||||
int triggered = 0;
|
||||
int triggered = 0;
|
||||
std::vector<KernelEqueueRef> queues;
|
||||
|
||||
{
|
||||
@@ -507,8 +506,7 @@ int KYTY_SYSV_ABI KernelTriggerUserEventForAll(int id, void* udata) {
|
||||
int KYTY_SYSV_ABI KernelDeleteUserEvent(KernelEqueue eq, int id) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t user event delete: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq),
|
||||
id);
|
||||
LOGF("\t user event delete: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq), id);
|
||||
|
||||
return KernelDeleteEvent(eq, static_cast<uintptr_t>(id), KERNEL_EVFILT_USER);
|
||||
}
|
||||
@@ -577,8 +575,7 @@ int KYTY_SYSV_ABI KernelAddAmprSystemEvent(KernelEqueue eq, int id, void* udata)
|
||||
int KYTY_SYSV_ABI KernelDeleteAmprEvent(KernelEqueue eq, int id) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t AMPR event delete: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq),
|
||||
id);
|
||||
LOGF("\t AMPR event delete: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq), id);
|
||||
|
||||
if (eq != KERNEL_EQUEUE_INVALID) {
|
||||
(void)KernelDeleteEvent(eq, static_cast<uintptr_t>(id), KERNEL_EVFILT_USER);
|
||||
@@ -590,8 +587,8 @@ int KYTY_SYSV_ABI KernelDeleteAmprEvent(KernelEqueue eq, int id) {
|
||||
int KYTY_SYSV_ABI KernelDeleteAmprSystemEvent(KernelEqueue eq, int id) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t AMPR system event delete: eq = 0x%016" PRIx64 ", id = %d\n",
|
||||
static_cast<uint64_t>(eq), id);
|
||||
LOGF("\t AMPR system event delete: eq = 0x%016" PRIx64 ", id = %d\n", static_cast<uint64_t>(eq),
|
||||
id);
|
||||
|
||||
return KernelDeleteAmprEvent(eq, id);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ struct KernelEvent {
|
||||
};
|
||||
|
||||
struct KernelFilter {
|
||||
void* data = nullptr;
|
||||
void* data = nullptr;
|
||||
std::shared_ptr<void> owner;
|
||||
trigger_func_t trigger_func = nullptr;
|
||||
reset_func_t reset_func = nullptr;
|
||||
|
||||
@@ -206,8 +206,8 @@ bool ConfigurationItem::operator<(const QTreeWidgetItem& other) const {
|
||||
GetStatusText(other_item->m_info->game_status);
|
||||
case GameVersionColumn:
|
||||
case FirmwareVersionColumn: {
|
||||
const auto& version = column == GameVersionColumn ? m_info->gameVersion
|
||||
: m_info->firmwareVer;
|
||||
const auto& version =
|
||||
column == GameVersionColumn ? m_info->gameVersion : m_info->firmwareVer;
|
||||
const auto& other_version = column == GameVersionColumn
|
||||
? other_item->m_info->gameVersion
|
||||
: other_item->m_info->firmwareVer;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "configurationListWidget.h"
|
||||
|
||||
#include "patchesDialog.h"
|
||||
#include "common.h"
|
||||
#include "compatibilityDatabase.h"
|
||||
#include "configuration.h"
|
||||
@@ -8,6 +7,7 @@
|
||||
#include "configurationItem.h"
|
||||
#include "gameListTreeWidget.h"
|
||||
#include "mainDialog.h"
|
||||
#include "patchesDialog.h"
|
||||
#include "trophyViewerDialog.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
@@ -272,10 +272,18 @@ static bool FindTerminal(QString* program, QStringList* prefix) {
|
||||
};
|
||||
|
||||
static const TerminalSpec candidates[] = {
|
||||
{"x-terminal-emulator", "-e"}, {"gnome-terminal", "--"}, {"konsole", "-e"},
|
||||
{"xfce4-terminal", "-x"}, {"mate-terminal", "--"}, {"tilix", "-e"},
|
||||
{"alacritty", "-e"}, {"kitty", nullptr}, {"foot", nullptr},
|
||||
{"wezterm", "-e"}, {"urxvt", "-e"}, {"xterm", "-e"},
|
||||
{"x-terminal-emulator", "-e"},
|
||||
{"gnome-terminal", "--"},
|
||||
{"konsole", "-e"},
|
||||
{"xfce4-terminal", "-x"},
|
||||
{"mate-terminal", "--"},
|
||||
{"tilix", "-e"},
|
||||
{"alacritty", "-e"},
|
||||
{"kitty", nullptr},
|
||||
{"foot", nullptr},
|
||||
{"wezterm", "-e"},
|
||||
{"urxvt", "-e"},
|
||||
{"xterm", "-e"},
|
||||
};
|
||||
|
||||
const auto try_candidate = [program, prefix](const QString& executable, const char* separator) {
|
||||
@@ -293,7 +301,7 @@ static bool FindTerminal(QString* program, QStringList* prefix) {
|
||||
|
||||
if (const auto from_env = qEnvironmentVariable("TERMINAL"); !from_env.isEmpty()) {
|
||||
// Reuse the known separator for an explicit terminal.
|
||||
const auto env_name = QFileInfo(from_env).fileName();
|
||||
const auto env_name = QFileInfo(from_env).fileName();
|
||||
const char* separator = "-e";
|
||||
for (const auto& candidate: candidates) {
|
||||
if (env_name == QLatin1String(candidate.executable)) {
|
||||
@@ -379,9 +387,9 @@ void MainDialog::RunInterpreter(QProcess* process, const Configuration& info) {
|
||||
#if !defined(_WIN32)
|
||||
// Report immediate launch failures.
|
||||
if (!process->waitForStarted(5000)) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to start:\n%1\n\n%2")
|
||||
.arg(process->program(), process->errorString()));
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Failed to start:\n%1\n\n%2").arg(process->program(), process->errorString()));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -59,16 +59,14 @@ void PatchesDialog::Load() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto patches = QJsonDocument::fromJson(file.readAll())
|
||||
.object()
|
||||
.value(QStringLiteral("patches"))
|
||||
.toArray();
|
||||
const auto patches =
|
||||
QJsonDocument::fromJson(file.readAll()).object().value(QStringLiteral("patches")).toArray();
|
||||
for (const auto& value: patches) {
|
||||
const auto patch = value.toObject();
|
||||
auto* item = new QListWidgetItem(patch.value(QStringLiteral("name")).toString(), m_patches);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(patch.value(QStringLiteral("enabled")).toBool(true) ? Qt::Checked
|
||||
: Qt::Unchecked);
|
||||
: Qt::Unchecked);
|
||||
}
|
||||
|
||||
m_apply->setEnabled(!patches.isEmpty());
|
||||
@@ -84,8 +82,8 @@ void PatchesDialog::Save() {
|
||||
|
||||
auto document = QJsonDocument::fromJson(input.readAll());
|
||||
input.close();
|
||||
auto root = document.object();
|
||||
auto patches = root.value(QStringLiteral("patches")).toArray();
|
||||
auto root = document.object();
|
||||
auto patches = root.value(QStringLiteral("patches")).toArray();
|
||||
for (int index = 0; index < patches.size(); index++) {
|
||||
auto patch = patches[index].toObject();
|
||||
patch.insert(QStringLiteral("enabled"),
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ KYTY_SUBSYSTEM_INIT(Graphics) {
|
||||
|
||||
auto& presenter = WindowInit(width, height);
|
||||
auto& video_out = VideoOut::VideoOutInit(width, height, presenter);
|
||||
g_renderer = &presenter.Renderer();
|
||||
g_renderer = &presenter.Renderer();
|
||||
g_renderer->InitializeGpu(&video_out);
|
||||
ShaderInit();
|
||||
}
|
||||
|
||||
@@ -1511,8 +1511,8 @@ static int ExecuteAprCommandBuffer(uint64_t command_buffer, int32_t* execution_r
|
||||
} break;
|
||||
case CommandKind::KernelEvent: {
|
||||
const auto& command = state.kernel_event_commands[entry.index];
|
||||
const auto eq = static_cast<LibKernel::EventQueue::KernelEqueue>(command.eq);
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEvent(
|
||||
const auto eq = static_cast<LibKernel::EventQueue::KernelEqueue>(command.eq);
|
||||
auto result = LibKernel::EventQueue::KernelTriggerUserEvent(
|
||||
eq, command.id, reinterpret_cast<void*>(command.data));
|
||||
if (result != OK) {
|
||||
LOGF("\tAPR submit event failed: eq=0x%016" PRIx64 ", id=%" PRId32
|
||||
|
||||
@@ -1241,7 +1241,7 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
|
||||
if (thread == PthreadSelfOrNull()) {
|
||||
SignalDispatchScope scope;
|
||||
auto ctx = CreateCurrentGuestCallSignalUcontext(
|
||||
reinterpret_cast<uint64_t>(__builtin_return_address(0)));
|
||||
reinterpret_cast<uint64_t>(__builtin_return_address(0)));
|
||||
handler(signum, &ctx);
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -438,11 +438,12 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
|
||||
*mount_result = {};
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir = std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
|
||||
const bool create = ((mount->mount_mode & 4u) != 0);
|
||||
const bool create2 = ((mount->mount_mode & 32u) != 0);
|
||||
const bool open = (!create && !create2 && ((mount->mount_mode & 3u) != 0));
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir =
|
||||
std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
|
||||
const bool create = ((mount->mount_mode & 4u) != 0);
|
||||
const bool create2 = ((mount->mount_mode & 32u) != 0);
|
||||
const bool open = (!create && !create2 && ((mount->mount_mode & 3u) != 0));
|
||||
|
||||
const int slot = g_mount_slots.FindAvailable(dir_name);
|
||||
if (slot == SaveDataMountSlots::BUSY) {
|
||||
@@ -594,9 +595,10 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
|
||||
*mount_result = {};
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir = std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
|
||||
const int slot = g_mount_slots.FindAvailable(dir_name);
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir =
|
||||
std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
|
||||
const int slot = g_mount_slots.FindAvailable(dir_name);
|
||||
if (slot == SaveDataMountSlots::BUSY) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
|
||||
@@ -79,10 +79,10 @@ static void Sha1Msg1(XmmWords& dest, const XmmWords& src2) {
|
||||
const uint32_t w3 = dest.w[0];
|
||||
const uint32_t w4 = src2.w[3];
|
||||
const uint32_t w5 = src2.w[2];
|
||||
dest.w[3] = w2 ^ w0;
|
||||
dest.w[2] = w3 ^ w1;
|
||||
dest.w[1] = w4 ^ w2;
|
||||
dest.w[0] = w5 ^ w3;
|
||||
dest.w[3] = w2 ^ w0;
|
||||
dest.w[2] = w3 ^ w1;
|
||||
dest.w[1] = w4 ^ w2;
|
||||
dest.w[0] = w5 ^ w3;
|
||||
}
|
||||
|
||||
static void Sha1Msg2(XmmWords& dest, const XmmWords& src2) {
|
||||
@@ -93,18 +93,18 @@ static void Sha1Msg2(XmmWords& dest, const XmmWords& src2) {
|
||||
const uint32_t w17 = Rol32(dest.w[2] ^ w14, 1u);
|
||||
const uint32_t w18 = Rol32(dest.w[1] ^ w15, 1u);
|
||||
const uint32_t w19 = Rol32(dest.w[0] ^ w16, 1u);
|
||||
dest.w[3] = w16;
|
||||
dest.w[2] = w17;
|
||||
dest.w[1] = w18;
|
||||
dest.w[0] = w19;
|
||||
dest.w[3] = w16;
|
||||
dest.w[2] = w17;
|
||||
dest.w[1] = w18;
|
||||
dest.w[0] = w19;
|
||||
}
|
||||
|
||||
static void Sha1Nexte(XmmWords& dest, const XmmWords& src2) {
|
||||
const uint32_t tmp = Rol32(dest.w[3], 30u);
|
||||
dest.w[3] = src2.w[3] + tmp;
|
||||
dest.w[2] = src2.w[2];
|
||||
dest.w[1] = src2.w[1];
|
||||
dest.w[0] = src2.w[0];
|
||||
const uint32_t tmp = Rol32(dest.w[3], 30u);
|
||||
dest.w[3] = src2.w[3] + tmp;
|
||||
dest.w[2] = src2.w[2];
|
||||
dest.w[1] = src2.w[1];
|
||||
dest.w[0] = src2.w[0];
|
||||
}
|
||||
|
||||
static uint32_t Sha1RoundFunc(uint8_t group, uint32_t b, uint32_t c, uint32_t d) {
|
||||
@@ -185,10 +185,10 @@ static void Sha256Msg1(XmmWords& dest, const XmmWords& src2) {
|
||||
const uint32_t w2 = dest.w[2];
|
||||
const uint32_t w1 = dest.w[1];
|
||||
const uint32_t w0 = dest.w[0];
|
||||
dest.w[3] = w3 + Sha256Sigma0(w4);
|
||||
dest.w[2] = w2 + Sha256Sigma0(w3);
|
||||
dest.w[1] = w1 + Sha256Sigma0(w2);
|
||||
dest.w[0] = w0 + Sha256Sigma0(w1);
|
||||
dest.w[3] = w3 + Sha256Sigma0(w4);
|
||||
dest.w[2] = w2 + Sha256Sigma0(w3);
|
||||
dest.w[1] = w1 + Sha256Sigma0(w2);
|
||||
dest.w[0] = w0 + Sha256Sigma0(w1);
|
||||
}
|
||||
|
||||
static void Sha256Msg2(XmmWords& dest, const XmmWords& src2) {
|
||||
@@ -312,7 +312,9 @@ static bool DecodeShaNiInsn(const uint8_t* rip, ShaNiInsn& insn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ShaNiModrmIsRegister(uint8_t modrm) { return (modrm & 0xc0u) == 0xc0u; }
|
||||
static bool ShaNiModrmIsRegister(uint8_t modrm) {
|
||||
return (modrm & 0xc0u) == 0xc0u;
|
||||
}
|
||||
|
||||
static uint8_t ShaNiRegIndex(uint8_t modrm, uint8_t rex, bool reg_field) {
|
||||
if (reg_field) {
|
||||
@@ -321,7 +323,7 @@ static uint8_t ShaNiRegIndex(uint8_t modrm, uint8_t rex, bool reg_field) {
|
||||
return (modrm & 0x07u) | ((rex & 0x01u) << 3u);
|
||||
}
|
||||
|
||||
static bool ResolveShaNiMemoryAddress(const uint8_t* rip, const ShaNiInsn& insn,
|
||||
static bool ResolveShaNiMemoryAddress(const uint8_t* rip, const ShaNiInsn& insn,
|
||||
const uint64_t (&gpr)[16], const void*& address) {
|
||||
const uint8_t modrm = rip[insn.modrm_offset];
|
||||
const uint8_t mod = modrm >> 6u;
|
||||
@@ -334,12 +336,12 @@ static bool ResolveShaNiMemoryAddress(const uint8_t* rip, const ShaNiInsn& insn,
|
||||
uint64_t result = 0;
|
||||
|
||||
if (rm == 4u) {
|
||||
const uint8_t sib = rip[offset++];
|
||||
const uint8_t scale = sib >> 6u;
|
||||
const uint8_t index_low = (sib >> 3u) & 0x07u;
|
||||
const uint8_t base_low = sib & 0x07u;
|
||||
const bool has_index = index_low != 4u || (insn.rex & 0x02u) != 0;
|
||||
const bool has_base = mod != 0u || base_low != 5u;
|
||||
const uint8_t sib = rip[offset++];
|
||||
const uint8_t scale = sib >> 6u;
|
||||
const uint8_t index_low = (sib >> 3u) & 0x07u;
|
||||
const uint8_t base_low = sib & 0x07u;
|
||||
const bool has_index = index_low != 4u || (insn.rex & 0x02u) != 0;
|
||||
const bool has_base = mod != 0u || base_low != 5u;
|
||||
|
||||
if (has_base) {
|
||||
const uint8_t base = base_low | ((insn.rex & 0x01u) << 3u);
|
||||
|
||||
+339
-396
@@ -1,5 +1,4 @@
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -17,484 +16,428 @@ namespace EventQueue = Libs::LibKernel::EventQueue;
|
||||
using Libs::LibKernel::KERNEL_ERROR_EBADF;
|
||||
using Libs::LibKernel::KERNEL_ERROR_ENOENT;
|
||||
|
||||
void Check(bool value, const char *text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "EventQueueLifetimeTests: failed: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckConcurrentResult(int result, const char *text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF ||
|
||||
result == KERNEL_ERROR_ENOENT,
|
||||
text);
|
||||
void CheckConcurrentResult(int result, const char* text) {
|
||||
Check(result == OK || result == KERNEL_ERROR_EBADF || result == KERNEL_ERROR_ENOENT, text);
|
||||
}
|
||||
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *count = static_cast<std::atomic_uint32_t *>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
void CountDeletedEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* count = static_cast<std::atomic_uint32_t*>(event->filter.data);
|
||||
count->fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
struct DuplicateEventOwner {
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
};
|
||||
|
||||
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent *event,
|
||||
void *trigger_data) {
|
||||
auto next = event->event;
|
||||
next.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
if (event->triggered) {
|
||||
event->pending_events.push_back(next);
|
||||
} else {
|
||||
event->event = next;
|
||||
event->triggered = true;
|
||||
}
|
||||
void QueueDuplicateEvent(EventQueue::KernelEqueueEvent* event, void* trigger_data) {
|
||||
auto next = event->event;
|
||||
next.data = reinterpret_cast<intptr_t>(trigger_data);
|
||||
if (event->triggered) {
|
||||
event->pending_events.push_back(next);
|
||||
} else {
|
||||
event->event = next;
|
||||
event->triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent *event) {
|
||||
event->triggered = false;
|
||||
event->event.data = 0;
|
||||
void ResetDuplicateEvent(EventQueue::KernelEqueueEvent* event) {
|
||||
event->triggered = false;
|
||||
event->event.data = 0;
|
||||
}
|
||||
|
||||
void DeleteDuplicateEvent(EventQueue::KernelEqueue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *owner = static_cast<DuplicateEventOwner *>(event->filter.data);
|
||||
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
void DeleteDuplicateEvent(EventQueue::KernelEqueue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* owner = static_cast<DuplicateEventOwner*>(event->filter.data);
|
||||
owner->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent *, void *) {
|
||||
Check(false, "duplicate add replaced trigger callback");
|
||||
void PoisonDuplicateEvent(EventQueue::KernelEqueueEvent*, void*) {
|
||||
Check(false, "duplicate add replaced trigger callback");
|
||||
}
|
||||
|
||||
void TestDuplicateAddPreservesEventState() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
|
||||
"create duplicate add queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "duplicate-add") == OK,
|
||||
"create duplicate add queue");
|
||||
|
||||
auto original_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
|
||||
EventQueue::KernelEqueueEvent original{};
|
||||
original.event.ident = 17;
|
||||
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
original.event.udata = reinterpret_cast<void *>(0x1111);
|
||||
original.filter.data = original_owner.get();
|
||||
original.filter.owner = original_owner;
|
||||
original.filter.trigger_func = QueueDuplicateEvent;
|
||||
original.filter.reset_func = ResetDuplicateEvent;
|
||||
original.filter.delete_event_func = DeleteDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, original) == OK,
|
||||
"add original duplicate event");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x1234)) == OK,
|
||||
"queue first trigger");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void *>(0x5678)) == OK,
|
||||
"queue pending trigger");
|
||||
auto original_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_original = original_owner;
|
||||
EventQueue::KernelEqueueEvent original {};
|
||||
original.event.ident = 17;
|
||||
original.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
original.event.udata = reinterpret_cast<void*>(0x1111);
|
||||
original.filter.data = original_owner.get();
|
||||
original.filter.owner = original_owner;
|
||||
original.filter.trigger_func = QueueDuplicateEvent;
|
||||
original.filter.reset_func = ResetDuplicateEvent;
|
||||
original.filter.delete_event_func = DeleteDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, original) == OK, "add original duplicate event");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void*>(0x1234)) == OK,
|
||||
"queue first trigger");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
reinterpret_cast<void*>(0x5678)) == OK,
|
||||
"queue pending trigger");
|
||||
|
||||
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
|
||||
EventQueue::KernelEqueueEvent duplicate{};
|
||||
duplicate.triggered = false;
|
||||
duplicate.deadline_ns = 1;
|
||||
duplicate.event.ident = 17;
|
||||
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
duplicate.event.data = 0x7fffffff;
|
||||
duplicate.event.udata = reinterpret_cast<void *>(0x2222);
|
||||
duplicate.filter.data = replacement_owner.get();
|
||||
duplicate.filter.owner = replacement_owner;
|
||||
duplicate.filter.trigger_func = PoisonDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK,
|
||||
"update duplicate event");
|
||||
auto replacement_owner = std::make_shared<DuplicateEventOwner>();
|
||||
std::weak_ptr<DuplicateEventOwner> weak_replacement = replacement_owner;
|
||||
EventQueue::KernelEqueueEvent duplicate {};
|
||||
duplicate.triggered = false;
|
||||
duplicate.deadline_ns = 1;
|
||||
duplicate.event.ident = 17;
|
||||
duplicate.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
duplicate.event.data = 0x7fffffff;
|
||||
duplicate.event.udata = reinterpret_cast<void*>(0x2222);
|
||||
duplicate.filter.data = replacement_owner.get();
|
||||
duplicate.filter.owner = replacement_owner;
|
||||
duplicate.filter.trigger_func = PoisonDuplicateEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, duplicate) == OK, "update duplicate event");
|
||||
|
||||
duplicate.filter.owner.reset();
|
||||
replacement_owner.reset();
|
||||
Check(weak_replacement.expired(), "duplicate owner is not retained");
|
||||
original.filter.owner.reset();
|
||||
original_owner.reset();
|
||||
Check(!weak_original.expired(), "original event owner remains retained");
|
||||
duplicate.filter.owner.reset();
|
||||
replacement_owner.reset();
|
||||
Check(weak_replacement.expired(), "duplicate owner is not retained");
|
||||
original.filter.owner.reset();
|
||||
original_owner.reset();
|
||||
Check(!weak_original.expired(), "original event owner remains retained");
|
||||
|
||||
EventQueue::KernelEvent events[2]{};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
|
||||
"read queued duplicate triggers");
|
||||
Check(out == 2, "duplicate add preserves pending event count");
|
||||
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
|
||||
"duplicate add preserves current and pending event data");
|
||||
Check(events[0].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates current user data");
|
||||
Check(events[1].udata == reinterpret_cast<void *>(0x2222),
|
||||
"duplicate add updates pending user data");
|
||||
EventQueue::KernelEvent events[2] {};
|
||||
int out = 0;
|
||||
Libs::LibKernel::KernelUseconds timeout = 0;
|
||||
Check(EventQueue::KernelWaitEqueue(queue, events, 2, &out, &timeout) == OK,
|
||||
"read queued duplicate triggers");
|
||||
Check(out == 2, "duplicate add preserves pending event count");
|
||||
Check(events[0].data == 0x1234 && events[1].data == 0x5678,
|
||||
"duplicate add preserves current and pending event data");
|
||||
Check(events[0].udata == reinterpret_cast<void*>(0x2222),
|
||||
"duplicate add updates current user data");
|
||||
Check(events[1].udata == reinterpret_cast<void*>(0x2222),
|
||||
"duplicate add updates pending user data");
|
||||
|
||||
EventQueue::KernelEvent timer_event{};
|
||||
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) ==
|
||||
OK &&
|
||||
out == 1,
|
||||
"duplicate add updates deadline metadata");
|
||||
Check(timer_event.data == 0 &&
|
||||
timer_event.udata == reinterpret_cast<void *>(0x2222),
|
||||
"deadline trigger retains updated duplicate metadata");
|
||||
EventQueue::KernelEvent timer_event {};
|
||||
Check(EventQueue::KernelWaitEqueue(queue, &timer_event, 1, &out, &timeout) == OK && out == 1,
|
||||
"duplicate add updates deadline metadata");
|
||||
Check(timer_event.data == 0 && timer_event.udata == reinterpret_cast<void*>(0x2222),
|
||||
"deadline trigger retains updated duplicate metadata");
|
||||
|
||||
auto retained_owner = weak_original.lock();
|
||||
Check(retained_owner != nullptr, "original owner alive before delete");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 17,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT) ==
|
||||
OK,
|
||||
"delete duplicate event");
|
||||
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"duplicate add preserves delete callback");
|
||||
retained_owner.reset();
|
||||
Check(weak_original.expired(), "original owner released on delete");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete duplicate add queue");
|
||||
auto retained_owner = weak_original.lock();
|
||||
Check(retained_owner != nullptr, "original owner alive before delete");
|
||||
Check(EventQueue::KernelDeleteEvent(queue, 17, EventQueue::KERNEL_EVFILT_VIDEO_OUT) == OK,
|
||||
"delete duplicate event");
|
||||
Check(retained_owner->delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"duplicate add preserves delete callback");
|
||||
retained_owner.reset();
|
||||
Check(weak_original.expired(), "original owner released on delete");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete duplicate add queue");
|
||||
}
|
||||
|
||||
struct SimulatedVideoOutEventState;
|
||||
|
||||
struct SimulatedVideoOutRegistration {
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<SimulatedVideoOutEventState> state;
|
||||
uint64_t marker = 0x123456789abcdef0ull;
|
||||
EventQueue::KernelEqueue handle = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
std::shared_ptr<SimulatedVideoOutEventState> state;
|
||||
uint64_t marker = 0x123456789abcdef0ull;
|
||||
};
|
||||
|
||||
struct SimulatedVideoOutEventState {
|
||||
SimulatedVideoOutEventState(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), destroy_count(destroy_count) {}
|
||||
SimulatedVideoOutEventState(std::atomic_uint32_t& stage, std::atomic_uint32_t& destroy_count)
|
||||
: stage(stage), destroy_count(destroy_count) {}
|
||||
|
||||
~SimulatedVideoOutEventState() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
~SimulatedVideoOutEventState() { destroy_count.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xfedcba9876543210ull;
|
||||
std::mutex mutex;
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> queues;
|
||||
std::atomic_uint32_t& stage;
|
||||
std::atomic_uint32_t& destroy_count;
|
||||
uint64_t marker = 0xfedcba9876543210ull;
|
||||
};
|
||||
|
||||
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *registration =
|
||||
static_cast<SimulatedVideoOutRegistration *>(event->filter.data);
|
||||
Check(registration != nullptr && registration->handle == queue,
|
||||
"simulated registration identity");
|
||||
auto state = registration->state;
|
||||
Check(state != nullptr, "simulated event owns shared state");
|
||||
state->stage.store(1, std::memory_order_release);
|
||||
while (state->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
void DetachSimulatedVideoOutEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent* event) {
|
||||
auto* registration = static_cast<SimulatedVideoOutRegistration*>(event->filter.data);
|
||||
Check(registration != nullptr && registration->handle == queue,
|
||||
"simulated registration identity");
|
||||
auto state = registration->state;
|
||||
Check(state != nullptr, "simulated event owns shared state");
|
||||
state->stage.store(1, std::memory_order_release);
|
||||
while (state->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(state->mutex);
|
||||
const auto entry =
|
||||
std::find_if(state->queues.begin(), state->queues.end(),
|
||||
[registration](const auto &candidate) {
|
||||
return candidate.get() == registration;
|
||||
});
|
||||
if (entry != state->queues.end()) {
|
||||
state->queues.erase(entry);
|
||||
}
|
||||
}
|
||||
event->filter.owner.reset();
|
||||
Check(state->marker == 0xfedcba9876543210ull &&
|
||||
registration->marker == 0x123456789abcdef0ull,
|
||||
"callback state survives simulated port destruction");
|
||||
{
|
||||
std::lock_guard lock(state->mutex);
|
||||
const auto entry = std::find_if(
|
||||
state->queues.begin(), state->queues.end(),
|
||||
[registration](const auto& candidate) { return candidate.get() == registration; });
|
||||
if (entry != state->queues.end()) {
|
||||
state->queues.erase(entry);
|
||||
}
|
||||
}
|
||||
event->filter.owner.reset();
|
||||
Check(state->marker == 0xfedcba9876543210ull && registration->marker == 0x123456789abcdef0ull,
|
||||
"callback state survives simulated port destruction");
|
||||
}
|
||||
|
||||
void TestCallbackStateOutlivesPort() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
|
||||
"create shared port state queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "shared-port-state") == OK,
|
||||
"create shared port state queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto port_state =
|
||||
std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
|
||||
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
|
||||
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
|
||||
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration =
|
||||
registration;
|
||||
registration->handle = queue;
|
||||
registration->state = port_state;
|
||||
port_state->queues.push_back(registration);
|
||||
std::atomic_uint32_t stage {0};
|
||||
std::atomic_uint32_t destroy_count {0};
|
||||
auto port_state = std::make_shared<SimulatedVideoOutEventState>(stage, destroy_count);
|
||||
std::weak_ptr<SimulatedVideoOutEventState> weak_state = port_state;
|
||||
auto registration = std::make_shared<SimulatedVideoOutRegistration>();
|
||||
std::weak_ptr<SimulatedVideoOutRegistration> weak_registration = registration;
|
||||
registration->handle = queue;
|
||||
registration->state = port_state;
|
||||
port_state->queues.push_back(registration);
|
||||
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 8;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add shared port state event");
|
||||
event.filter.owner.reset();
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 8;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DetachSimulatedVideoOutEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add shared port state event");
|
||||
event.filter.owner.reset();
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete shared port state queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete shared port state queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
|
||||
{
|
||||
std::lock_guard lock(port_state->mutex);
|
||||
detached = std::move(port_state->queues);
|
||||
}
|
||||
registration.reset();
|
||||
detached.clear();
|
||||
port_state.reset();
|
||||
Check(!weak_state.expired(),
|
||||
"callback state outlives simulated port object");
|
||||
Check(!weak_registration.expired(),
|
||||
"registration outlives simulated port object");
|
||||
std::vector<std::shared_ptr<SimulatedVideoOutRegistration>> detached;
|
||||
{
|
||||
std::lock_guard lock(port_state->mutex);
|
||||
detached = std::move(port_state->queues);
|
||||
}
|
||||
registration.reset();
|
||||
detached.clear();
|
||||
port_state.reset();
|
||||
Check(!weak_state.expired(), "callback state outlives simulated port object");
|
||||
Check(!weak_registration.expired(), "registration outlives simulated port object");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(weak_registration.expired(), "detached registration is released");
|
||||
Check(weak_state.expired(), "detached event state is released");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"shared event state is destroyed exactly once");
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(weak_registration.expired(), "detached registration is released");
|
||||
Check(weak_state.expired(), "detached event state is released");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"shared event state is destroyed exactly once");
|
||||
}
|
||||
|
||||
struct OwnedCallbackPayload {
|
||||
OwnedCallbackPayload(std::atomic_uint32_t &stage,
|
||||
std::atomic_uint32_t &delete_count,
|
||||
std::atomic_uint32_t &destroy_count)
|
||||
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
|
||||
OwnedCallbackPayload(std::atomic_uint32_t& stage, std::atomic_uint32_t& delete_count,
|
||||
std::atomic_uint32_t& destroy_count)
|
||||
: stage(stage), delete_count(delete_count), destroy_count(destroy_count) {}
|
||||
|
||||
std::atomic_uint32_t &stage;
|
||||
std::atomic_uint32_t &delete_count;
|
||||
std::atomic_uint32_t &destroy_count;
|
||||
uint64_t marker = 0xc0dec0dec0dec0deull;
|
||||
std::atomic_uint32_t& stage;
|
||||
std::atomic_uint32_t& delete_count;
|
||||
std::atomic_uint32_t& destroy_count;
|
||||
uint64_t marker = 0xc0dec0dec0dec0deull;
|
||||
|
||||
~OwnedCallbackPayload() {
|
||||
destroy_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
~OwnedCallbackPayload() { destroy_count.fetch_add(1, std::memory_order_relaxed); }
|
||||
};
|
||||
|
||||
void DeleteOwnedEvent(EventQueue::KernelEqueue queue,
|
||||
EventQueue::KernelEqueueEvent *event) {
|
||||
auto *payload = static_cast<OwnedCallbackPayload *>(event->filter.data);
|
||||
Check(payload != nullptr, "owned callback payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback runs after registry removal");
|
||||
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
event->filter.owner.reset();
|
||||
payload->stage.store(1, std::memory_order_release);
|
||||
while (payload->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Check(payload->marker == 0xc0dec0dec0dec0deull,
|
||||
"owned callback payload remains valid");
|
||||
void DeleteOwnedEvent(EventQueue::KernelEqueue queue, EventQueue::KernelEqueueEvent* event) {
|
||||
auto* payload = static_cast<OwnedCallbackPayload*>(event->filter.data);
|
||||
Check(payload != nullptr, "owned callback payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "owned callback runs after registry removal");
|
||||
payload->delete_count.fetch_add(1, std::memory_order_relaxed);
|
||||
event->filter.owner.reset();
|
||||
payload->stage.store(1, std::memory_order_release);
|
||||
while (payload->stage.load(std::memory_order_acquire) != 2) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
Check(payload->marker == 0xc0dec0dec0dec0deull, "owned callback payload remains valid");
|
||||
}
|
||||
|
||||
void TestCallbackOwnsPayload() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
|
||||
"create owned callback queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "owned-callback") == OK,
|
||||
"create owned callback queue");
|
||||
|
||||
std::atomic_uint32_t stage{0};
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
std::atomic_uint32_t destroy_count{0};
|
||||
auto registration =
|
||||
std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
|
||||
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
|
||||
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations{
|
||||
registration};
|
||||
{
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 2;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DeleteOwnedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add owned callback event");
|
||||
}
|
||||
std::atomic_uint32_t stage {0};
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
std::atomic_uint32_t destroy_count {0};
|
||||
auto registration = std::make_shared<OwnedCallbackPayload>(stage, delete_count, destroy_count);
|
||||
std::weak_ptr<OwnedCallbackPayload> weak_registration = registration;
|
||||
std::vector<std::shared_ptr<OwnedCallbackPayload>> port_registrations {registration};
|
||||
{
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 2;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = registration.get();
|
||||
event.filter.owner = registration;
|
||||
event.filter.delete_event_func = DeleteOwnedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add owned callback event");
|
||||
}
|
||||
|
||||
std::jthread close([&] {
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"delete owned callback queue");
|
||||
});
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
std::jthread close(
|
||||
[&] { Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete owned callback queue"); });
|
||||
while (stage.load(std::memory_order_acquire) != 1) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback queue removed while callback blocked");
|
||||
port_registrations.clear();
|
||||
registration.reset();
|
||||
Check(!weak_registration.expired(),
|
||||
"delete callback retains detached payload");
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"owned callback queue removed while callback blocked");
|
||||
port_registrations.clear();
|
||||
registration.reset();
|
||||
Check(!weak_registration.expired(), "delete callback retains detached payload");
|
||||
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback runs exactly once");
|
||||
Check(weak_registration.expired(),
|
||||
"owned callback payload released with event");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback payload destroyed exactly once");
|
||||
stage.store(2, std::memory_order_release);
|
||||
close.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1, "owned callback runs exactly once");
|
||||
Check(weak_registration.expired(), "owned callback payload released with event");
|
||||
Check(destroy_count.load(std::memory_order_relaxed) == 1,
|
||||
"owned callback payload destroyed exactly once");
|
||||
}
|
||||
|
||||
void TestPinnedClose() {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK,
|
||||
"create pinned queue");
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "pinned-close") == OK, "create pinned queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 1;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = &delete_count;
|
||||
event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 1;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_VIDEO_OUT;
|
||||
event.filter.data = &delete_count;
|
||||
event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add callback event");
|
||||
|
||||
auto owner = EventQueue::KernelPinEqueue(queue);
|
||||
Check(owner != nullptr, "pin live queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"close invokes callback once");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 1,
|
||||
EventQueue::KERNEL_EVFILT_VIDEO_OUT,
|
||||
nullptr) == KERNEL_ERROR_EBADF,
|
||||
"stale trigger rejected");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
|
||||
"second queue delete rejected");
|
||||
auto owner = EventQueue::KernelPinEqueue(queue);
|
||||
Check(owner != nullptr, "pin live queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "delete pinned queue");
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1, "close invokes callback once");
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "deleted queue leaves registry");
|
||||
Check(EventQueue::KernelTriggerEvent(queue, 1, EventQueue::KERNEL_EVFILT_VIDEO_OUT, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"stale trigger rejected");
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == KERNEL_ERROR_EBADF,
|
||||
"second queue delete rejected");
|
||||
|
||||
owner.reset();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"deferred destruction does not repeat callback");
|
||||
owner.reset();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"deferred destruction does not repeat callback");
|
||||
}
|
||||
|
||||
void TestStaleHandleNeverAliasesNewQueue() {
|
||||
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK,
|
||||
"create stale queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
|
||||
EventQueue::KernelEqueue stale = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&stale, "stale-handle") == OK, "create stale queue");
|
||||
Check(EventQueue::KernelDeleteEqueue(stale) == OK, "delete stale queue");
|
||||
|
||||
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
|
||||
"create replacement queue");
|
||||
Check(stale != replacement, "queue handles are never recycled");
|
||||
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
|
||||
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot mutate replacement");
|
||||
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
|
||||
"replacement handle remains valid");
|
||||
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) ==
|
||||
KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot trigger replacement");
|
||||
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
|
||||
"replacement event triggers");
|
||||
Check(EventQueue::KernelDeleteEqueue(replacement) == OK,
|
||||
"delete replacement queue");
|
||||
EventQueue::KernelEqueue replacement = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&replacement, "replacement") == OK,
|
||||
"create replacement queue");
|
||||
Check(stale != replacement, "queue handles are never recycled");
|
||||
Check(!EventQueue::KernelPinEqueue(stale), "stale handle does not pin");
|
||||
Check(EventQueue::KernelAddUserEvent(stale, 11) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot mutate replacement");
|
||||
Check(EventQueue::KernelAddUserEvent(replacement, 11) == OK,
|
||||
"replacement handle remains valid");
|
||||
Check(EventQueue::KernelTriggerUserEvent(stale, 11, nullptr) == KERNEL_ERROR_EBADF,
|
||||
"stale handle cannot trigger replacement");
|
||||
Check(EventQueue::KernelTriggerUserEvent(replacement, 11, nullptr) == OK,
|
||||
"replacement event triggers");
|
||||
Check(EventQueue::KernelDeleteEqueue(replacement) == OK, "delete replacement queue");
|
||||
}
|
||||
|
||||
void TestConcurrentCloseCallback() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
|
||||
"create callback race queue");
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "callback-race") == OK,
|
||||
"create callback race queue");
|
||||
|
||||
std::atomic_uint32_t delete_count{0};
|
||||
EventQueue::KernelEqueueEvent callback_event{};
|
||||
callback_event.event.ident = 9;
|
||||
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
callback_event.filter.data = &delete_count;
|
||||
callback_event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK,
|
||||
"add callback race event");
|
||||
std::atomic_uint32_t delete_count {0};
|
||||
EventQueue::KernelEqueueEvent callback_event {};
|
||||
callback_event.event.ident = 9;
|
||||
callback_event.event.filter = EventQueue::KERNEL_EVFILT_GRAPHICS;
|
||||
callback_event.filter.data = &delete_count;
|
||||
callback_event.filter.delete_event_func = CountDeletedEvent;
|
||||
Check(EventQueue::KernelAddEvent(queue, callback_event) == OK, "add callback race event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
|
||||
"callback race trigger result");
|
||||
}
|
||||
});
|
||||
std::atomic_bool start {false};
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 9, EventQueue::KERNEL_EVFILT_GRAPHICS, nullptr),
|
||||
"callback race trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"callback race queue delete");
|
||||
trigger.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent close invokes callback exactly once");
|
||||
}
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "callback race queue delete");
|
||||
trigger.join();
|
||||
Check(delete_count.load(std::memory_order_relaxed) == 1,
|
||||
"concurrent close invokes callback exactly once");
|
||||
}
|
||||
}
|
||||
|
||||
void TestConcurrentDelete() {
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
|
||||
"create concurrent queue");
|
||||
EventQueue::KernelEqueueEvent event{};
|
||||
event.event.ident = 7;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK,
|
||||
"add concurrent event");
|
||||
for (uint32_t iteration = 0; iteration < 64; iteration++) {
|
||||
EventQueue::KernelEqueue queue = EventQueue::KERNEL_EQUEUE_INVALID;
|
||||
Check(EventQueue::KernelCreateEqueue(&queue, "concurrent-delete") == OK,
|
||||
"create concurrent queue");
|
||||
EventQueue::KernelEqueueEvent event {};
|
||||
event.event.ident = 7;
|
||||
event.event.filter = EventQueue::KERNEL_EVFILT_USER;
|
||||
Check(EventQueue::KernelAddEvent(queue, event) == OK, "add concurrent event");
|
||||
|
||||
std::atomic_bool start{false};
|
||||
std::jthread mutate([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
|
||||
"concurrent add result");
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"concurrent trigger result");
|
||||
CheckConcurrentResult(EventQueue::KernelDeleteEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER),
|
||||
"concurrent event delete result");
|
||||
}
|
||||
});
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"parallel trigger result");
|
||||
}
|
||||
});
|
||||
std::atomic_bool start {false};
|
||||
std::jthread mutate([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelAddEvent(queue, event),
|
||||
"concurrent add result");
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"concurrent trigger result");
|
||||
CheckConcurrentResult(
|
||||
EventQueue::KernelDeleteEvent(queue, 7, EventQueue::KERNEL_EVFILT_USER),
|
||||
"concurrent event delete result");
|
||||
}
|
||||
});
|
||||
std::jthread trigger([&] {
|
||||
while (!start.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (uint32_t i = 0; i < 128; i++) {
|
||||
CheckConcurrentResult(EventQueue::KernelTriggerEvent(
|
||||
queue, 7, EventQueue::KERNEL_EVFILT_USER, nullptr),
|
||||
"parallel trigger result");
|
||||
}
|
||||
});
|
||||
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK,
|
||||
"concurrent queue delete");
|
||||
mutate.join();
|
||||
trigger.join();
|
||||
Check(!EventQueue::KernelPinEqueue(queue),
|
||||
"concurrent queue removed from registry");
|
||||
}
|
||||
start.store(true, std::memory_order_release);
|
||||
Check(EventQueue::KernelDeleteEqueue(queue) == OK, "concurrent queue delete");
|
||||
mutate.join();
|
||||
trigger.join();
|
||||
Check(!EventQueue::KernelPinEqueue(queue), "concurrent queue removed from registry");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
TestPinnedClose();
|
||||
TestStaleHandleNeverAliasesNewQueue();
|
||||
TestConcurrentCloseCallback();
|
||||
TestConcurrentDelete();
|
||||
std::printf("EventQueueLifetimeTests: all cases passed\n");
|
||||
return 0;
|
||||
TestDuplicateAddPreservesEventState();
|
||||
TestCallbackStateOutlivesPort();
|
||||
TestCallbackOwnsPayload();
|
||||
TestPinnedClose();
|
||||
TestStaleHandleNeverAliasesNewQueue();
|
||||
TestConcurrentCloseCallback();
|
||||
TestConcurrentDelete();
|
||||
std::printf("EventQueueLifetimeTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using OwnerIndex = Libs::Graphics::MultiRangePageOwnerIndex<uint32_t>;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
@@ -24,23 +24,26 @@ void TestMultiOwnerAndExactErase() {
|
||||
owners.push_back(11);
|
||||
owners.push_back(22);
|
||||
|
||||
Check(table.Find(17) != nullptr && table.Find(17)->size() == 2, "both page owners are retained");
|
||||
Check(table.Find(17) != nullptr && table.Find(17)->size() == 2,
|
||||
"both page owners are retained");
|
||||
Check(Libs::Graphics::EraseExact(owners, 11U), "registered owner is erased");
|
||||
Check(owners.size() == 1 && owners.front() == 22, "erasing one owner preserves its neighbor");
|
||||
Check(!Libs::Graphics::EraseExact(owners, 33U), "missing owner is reported without mutation");
|
||||
}
|
||||
|
||||
void TestCrossBucketRange() {
|
||||
Table::PageRange range{};
|
||||
constexpr uint64_t bucket_boundary = uint64_t{Table::kBucketEntries} << Table::kPageBits;
|
||||
Table::PageRange range {};
|
||||
constexpr uint64_t bucket_boundary = uint64_t {Table::kBucketEntries} << Table::kPageBits;
|
||||
Check(Table::TryGetPageRange(bucket_boundary - 1, 2, range), "cross-bucket range is valid");
|
||||
Check(range.first == Table::kBucketEntries - 1 && range.last_exclusive == Table::kBucketEntries + 1,
|
||||
Check(range.first == Table::kBucketEntries - 1 &&
|
||||
range.last_exclusive == Table::kBucketEntries + 1,
|
||||
"cross-bucket range covers both pages");
|
||||
|
||||
Table table;
|
||||
table[range.first].push_back(1);
|
||||
table[range.last_exclusive - 1].push_back(2);
|
||||
Check(table.AllocatedBucketCount() == 2, "pages across the L1 boundary use distinct sparse buckets");
|
||||
Check(table.AllocatedBucketCount() == 2,
|
||||
"pages across the L1 boundary use distinct sparse buckets");
|
||||
}
|
||||
|
||||
void TestQueriesDoNotAllocate() {
|
||||
@@ -55,27 +58,34 @@ void TestQueriesDoNotAllocate() {
|
||||
}
|
||||
|
||||
void TestAddressSpaceBoundaries() {
|
||||
Table::PageRange range{};
|
||||
Check(Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 1, range), "last guest byte is valid");
|
||||
Table::PageRange range {};
|
||||
Check(Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 1, range),
|
||||
"last guest byte is valid");
|
||||
Check(range.first == Table::kPageCount - 1 && range.last_exclusive == Table::kPageCount,
|
||||
"last guest byte maps to the final page");
|
||||
Check(!Table::TryGetPageRange(0, 0, range), "empty ranges are rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize, 1, range), "first out-of-range byte is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 2, range), "crossing the address-space end is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize, 1, range),
|
||||
"first out-of-range byte is rejected");
|
||||
Check(!Table::TryGetPageRange(Table::kAddressSpaceSize - 1, 2, range),
|
||||
"crossing the address-space end is rejected");
|
||||
Check(!Table::TryGetPageRange(UINT64_MAX - 1, 4, range), "wrapping input is rejected");
|
||||
|
||||
Table table;
|
||||
table.GetOrCreate(Table::kPageCount - 1).push_back(99);
|
||||
Check(table.Find(Table::kPageCount - 1) != nullptr && table.Find(Table::kPageCount - 1)->front() == 99,
|
||||
Check(table.Find(Table::kPageCount - 1) != nullptr &&
|
||||
table.Find(Table::kPageCount - 1)->front() == 99,
|
||||
"final page supports allocating and nonallocating access");
|
||||
}
|
||||
|
||||
void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
OwnerIndex index;
|
||||
// Depth and stencil-like planes overlap tracking pages and share one 1 MiB bucket.
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}), "multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1, "one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1, "overlapping planes insert one 4 KiB membership");
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}),
|
||||
"multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1,
|
||||
"one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1,
|
||||
"overlapping planes insert one 4 KiB membership");
|
||||
Check(!index.Register(7, {{0x101000, 0x1000}}), "duplicate owner registration hard-fails");
|
||||
|
||||
const auto owners = index.Query(0x100000, 0x10000);
|
||||
@@ -83,9 +93,10 @@ void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
}
|
||||
|
||||
void TestSharedPageUnregisterLifecycle() {
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges{{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges), "two owners register on identical pages");
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges {{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges),
|
||||
"two owners register on identical pages");
|
||||
Check(index.CoarseMembershipCount(2) == 2 && index.TrackingMembershipCount(0x202) == 2,
|
||||
"coarse and tracking pages retain both owners");
|
||||
|
||||
@@ -97,7 +108,8 @@ void TestSharedPageUnregisterLifecycle() {
|
||||
Check(!index.Unregister(11, releases), "missing membership hard-fails without mutation");
|
||||
|
||||
Check(index.Unregister(22, releases), "final owner unregisters");
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 && releases.front().size == 0x2000,
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 &&
|
||||
releases.front().size == 0x2000,
|
||||
"adjacent final-owner tracking pages return one contiguous release");
|
||||
}
|
||||
|
||||
@@ -105,14 +117,19 @@ void TestStrictByteFilteringAndPredicate() {
|
||||
OwnerIndex index;
|
||||
Check(index.Register(31, {{0x300100, 0x100}}), "first byte-disjoint owner registers");
|
||||
Check(index.Register(32, {{0x300800, 0x100}}), "second byte-disjoint owner registers");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2, "byte-disjoint owners share one tracking page");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2,
|
||||
"byte-disjoint owners share one tracking page");
|
||||
Check(index.Query(0x300400, 0x40).empty(), "page hit without byte overlap is filtered out");
|
||||
const auto page_candidates = index.QueryCandidates(0x300400, 0x40);
|
||||
Check(page_candidates.size() == 2, "fault candidate query retains byte-disjoint owners on the touched page");
|
||||
Check(page_candidates.size() == 2,
|
||||
"fault candidate query retains byte-disjoint owners on the touched page");
|
||||
const auto first = index.Query(0x300180, 0x10);
|
||||
Check(first.size() == 1 && first.front() == 31, "strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered = index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32, "supplied predicate filters query owners");
|
||||
Check(first.size() == 1 && first.front() == 31,
|
||||
"strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered =
|
||||
index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32,
|
||||
"supplied predicate filters query owners");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
+967
-1053
File diff suppressed because it is too large
Load Diff
+578
-626
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -36,7 +36,7 @@ void YieldMany() {
|
||||
}
|
||||
|
||||
void TestFaultBlocksPublisher() {
|
||||
ResourceMutex mutex;
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
@@ -67,7 +67,7 @@ void TestFaultDrainsExistingOwner() {
|
||||
std::atomic_bool release_fault {false};
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread fault([&] {
|
||||
std::thread fault([&] {
|
||||
fault_started.store(true, std::memory_order_release);
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
fault_entered.store(true, std::memory_order_release);
|
||||
@@ -111,7 +111,7 @@ void TestFaultScopesSerialize() {
|
||||
std::atomic_bool release_first {false};
|
||||
std::atomic_bool release_second {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread first([&] {
|
||||
std::thread first([&] {
|
||||
ResourceMutex::FaultScope scope(mutex);
|
||||
first_entered.store(true, std::memory_order_release);
|
||||
while (!release_first.load(std::memory_order_acquire)) {
|
||||
@@ -155,7 +155,7 @@ void TestFaultScopesSerialize() {
|
||||
}
|
||||
|
||||
void TestPreownedFaultKeepsResourceTransaction() {
|
||||
ResourceMutex mutex;
|
||||
ResourceMutex mutex;
|
||||
std::atomic_bool publisher_started {false};
|
||||
std::atomic_bool publisher_entered {false};
|
||||
std::thread publisher;
|
||||
@@ -199,10 +199,10 @@ void TestPreownedFaultKeepsResourceTransaction() {
|
||||
void CheckDeathCase(const char* name) {
|
||||
char path[MAX_PATH] {};
|
||||
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
std::vector<char> mutable_command(command.begin(), command.end());
|
||||
mutable_command.push_back('\0');
|
||||
STARTUPINFOA startup {sizeof(startup)};
|
||||
STARTUPINFOA startup {sizeof(startup)};
|
||||
PROCESS_INFORMATION process {};
|
||||
Check(CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
|
||||
nullptr, nullptr, &startup, &process) != 0,
|
||||
|
||||
+1686
-1858
File diff suppressed because it is too large
Load Diff
@@ -199,13 +199,13 @@ void TestNestedLoopPhiConvergence() {
|
||||
program.blocks[3].predecessors = {2};
|
||||
program.blocks[3].successors = {1};
|
||||
Instruction increment;
|
||||
increment.op = Opcode::IAddU32;
|
||||
increment.dst = Sgpr(0);
|
||||
increment.src[0] = Sgpr(0);
|
||||
increment.src[1] = Imm(1);
|
||||
increment.src_count = 2;
|
||||
program.blocks[0].instructions = {increment};
|
||||
program.blocks[2].instructions = {BufferUse(4, 0)};
|
||||
increment.op = Opcode::IAddU32;
|
||||
increment.dst = Sgpr(0);
|
||||
increment.src[0] = Sgpr(0);
|
||||
increment.src[1] = Imm(1);
|
||||
increment.src_count = 2;
|
||||
program.blocks[0].instructions = {increment};
|
||||
program.blocks[2].instructions = {BufferUse(4, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -443,19 +443,19 @@ void TestReadLaneDescriptorSpill() {
|
||||
program.blocks[0].instructions.push_back(MoveImmediate(0xb0, 84, 0));
|
||||
program.blocks[0].instructions.push_back(ReadLane(0x510, 0, 11, 0));
|
||||
Instruction sample;
|
||||
sample.pc = 0x868;
|
||||
sample.op = Opcode::ImageSample;
|
||||
sample.memory.kind = ResourceKind::Image;
|
||||
sample.memory.resource = 0;
|
||||
sample.memory.sampler = 4;
|
||||
sample.pc = 0x868;
|
||||
sample.op = Opcode::ImageSample;
|
||||
sample.memory.kind = ResourceKind::Image;
|
||||
sample.memory.resource = 0;
|
||||
sample.memory.sampler = 4;
|
||||
sample.memory.image_dimension =
|
||||
Libs::Graphics::ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
program.blocks[0].instructions.push_back(sample);
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
const auto source_id = program.blocks[0].instructions.back().memory.resource_source;
|
||||
const auto* source = GetDescriptorSource(program, source_id);
|
||||
const auto source_id = program.blocks[0].instructions.back().memory.resource_source;
|
||||
const auto* source = GetDescriptorSource(program, source_id);
|
||||
Check(source != nullptr && DescriptorSourceResolved(program, source_id),
|
||||
"readlane descriptor spill was not resolved");
|
||||
Check(Value(program, source->dwords[0]).op == ScalarValueOp::ReadConstBuffer,
|
||||
@@ -463,7 +463,7 @@ void TestReadLaneDescriptorSpill() {
|
||||
std::vector<uint32_t> user_data(28);
|
||||
SetPointer(&user_data, 24, table.data());
|
||||
user_data[26] = sizeof(table);
|
||||
DescriptorValue descriptor;
|
||||
DescriptorValue descriptor;
|
||||
const SrtRuntime runtime {user_data, 0, ReadHostMemory, nullptr};
|
||||
Check(EvaluateDescriptorSource(program, source_id, sample.pc, runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
@@ -477,13 +477,13 @@ void TestReadLaneVectorOverwriteInvalidatesSpill() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 0), overwrite,
|
||||
ReadLane(8, 0, 11, 0), BufferUse(12, 0)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 0), overwrite, ReadLane(8, 0, 11, 0),
|
||||
BufferUse(12, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -545,13 +545,13 @@ void TestReadLaneWideAndRelativeWritesInvalidateSpill() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = op;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 12, 4, 0), overwrite,
|
||||
ReadLane(8, 0, 12, 0), BufferUse(12, 0)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = op;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 12, 4, 0), overwrite, ReadLane(8, 0, 12, 0),
|
||||
BufferUse(12, 0)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -585,12 +585,12 @@ void TestReadLaneModuloAndDynamicLane() {
|
||||
dynamic.wave_size = 64;
|
||||
dynamic.user_data_count = 8;
|
||||
dynamic.blocks.resize(1);
|
||||
auto write = WriteLane(0, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto write = WriteLane(0, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
dynamic.blocks[0].instructions = {write, ReadLane(4, 0, 11, 0), BufferUse(8, 0)};
|
||||
Check(BuildScalarProvenance(dynamic, &error), error.c_str());
|
||||
Check(!DescriptorSourceResolved(
|
||||
dynamic, dynamic.blocks[0].instructions.back().memory.resource_source),
|
||||
Check(!DescriptorSourceResolved(dynamic,
|
||||
dynamic.blocks[0].instructions.back().memory.resource_source),
|
||||
"dynamic writelane selector retained unsafe lane provenance");
|
||||
}
|
||||
|
||||
@@ -599,9 +599,8 @@ void TestReadLaneEliminationSnapshotsWriteValue() {
|
||||
program.wave_size = 64;
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
program.blocks[0].instructions = {
|
||||
MoveImmediate(0, 4, 0x12345678u), WriteLane(4, 11, 4, 4),
|
||||
MoveImmediate(8, 4, 0xdeadbeefu), ReadLane(12, 0, 11, 4)};
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 4, 0x12345678u), WriteLane(4, 11, 4, 4),
|
||||
MoveImmediate(8, 4, 0xdeadbeefu), ReadLane(12, 0, 11, 4)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -675,13 +674,12 @@ void TestReadLaneEliminationHonorsVectorInvalidation() {
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
Instruction overwrite;
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 4), overwrite,
|
||||
ReadLane(8, 0, 11, 4)};
|
||||
overwrite.pc = 4;
|
||||
overwrite.op = Opcode::MoveU32;
|
||||
overwrite.dst = Vgpr(11);
|
||||
overwrite.src[0] = Imm(0);
|
||||
overwrite.src_count = 1;
|
||||
program.blocks[0].instructions = {WriteLane(0, 11, 4, 4), overwrite, ReadLane(8, 0, 11, 4)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error), error.c_str());
|
||||
@@ -696,10 +694,10 @@ void TestReadLaneEliminationFoldsScalarLaneSelector() {
|
||||
program.wave_size = 32;
|
||||
program.user_data_count = 8;
|
||||
program.blocks.resize(1);
|
||||
auto write = WriteLane(4, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto read = ReadLane(8, 0, 11, 0);
|
||||
read.src[1] = Sgpr(7);
|
||||
auto write = WriteLane(4, 11, 4, 0);
|
||||
write.src[1] = Sgpr(7);
|
||||
auto read = ReadLane(8, 0, 11, 0);
|
||||
read.src[1] = Sgpr(7);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 7, 33), write, read};
|
||||
|
||||
std::string error;
|
||||
@@ -840,8 +838,7 @@ void TestDynamicReadIsNotFlattened() {
|
||||
DescriptorValue descriptor;
|
||||
const auto source = program.blocks[0].instructions[1].memory.resource_source;
|
||||
const SrtRuntime runtime {user_data, 0, ReadHostMemory, nullptr};
|
||||
Check(EvaluateDescriptorSource(program, source, 4, runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(EvaluateDescriptorSource(program, source, 4, runtime, descriptor, &error), error.c_str());
|
||||
Check(descriptor.dwords[0] == table[1], "dynamic ReadConst evaluated the wrong dword");
|
||||
}
|
||||
|
||||
@@ -1144,46 +1141,43 @@ void TestCommonScalarPointerOps() {
|
||||
void TestBitFieldMaskDescriptor() {
|
||||
Program program;
|
||||
program.blocks.resize(1);
|
||||
auto mask = MoveImmediate(4, 29, 0);
|
||||
mask.op = Opcode::BitFieldMaskU32;
|
||||
mask.src[0] = Imm(12);
|
||||
mask.src[1] = Imm(12);
|
||||
mask.src_count = 2;
|
||||
auto high = MoveImmediate(8, 30, 0x05500000u);
|
||||
high.op = Opcode::MoveU64;
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
auto mask = MoveImmediate(4, 29, 0);
|
||||
mask.op = Opcode::BitFieldMaskU32;
|
||||
mask.src[0] = Imm(12);
|
||||
mask.src[1] = Imm(12);
|
||||
mask.src_count = 2;
|
||||
auto high = MoveImmediate(8, 30, 0x05500000u);
|
||||
high.op = Opcode::MoveU64;
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
|
||||
std::string error;
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
DescriptorValue descriptor;
|
||||
DescriptorValue descriptor;
|
||||
const SrtRuntime runtime {{}, 0, nullptr, nullptr};
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
16, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 16,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[0] == 0x92u && descriptor.dwords[1] == 0x00fff000u &&
|
||||
descriptor.dwords[2] == 0x05500000u && descriptor.dwords[3] == 0,
|
||||
"production sampler bit-field mask evaluated incorrectly");
|
||||
|
||||
mask.src[0] = Imm(0);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
mask.src[0] = Imm(0);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
12, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 12,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[1] == 0, "zero-width bit-field mask was not zero");
|
||||
|
||||
mask.src[0] = Imm(31);
|
||||
mask.src[1] = Imm(31);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high,
|
||||
BufferUse(12, 28)};
|
||||
mask.src[0] = Imm(31);
|
||||
mask.src[1] = Imm(31);
|
||||
program.blocks[0].instructions = {MoveImmediate(0, 28, 0x92u), mask, high, BufferUse(12, 28)};
|
||||
Check(BuildScalarProvenance(program, &error) && BuildSrtPlan(program, &error), error.c_str());
|
||||
Check(EvaluateDescriptorSource(program,
|
||||
program.blocks[0].instructions.back().memory.resource_source,
|
||||
12, runtime, descriptor, &error),
|
||||
program.blocks[0].instructions.back().memory.resource_source, 12,
|
||||
runtime, descriptor, &error),
|
||||
error.c_str());
|
||||
Check(descriptor.dwords[1] == 0x80000000u,
|
||||
"maximum bit-field mask count/offset evaluated incorrectly");
|
||||
|
||||
+15146
-16525
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -24,12 +23,12 @@ std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program> SrtProgram(
|
||||
program.srt_patching_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
program.provenance.values.resize(6);
|
||||
program.provenance.values[0].op = ScalarValueOp::Undefined;
|
||||
program.provenance.values[1].op = ScalarValueOp::Unknown;
|
||||
program.provenance.values[2].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[2].imm = static_cast<uint32_t>(address);
|
||||
program.provenance.values[3].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[3].imm = static_cast<uint32_t>(address >> 32u);
|
||||
program.provenance.values[0].op = ScalarValueOp::Undefined;
|
||||
program.provenance.values[1].op = ScalarValueOp::Unknown;
|
||||
program.provenance.values[2].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[2].imm = static_cast<uint32_t>(address);
|
||||
program.provenance.values[3].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[3].imm = static_cast<uint32_t>(address >> 32u);
|
||||
program.provenance.values[4].op = ScalarValueOp::Constant;
|
||||
program.provenance.values[5].op = ScalarValueOp::ReadConst;
|
||||
program.provenance.values[5].args = {2, 3, 4};
|
||||
@@ -54,15 +53,14 @@ std::shared_ptr<const Libs::Graphics::ShaderRecompiler::IR::Program> UnbasedFlat
|
||||
|
||||
void TestMappedSrtUsesDirectReaderByDefault() {
|
||||
using namespace Libs::Graphics;
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
|
||||
const uint32_t dword = 0x12345678;
|
||||
auto cached_program = SrtProgram(reinterpret_cast<uint64_t>(&dword));
|
||||
ShaderStageRuntime stage;
|
||||
std::string error;
|
||||
std::string error;
|
||||
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
|
||||
Check(stage.program == cached_program && stage.resources != nullptr,
|
||||
"cache rematerialization did not publish the mapped stage");
|
||||
Check(stage.resources->flattened_srt.size() == 1 &&
|
||||
stage.resources->flattened_srt[0] == dword,
|
||||
Check(stage.resources->flattened_srt.size() == 1 && stage.resources->flattened_srt[0] == dword,
|
||||
"cache rematerialization did not use the direct reader by default");
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ void TestUnbasedFlatCacheHitFailsClosed() {
|
||||
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
|
||||
auto prior_resources = std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
ShaderStageRuntime stage {prior_program, prior_resources};
|
||||
std::string error;
|
||||
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");
|
||||
|
||||
@@ -18,19 +18,19 @@ void Check(bool value, const char* text) {
|
||||
|
||||
struct Fixture {
|
||||
std::array<uint16_t, static_cast<size_t>(AgcDirectResourceType::Last) + 1> offsets {};
|
||||
ShaderUserData user_data {};
|
||||
ShaderSemantic semantic {};
|
||||
ShaderMappedData mapped {};
|
||||
ShaderUserData user_data {};
|
||||
ShaderSemantic semantic {};
|
||||
ShaderMappedData mapped {};
|
||||
|
||||
Fixture() {
|
||||
offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET);
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 2;
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 2;
|
||||
offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexAttribDescTable)] = 4;
|
||||
user_data.direct_resource_offset = offsets.data();
|
||||
user_data.direct_resource_count = static_cast<uint16_t>(offsets.size());
|
||||
mapped.user_data = &user_data;
|
||||
mapped.input_semantics = &semantic;
|
||||
mapped.num_input_semantics = 1;
|
||||
mapped.user_data = &user_data;
|
||||
mapped.input_semantics = &semantic;
|
||||
mapped.num_input_semantics = 1;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,9 +48,9 @@ void CheckRejected(const ShaderMappedData& data, const char* text) {
|
||||
}
|
||||
|
||||
void TestValidAndInvalidMetadata() {
|
||||
Fixture fixture;
|
||||
Fixture fixture;
|
||||
ShaderVertexMetadata output;
|
||||
std::string error;
|
||||
std::string error;
|
||||
Check(ShaderReadVertexMetadata(fixture.mapped, 64, output, &error),
|
||||
"valid AGC vertex metadata was rejected");
|
||||
Check(output.vertex_buffer_reg == 2 && output.vertex_attrib_reg == 4 &&
|
||||
@@ -75,8 +75,8 @@ void TestValidAndInvalidMetadata() {
|
||||
CheckRejected(excessive_semantics.mapped, "excessive vertex semantic count was accepted");
|
||||
|
||||
Fixture excessive_register;
|
||||
excessive_register.offsets[
|
||||
static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 63;
|
||||
excessive_register.offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] =
|
||||
63;
|
||||
CheckRejected(excessive_register.mapped, "out-of-domain vertex table SGPR was accepted");
|
||||
|
||||
Fixture missing_semantics;
|
||||
|
||||
@@ -107,12 +107,11 @@ void InitSubsystems() {
|
||||
slist->Add(log, {core, config});
|
||||
Check("InitSubsystems", slist->InitAll(false), "failed to initialize logging subsystem");
|
||||
|
||||
const auto param_json =
|
||||
std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
const auto param_json = std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
constexpr char json[] = R"({"kernel":{"flexibleMemorySize":3221225472}})";
|
||||
Common::File param_file;
|
||||
Common::File param_file;
|
||||
Check("InitSubsystems", param_file.Create(param_json), "failed to create temporary param.json");
|
||||
uint32_t bytes_written = 0;
|
||||
param_file.Write(json, sizeof(json) - 1, &bytes_written);
|
||||
@@ -247,8 +246,8 @@ void TestGuestAddressSpaceOwnsReservationsBeforeBacking() {
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
"semantic reservation replaced the owner's placeholder");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize, Common::VirtualMemory::Mode::NoAccess),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::NoAccess),
|
||||
"owner rejected a sparse placeholder protection no-op");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
@@ -402,8 +401,7 @@ void TestFlexibleDmemCompatAndAlignmentFlags() {
|
||||
Check(test, stack_start == nullptr && stack_end == nullptr,
|
||||
"DMEM_COMPAT flexible mapping was reported as a stack");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize),
|
||||
"KernelMunmap");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"DMEM_COMPAT cleanup did not restore flexible capacity");
|
||||
|
||||
@@ -443,8 +441,8 @@ void TestFlexibleNoCoalescePreservesBoundaries() {
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
void* reserve = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(
|
||||
&reserve, SceKernelPageSize * 2, 0, SceKernelPageSize),
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, SceKernelPageSize * 2, 0,
|
||||
SceKernelPageSize),
|
||||
"KernelReserveVirtualRange");
|
||||
const auto base = reinterpret_cast<uint64_t>(reserve);
|
||||
|
||||
@@ -601,13 +599,12 @@ void TestRuntimeMemoryOwnerLifecycle() {
|
||||
Common::VirtualMemory::Mode::ReadWrite, "runtime_adjacent_second", true);
|
||||
Check(test, adjacent_second == adjacent_first + SceKernelPageSize,
|
||||
"second adjacent runtime allocation failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::FreeGuestMemory(adjacent_first, SceKernelPageSize * 2),
|
||||
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(adjacent_first, SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first,
|
||||
SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free did not restore one owner placeholder");
|
||||
Check(
|
||||
test,
|
||||
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first, SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free did not restore one owner placeholder");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
@@ -877,12 +874,12 @@ void TestDirectPartialProtectUnmapPreservesNeighbors() {
|
||||
SceKernelPageSize, SceKernelProtCpuRead),
|
||||
"KernelMprotect(middle)");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::Read),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, size,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect fragmented backing views");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::ReadWrite),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, size,
|
||||
Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore fragmented backing views");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize),
|
||||
@@ -1091,12 +1088,12 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
|
||||
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0x100, 0x200) == 0x200,
|
||||
"ClampRangeSize did not cross adjacent committed mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::Read),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize * 2,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect adjacent backing mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite),
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(base, SceKernelPageSize * 2,
|
||||
Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore adjacent backing mappings");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
|
||||
+63
-73
@@ -4,22 +4,22 @@
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
|
||||
#include "graphics/shader/recompiler/ExecMask.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
|
||||
#include "graphics/shader/recompiler/ir/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "libs/agc.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
@@ -411,9 +411,9 @@ constexpr uint32_t EncodeSopp(uint32_t opcode, uint32_t simm = 0) {
|
||||
}
|
||||
|
||||
void TestNativeShaderResourceDependencies() {
|
||||
const auto stages = ShaderPipelineStages(
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
const auto stages =
|
||||
ShaderPipelineStages(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
Check(stages == (vk::PipelineStageFlagBits::eVertexShader |
|
||||
vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eComputeShader),
|
||||
@@ -463,8 +463,7 @@ void TestNormalizedImageContracts() {
|
||||
ImageInfo container {};
|
||||
container.data = {0x10000, 0x15000};
|
||||
container.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
||||
container.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
container.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm);
|
||||
container.type = Prospero::ImageType::kColor2D;
|
||||
container.extent = {64, 64, 1};
|
||||
container.resources = {3, 4};
|
||||
@@ -476,38 +475,34 @@ void TestNormalizedImageContracts() {
|
||||
container.mip_layout[1] = {0x10000, 0x4000, 32, 32};
|
||||
container.mip_layout[2] = {0x14000, 0x1000, 16, 16};
|
||||
|
||||
ImageInfo subresource = container;
|
||||
subresource.data = {0x22000, 0x1000};
|
||||
subresource.extent = {32, 32, 1};
|
||||
subresource.resources = {1, 1};
|
||||
subresource.pitch = 32;
|
||||
subresource.mip_layout = {};
|
||||
ImageInfo subresource = container;
|
||||
subresource.data = {0x22000, 0x1000};
|
||||
subresource.extent = {32, 32, 1};
|
||||
subresource.resources = {1, 1};
|
||||
subresource.pitch = 32;
|
||||
subresource.mip_layout = {};
|
||||
subresource.mip_layout[0] = {0, 0x1000, 32, 32};
|
||||
|
||||
Check(container.BlockExtent() == vk::Extent2D {64, 64},
|
||||
"normalized image block extent changed");
|
||||
Check(subresource.IsCompatible(container), "normalized compatible image was rejected");
|
||||
Check(subresource.MipOf(container) == 1, "normalized mip lookup missed a subresource");
|
||||
Check(subresource.SliceOf(container, 1) == 2,
|
||||
"normalized slice lookup missed a subresource");
|
||||
Check(subresource.SliceOf(container, 1) == 2, "normalized slice lookup missed a subresource");
|
||||
|
||||
auto incompatible = subresource;
|
||||
auto incompatible = subresource;
|
||||
incompatible.samples = 2;
|
||||
Check(!incompatible.IsCompatible(container) && incompatible.MipOf(container) == -1,
|
||||
"sample-count mismatch was accepted as a compatible image");
|
||||
|
||||
auto compressed = container;
|
||||
compressed.guest_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm);
|
||||
compressed.pitch = 128;
|
||||
compressed.extent.height = 64;
|
||||
auto compressed = container;
|
||||
compressed.guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::kBc3UNorm);
|
||||
compressed.pitch = 128;
|
||||
compressed.extent.height = 64;
|
||||
Check(compressed.BlockExtent() == vk::Extent2D {32, 16},
|
||||
"block-compressed extent was not expressed in blocks");
|
||||
|
||||
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm,
|
||||
vk::Format::eR8G8B8A8Uint) &&
|
||||
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat,
|
||||
vk::Format::eR32Sfloat) &&
|
||||
Check(ImageViewOps::FormatsCompatible(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Uint) &&
|
||||
!ImageViewOps::FormatsCompatible(vk::Format::eD32Sfloat, vk::Format::eR32Sfloat) &&
|
||||
ImageViewOps::FormatsCompatible(vk::Format::eBc3UnormBlock,
|
||||
vk::Format::eR32G32B32A32Uint),
|
||||
"Vulkan image-view compatibility classes diverged from production");
|
||||
@@ -524,20 +519,19 @@ void TestNativeSubgroupPolicy() {
|
||||
safe.wave_size = 32;
|
||||
safe.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::Natural,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::Natural,
|
||||
"native wave32 policy changed");
|
||||
safe.wave_size = 64;
|
||||
Check(SelectGraphicsLaneMaskMode(safe.wave_size) ==
|
||||
ShaderLaneMaskMode::PerInvocation &&
|
||||
Check(SelectGraphicsLaneMaskMode(safe.wave_size) == ShaderLaneMaskMode::PerInvocation &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"wave64 graphics mismatch accepted native-wave mask lowering");
|
||||
safe.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, safe).mode ==
|
||||
ShaderSubgroupMode::PerInvocationGraphics,
|
||||
vk::ShaderStageFlagBits::eVertex, safe)
|
||||
.mode == ShaderSubgroupMode::PerInvocationGraphics,
|
||||
"wave64 graphics mismatch did not select per-invocation masks");
|
||||
|
||||
ShaderRecompiler::IR::Program cross_lane = safe;
|
||||
@@ -545,14 +539,14 @@ void TestNativeSubgroupPolicy() {
|
||||
ShaderRecompiler::IR::Opcode::ReadLaneU32;
|
||||
Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(cross_lane) &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eVertex, cross_lane).mode ==
|
||||
ShaderSubgroupMode::PerInvocationGraphics,
|
||||
vk::ShaderStageFlagBits::eVertex, cross_lane)
|
||||
.mode == ShaderSubgroupMode::PerInvocationGraphics,
|
||||
"graphics mismatch did not select per-invocation masks");
|
||||
auto cross_lane_compute = cross_lane;
|
||||
cross_lane_compute.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"cross-lane compute mismatch bypassed the exact subgroup requirement");
|
||||
|
||||
ShaderRecompiler::IR::Program zero_exec = safe;
|
||||
@@ -568,8 +562,8 @@ void TestNativeSubgroupPolicy() {
|
||||
zero_bfm.scalar_sources[0] = 2;
|
||||
Check(!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(zero_exec) &&
|
||||
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, zero_exec).mode ==
|
||||
ShaderSubgroupMode::FlattenedMasks,
|
||||
vk::ShaderStageFlagBits::eCompute, zero_exec)
|
||||
.mode == ShaderSubgroupMode::FlattenedMasks,
|
||||
"compile-time uniform-zero EXEC write did not stay on the mask-free path");
|
||||
|
||||
ShaderRecompiler::IR::Program selective_exec = safe;
|
||||
@@ -600,8 +594,8 @@ void TestNativeSubgroupPolicy() {
|
||||
ds_partial.blocks.emplace_back().instructions.emplace_back().op =
|
||||
ShaderRecompiler::IR::Opcode::DsAppend;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, ds_partial).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, ds_partial)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"partial wave64 DS append bypassed the exact subgroup requirement");
|
||||
context.max_subgroup_size = 64;
|
||||
const auto controlled =
|
||||
@@ -614,17 +608,18 @@ void TestNativeSubgroupPolicy() {
|
||||
cross_lane.wave_size = 32;
|
||||
cross_lane.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eFragment, cross_lane).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eFragment, cross_lane)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"inverse graphics mismatch was accepted as one guest wave");
|
||||
cross_lane_compute.wave_size = 32;
|
||||
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
|
||||
ShaderSubgroupMode::Unsupported,
|
||||
vk::ShaderStageFlagBits::eCompute, cross_lane_compute)
|
||||
.mode == ShaderSubgroupMode::Unsupported,
|
||||
"inverse cross-lane compute mismatch was accepted");
|
||||
}
|
||||
|
||||
std::array<uint32_t, 64> ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
|
||||
std::array<uint32_t, 64>
|
||||
ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
|
||||
std::array<uint32_t, 64> data {};
|
||||
for (uint32_t start = 0; start + 3u < data.size(); start += 4u) {
|
||||
data[start] = 0x1000u + start * 0x100u;
|
||||
@@ -3021,7 +3016,7 @@ void TestNewShaderRecompilerImageQueryLowering() {
|
||||
|
||||
void TestNewShaderRecompilerCubeSampleCoordinates() {
|
||||
constexpr uint32_t MimgDimCube = 3;
|
||||
const uint32_t shader[] = {
|
||||
const uint32_t shader[] = {
|
||||
EncodeMimg0(0x20, 0xf, false, MimgDimCube),
|
||||
EncodeMimg1(0, 0, 1, 0), // image_sample cube
|
||||
EncodeMimg0(0x60, 0x3, false, MimgDimCube),
|
||||
@@ -3029,7 +3024,7 @@ void TestNewShaderRecompilerCubeSampleCoordinates() {
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
auto user_data = ImageTestUserData(Prospero::ImageType::kCube);
|
||||
auto user_data = ImageTestUserData(Prospero::ImageType::kCube);
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.user_data = user_data.data();
|
||||
@@ -3525,10 +3520,8 @@ void TestNewShaderRecompilerImageViewDimensions() {
|
||||
"SPIR-V binary does not contain sampled 2D-array image type");
|
||||
Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 1),
|
||||
"SPIR-V binary does not contain sampled 3D image type");
|
||||
Check(SpirvContainsCapability(result.spirv, 43),
|
||||
"SPIR-V binary does not request Sampled1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 44),
|
||||
"SPIR-V binary does not request Image1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 43), "SPIR-V binary does not request Sampled1D");
|
||||
Check(SpirvContainsCapability(result.spirv, 44), "SPIR-V binary does not request Image1D");
|
||||
Check(SpirvContainsOpcode(result.spirv, 95),
|
||||
"SPIR-V binary does not contain array image fetch");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
@@ -3617,7 +3610,7 @@ void TestNewShaderRecompilerRejectsOneDimensionalGather() {
|
||||
EncodeMimg1(0, 0, 1, 0),
|
||||
0xbf810000u,
|
||||
};
|
||||
auto user_data = ImageTestUserData(type);
|
||||
auto user_data = ImageTestUserData(type);
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = ShaderType::Compute;
|
||||
options.user_data = user_data.data();
|
||||
@@ -4116,8 +4109,7 @@ void TestNewShaderRecompilerVintrpLowering() {
|
||||
options.pixel_input_info = &flat_ps_info;
|
||||
|
||||
ShaderRecompiler::CompileResult flat_result;
|
||||
Check(ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error),
|
||||
error.c_str());
|
||||
Check(ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error), error.c_str());
|
||||
Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
|
||||
"flat VINTRP input did not emit a Flat decoration");
|
||||
Check(!SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 13u),
|
||||
@@ -5996,7 +5988,7 @@ void TestNewShaderRecompilerExpPixelOutputs() {
|
||||
|
||||
ShaderPixelInputInfo uint16_info;
|
||||
uint16_info.target_output_mode[0] = 7;
|
||||
options.pixel_input_info = &uint16_info;
|
||||
options.pixel_input_info = &uint16_info;
|
||||
ShaderRecompiler::CompileResult uint16_result;
|
||||
Check(ShaderRecompiler::TryRecompile(shader, options, uint16_result, &error), error.c_str());
|
||||
const auto uint16_source = DisassembleSpirvBinary(uint16_result.spirv);
|
||||
@@ -6027,10 +6019,10 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
format.export_mapping.ApplyMask(0xfu) == 0xfu,
|
||||
"reverse RGBA16F render-target export or write-mask mapping is "
|
||||
"incorrect");
|
||||
const auto legacy_alt = TextureGetRenderTargetFormat(
|
||||
Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt));
|
||||
const auto legacy_alt =
|
||||
TextureGetRenderTargetFormat(Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt));
|
||||
Check(legacy_alt.format == vk::Format::eB8G8R8A8Unorm && legacy_alt.export_mapping.IsIdentity(),
|
||||
"legacy BGRA render target acquired a duplicate shader export mapping");
|
||||
|
||||
@@ -6060,8 +6052,7 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
CheckSpirvBinaryValidates(reversed_result.spirv);
|
||||
|
||||
HW::PixelShaderInfo regs {};
|
||||
Check(ShaderGetIdPS(regs, identity_info, false) !=
|
||||
ShaderGetIdPS(regs, reversed_info, false),
|
||||
Check(ShaderGetIdPS(regs, identity_info, false) != ShaderGetIdPS(regs, reversed_info, false),
|
||||
"pixel shader cache identity omitted the render-target export mapping");
|
||||
|
||||
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
|
||||
@@ -6069,7 +6060,7 @@ void TestRenderTargetReverseFloat16ExportMapping() {
|
||||
ShaderMappedData mapped {};
|
||||
mapped.code_size_bytes = sizeof(shader);
|
||||
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
|
||||
HW::ShaderRegisters sh {};
|
||||
HW::ShaderRegisters sh {};
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
|
||||
std::array<Prospero::ColorComponentMapping, 8> mappings {};
|
||||
@@ -6901,8 +6892,8 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::move(vs_program);
|
||||
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info,
|
||||
identity_mappings, ps_info, spirv),
|
||||
"pixel program-cache transition failed to compile");
|
||||
@@ -6929,8 +6920,8 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
|
||||
ShaderVertexInputInfo vs_info {};
|
||||
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
|
||||
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
ShaderPixelInputInfo ps_info {};
|
||||
std::span<const uint32_t> spirv;
|
||||
Check(ShaderCompileInfoPS(mask_regs, sh, mode, vs_info, identity_mappings, ps_info, spirv),
|
||||
"pixel lane-mask cache transition failed to compile");
|
||||
Check(ps_info.stage.program != nullptr && ps_info.stage.program->lane_mask_mode == mode,
|
||||
@@ -7006,8 +6997,7 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
|
||||
options.flat_memory_base = 0;
|
||||
ShaderRecompiler::CompileResult result;
|
||||
std::string error;
|
||||
const bool compiled =
|
||||
ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
|
||||
const bool compiled = ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
|
||||
Check(compiled, error.c_str());
|
||||
Check(result.program.info.addresses.size() == 2,
|
||||
"segmented address resources were not tracked independently");
|
||||
|
||||
Reference in New Issue
Block a user