Compare commits

...
Author SHA1 Message Date
nmzik e559771a08 libc: isolate guest setenv from host environment. Fix linux debugging 2026-08-29 03:57:09 +04:00
nmzik b870a77176 network: enable IPv4 UDP sockets on POSIX 2026-08-29 03:57:09 +04:00
nmzikandTechx3 62eaead3a3 add configurable Vulkan present modes
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzikandTechx3 d7808d9cb4 add configurable local user name
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzikandTechx3 8267c422b5 launcher: add 1440p/2160p
Co-authored-by: Techx3 <56268269+Techx3@users.noreply.github.com>
2026-08-29 03:57:09 +04:00
nmzik b46e141762 dev: add Linux overrides to VS Code launch configurations 2026-08-29 03:55:29 +04:00
nmzik cfa140fb12 window: create Vulkan surface visible on main thread 2026-08-29 01:31:02 +04:00
Chai T c52bf450f5 fiber: save contexts without compiler frames 2026-08-28 05:33:46 +04:00
Chai T 4cc2f00273 libc: isolate guest tm from host ABI 2026-08-28 05:18:27 +04:00
nmzik ade32e7ba7 kernel: implement AIO request polling 2026-08-27 22:01:13 +02:00
nmzik cc5c0ec6b8 graphics: support PS5 1D color render targets 2026-08-27 14:47:17 +02:00
nmzik c167375fe1 shader: centralize program compilation and permutation caching 2026-08-27 13:37:46 +02:00
nmzik 65871a53a6 graphics: collapse redundant Vulkan and command buffer types 2026-08-27 06:22:15 +02:00
nmzik 1ff580498c shader: support V_CMPX_NGT_F16 SDWA 2026-08-27 04:25:11 +02:00
nmzik ae896d0bf6 shader: support V_CMPX_GT_U16 SDWA 2026-08-27 04:13:12 +02:00
nmzik e738535294 graphics: add RDNA2 FLAT BDA buffer cache and faults 2026-08-27 03:45:41 +02:00
nmzik 8072718462 graphics: replace command fences with timeline reuse 2026-08-26 10:03:43 +02:00
75 changed files with 2690 additions and 2685 deletions

No files matched your search

+37 -3
View File
@@ -10,7 +10,14 @@
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\_Build\\vscode-clang\\launcher",
"environment": [],
"externalConsole": false
"externalConsole": false,
"linux": {
"type": "cppdbg",
"program": "${workspaceFolder}/_Build/vscode-clang/launcher/launcher",
"cwd": "${workspaceFolder}/_Build/vscode-clang/launcher",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb"
}
},
{
"name": "Debug kyty_emulator",
@@ -25,13 +32,40 @@
"stopAtEntry": false,
"cwd": "${workspaceFolder}\\_Build\\vscode-clang",
"environment": [],
"externalConsole": true
"externalConsole": true,
"linux": {
"type": "cppdbg",
"program": "${workspaceFolder}/_Build/vscode-clang/kyty_emulator",
"cwd": "${workspaceFolder}/_Build/vscode-clang",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{
"description": "Pass Kyty's internal signals to its handlers",
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
"ignoreFailures": false
}
]
}
},
{
"name": "Attach to running kyty_emulator",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}"
"processId": "${command:pickProcess}",
"linux": {
"type": "cppdbg",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{
"description": "Pass Kyty's internal signals to its handlers",
"text": "handle SIG37 SIGSEGV SIGBUS SIGILL nostop noprint pass",
"ignoreFailures": false
}
]
}
}
]
}
+26
View File
@@ -263,6 +263,22 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
)
list(APPEND gpu_tiler_shader_headers "${shader_header}")
endforeach()
set(fault_buffer_shader_source "${gpu_tiler_shader_dir}/fault_buffer_process.comp")
set(fault_buffer_shader_spv "${gpu_tiler_generated_dir}/fault_buffer_process.spv")
set(fault_buffer_shader_header "${gpu_tiler_generated_dir}/fault_buffer_process_spv.h")
add_custom_command(
OUTPUT "${fault_buffer_shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_tiler_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
-DCACHING_PAGEBITS=14 -DMAX_PAGE_FAULTS=1024
-o "${fault_buffer_shader_spv}" "${fault_buffer_shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${fault_buffer_shader_spv} -DOUTPUT=${fault_buffer_shader_header}
-DSYMBOL=FAULT_BUFFER_PROCESS_SPV -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${fault_buffer_shader_source}"
VERBATIM
)
list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
@@ -508,6 +524,16 @@ if(NOT KYTY_CLANG_CL)
endif()
if(BUILD_TESTING)
add_test(NAME emulator_user_name_cli
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 1234567890123456)
add_test(NAME emulator_user_name_too_long
COMMAND $<TARGET_FILE:kyty_emulator> --help --user-name 12345678901234567)
set_tests_properties(emulator_user_name_too_long PROPERTIES WILL_FAIL TRUE)
add_test(NAME emulator_present_mode_cli
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Immediate)
add_test(NAME emulator_present_mode_invalid
COMMAND $<TARGET_FILE:kyty_emulator> --help --present-mode Invalid)
set_tests_properties(emulator_present_mode_invalid PROPERTIES WILL_FAIL TRUE)
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
+9
View File
@@ -21,6 +21,7 @@ void Shutdown() {
void Load(const ConfigOptions& cfg) {
EXIT_IF(g_config == nullptr);
EXIT_IF(cfg.user_name.empty() || cfg.user_name.size() > MAX_USER_NAME_LENGTH);
*g_config = cfg;
}
@@ -33,6 +34,14 @@ uint32_t GetScreenHeight() {
return g_config->screen_height;
}
const std::string& GetUserName() {
return g_config->user_name;
}
PresentMode GetPresentMode() {
return g_config->present_mode;
}
bool FullscreenEnabled() {
return g_config->fullscreen_enabled;
}
+8
View File
@@ -3,6 +3,7 @@
#include "common/common.h"
#include <cstddef>
#include <filesystem>
#include <string>
#include <vector>
@@ -26,14 +27,19 @@ enum class ProfilerDirection { None, Network };
enum class OutputDirection { Silent, Console, File };
enum class PresentMode { Fifo, Mailbox, Immediate };
using Keymap = std::vector<std::string>;
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
constexpr std::size_t MAX_USER_NAME_LENGTH = 16;
struct ConfigOptions {
uint32_t screen_width = 1280;
uint32_t screen_height = 720;
std::string user_name = "Kyty";
PresentMode present_mode = PresentMode::Fifo;
bool fullscreen_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
@@ -63,6 +69,8 @@ void Load(const ConfigOptions& cfg);
uint32_t GetScreenWidth();
uint32_t GetScreenHeight();
const std::string& GetUserName();
PresentMode GetPresentMode();
bool FullscreenEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
@@ -153,10 +153,10 @@ private:
uint32_t render_target_slice_offset, uint32_t first_vertex,
uint32_t first_instance);
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
RenderContext& m_renderer;
HW::Context m_ctx;
+2 -3
View File
@@ -255,8 +255,7 @@ void CommandProcessor::BufferFlush() {
}
void CommandProcessor::BufferFlushAndWait() {
auto& submitted = GetScheduler().FlushAndGetSubmitted();
submitted.WaitForFenceOnly();
GetScheduler().FlushAndWait();
}
void CommandProcessor::BufferWait() {
@@ -1622,7 +1621,7 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
}
void CommandProcessor::SynchronizeGpu() {
GetScheduler().FinishCurrent();
GetScheduler().Finish();
}
bool GuestGpu::IsGpuThread() noexcept {
+80 -3
View File
@@ -3,20 +3,88 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
#include "graphics/host_gpu/vulkanInstance.h"
#include <map>
#include <mutex>
#include <tuple>
#include <vector>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
class CommandBuffer;
struct VulkanBuffer;
struct VulkanImage;
struct VulkanMemory;
struct GraphicContext: public VulkanInstance {
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
struct GraphicContext {
vk::Instance instance = nullptr;
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
vk::PhysicalDevice physical_device = nullptr;
vk::PhysicalDeviceProperties physical_device_properties = {};
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool compute_wave64_supported = false;
bool sample_rate_shading_enabled = false;
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
uint32_t max_push_descriptors = 0;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
uint32_t queue_family = static_cast<uint32_t>(-1);
vk::Queue queue = nullptr;
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
return physical_device_properties;
}
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
GetPhysicalDeviceMemoryProperties() const {
return physical_device_memory_properties;
}
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
std::scoped_lock lock(m_format_properties_mutex);
auto [it, inserted] = m_format_properties.try_emplace(format);
if (inserted) {
physical_device.getFormatProperties(format, &it->second);
}
return it->second;
}
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
vk::ImageTiling tiling,
vk::ImageUsageFlags usage,
vk::ImageCreateFlags flags,
vk::ImageFormatProperties* properties) const {
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>;
std::scoped_lock lock(m_image_format_properties_mutex);
auto [it, inserted] =
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
if (inserted) {
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
flags, &it->second.second);
}
if (properties != nullptr) {
*properties = it->second.second;
}
return it->second.first;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
}
[[nodiscard]] bool CreateAllocator();
void DestroyAllocator();
void LogMemoryBudget() const;
@@ -35,6 +103,15 @@ struct GraphicContext: public VulkanInstance {
uint32_t screen_width = 0;
uint32_t screen_height = 0;
private:
mutable std::mutex m_format_properties_mutex;
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
mutable std::mutex m_image_format_properties_mutex;
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>,
std::pair<vk::Result, vk::ImageFormatProperties>>
m_image_format_properties;
};
struct VulkanMemory {
+10 -1
View File
@@ -53,6 +53,15 @@ public:
}
}
void Clear() { m_ranges.clear(); }
template <typename Func>
void ForEach(Func&& func) const {
for (const auto& [begin, end]: m_ranges) {
func(begin, end);
}
}
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
std::vector<Range> result;
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
@@ -98,7 +107,7 @@ public:
private:
static uint64_t End(uint64_t address, uint64_t size) {
if (address == 0 || size == 0 || size > UINT64_MAX - address) {
if (size == 0 || size > UINT64_MAX - address) {
EXIT("invalid range-set address or size\n");
}
return address + size;
+76 -41
View File
@@ -51,41 +51,50 @@ struct BufferCache::DownloadCopy {
};
void BufferCache::Register(BufferId id) {
auto& buffer = m_slot_buffers[id];
PageTable::PageRange pages {};
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
m_page_table[page] = id;
}
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
(void)it;
EXIT_IF(!inserted);
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
m_total_used_memory += buffer.Size();
ChangeRegister<true>(id);
}
void BufferCache::Unregister(BufferId id) {
ChangeRegister<false>(id);
}
template <bool insert>
void BufferCache::ChangeRegister(BufferId id) {
auto& buffer = m_slot_buffers[id];
if (buffer.is_deleted) {
return;
}
PageTable::PageRange pages {};
EXIT_IF(!PageTable::TryGetPageRange(buffer.CpuAddress(), buffer.Size(), pages));
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
auto* owner = m_page_table.Find(page);
if (owner != nullptr && *owner == id) {
*owner = {};
if constexpr (insert) {
m_page_table[page] = id;
} else {
m_page_table[page] = {};
}
}
const auto found = m_buffers.find(buffer.CpuAddress());
EXIT_IF(found == m_buffers.end() || found->second != id);
m_buffers.erase(found);
if (buffer.Size() > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
const auto size_pages = pages.last_exclusive - pages.first;
if constexpr (insert) {
const auto [it, inserted] = m_buffers.emplace(buffer.CpuAddress(), id);
(void)it;
EXIT_IF(!inserted);
m_total_used_memory += buffer.Size();
buffer.lru_id = m_lru_cache.Insert(id, m_gc_tick);
std::vector<vk::DeviceAddress> addresses;
addresses.reserve(size_pages);
for (uint64_t i = 0; i < size_pages; ++i) {
addresses.push_back(buffer.BufferDeviceAddress() + (i << CACHING_PAGEBITS));
}
WriteDataBuffer(m_bda_pagetable_buffer, pages.first * sizeof(vk::DeviceAddress),
addresses.data(), addresses.size() * sizeof(vk::DeviceAddress));
} else {
const auto found = m_buffers.find(buffer.CpuAddress());
EXIT_IF(found == m_buffers.end() || found->second != id);
m_buffers.erase(found);
EXIT_IF(buffer.Size() > m_total_used_memory);
m_total_used_memory -= buffer.Size();
m_lru_cache.Free(buffer.lru_id);
m_bda_pagetable_buffer.Fill(pages.first * sizeof(vk::DeviceAddress),
size_pages * sizeof(vk::DeviceAddress), 0);
buffer.is_deleted = true;
}
m_total_used_memory -= buffer.Size();
m_lru_cache.Free(buffer.lru_id);
buffer.is_deleted = true;
}
void BufferCache::TouchBuffer(const Buffer& buffer) {
@@ -143,7 +152,7 @@ void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
}
download.Commit();
const auto completion_tick = m_scheduler.CurrentTick();
m_scheduler.FinishCurrent();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(completion_tick);
cursor = 0;
for (const auto& copy: batch) {
@@ -184,16 +193,21 @@ void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache)
: m_graphics(graphics), m_scheduler(scheduler),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
: m_graphics(graphics), m_scheduler(scheduler),
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
BDA_PAGETABLE_SIZE),
m_memory_tracker(page_manager),
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
m_gds_buffer.Flush(0, m_gds_buffer.Size());
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
"BDA Page Table Buffer");
const auto null_id =
m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16);
EXIT_IF(null_id != NULL_BUFFER_ID);
@@ -315,9 +329,9 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
auto& command = m_scheduler.Current();
EXIT_IF(command.IsInvalid() || command.IsExecute());
auto begin = vaddr & ~(CACHING_PAGE_SIZE - 1);
auto end = (vaddr + size + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
EXIT_IF(command.IsInvalid());
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
auto first = m_buffers.lower_bound(begin);
if (first != m_buffers.begin()) {
const auto previous = std::prev(first);
@@ -333,8 +347,9 @@ BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
end = std::max(end, buffer.CpuAddress() + buffer.Size());
}
const auto id = m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
AllFlags, end - begin);
const auto id = m_slot_buffers.insert(
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
auto& buffer = m_slot_buffers[id];
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
@@ -428,12 +443,12 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
bool is_written, bool is_texel_buffer,
BufferId id) {
auto& command = m_scheduler.Current();
if (command.IsInvalid() || command.IsExecute() || vaddr == 0 || size == 0 ||
if (command.IsInvalid() || vaddr == 0 || size == 0 ||
vaddr >= TRACKER_ADDRESS_SIZE || size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: buffer request requires a recording command buffer\n");
}
if (!is_written && size <= CACHING_PAGE_SIZE &&
if (!is_written && size <= CACHING_PAGESIZE &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
const auto alignment = std::max<uint64_t>(
@@ -737,4 +752,24 @@ void BufferCache::RunGarbageCollector() {
}
}
void BufferCache::ProcessFaultBuffer() {
m_fault_manager.ProcessFaultBuffer();
}
void BufferCache::SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size) {
const auto end = vaddr + size;
auto it = m_buffers.upper_bound(vaddr);
if (it != m_buffers.begin()) {
--it;
}
for (; it != m_buffers.end() && it->first < end; ++it) {
auto& buffer = m_slot_buffers[it->second];
const auto start = std::max(buffer.CpuAddress(), vaddr);
const auto finish = std::min(buffer.CpuAddress() + buffer.Size(), end);
if (start < finish) {
(void)SynchronizeBuffer(buffer, start, finish - start, false, false);
}
}
}
} // namespace Libs::Graphics
+16 -3
View File
@@ -7,6 +7,7 @@
#include "common/slotVector.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/faultManager.h"
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
@@ -26,7 +27,11 @@ inline constexpr BufferId NULL_BUFFER_ID {0};
class BufferCache {
public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint32_t CACHING_PAGEBITS = 14;
static constexpr uint64_t CACHING_PAGESIZE = uint64_t {1} << CACHING_PAGEBITS;
static constexpr uint64_t CACHING_NUMPAGES = uint64_t {1} << (40 - CACHING_PAGEBITS);
static constexpr uint64_t BDA_PAGETABLE_SIZE =
CACHING_NUMPAGES * sizeof(vk::DeviceAddress);
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache);
@@ -51,6 +56,8 @@ public:
EXIT("BufferCache: invalid utility-buffer usage\n");
}
[[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { return &m_gds_buffer; }
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { return &m_bda_pagetable_buffer; }
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return m_fault_manager.GetFaultBuffer(); }
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
@@ -60,14 +67,16 @@ public:
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void ProcessFaultBuffer();
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
void RunGarbageCollector();
private:
friend struct BufferCacheTestAccess;
struct DownloadCopy;
using PageTable = MultiLevelPageTable<BufferId, 14, 40, 16>;
static_assert(CACHING_PAGE_SIZE == (uint64_t {1} << PageTable::kPageBits));
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
@@ -78,6 +87,8 @@ private:
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
void Register(BufferId id);
void Unregister(BufferId id);
template <bool insert>
void ChangeRegister(BufferId id);
void DeleteBuffer(BufferId id);
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
bool is_written, bool is_texel_buffer);
@@ -90,7 +101,9 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
FaultManager m_fault_manager;
Buffer m_gds_buffer;
Buffer m_bda_pagetable_buffer;
Common::SlotVector<Buffer> m_slot_buffers;
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
std::map<uint64_t, BufferId> m_buffers;
+167
View File
@@ -0,0 +1,167 @@
#include "graphics/host_gpu/renderer/cache/faultManager.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "gpu_tiler_shaders/fault_buffer_process_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <bit>
#include <cinttypes>
#include <cstring>
#include <limits>
namespace Libs::Graphics {
namespace {
constexpr size_t MaxPageFaults = 1024;
constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
} // namespace
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
BufferCache& buffer_cache, uint32_t caching_pagebits,
uint64_t caching_num_pages)
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
m_caching_pagesize(uint64_t {1} << caching_pagebits),
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
m_fault_buffer_size),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
MaxPendingFaults * PageFaultAreaSize) {
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
const vk::DescriptorSetLayoutBinding bindings[] {
{0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
};
vk::DescriptorSetLayoutCreateInfo layout_info {};
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
layout_info.bindingCount = std::size(bindings);
layout_info.pBindings = bindings;
RequireVulkanSuccess(
m_graphics.device.createDescriptorSetLayout(&layout_info, nullptr,
&m_fault_process_desc_layout),
"create fault-buffer descriptor layout");
vk::ShaderModuleCreateInfo module_info {};
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
vk::ShaderModule module = nullptr;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
"create fault-buffer shader module");
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.setLayoutCount = 1;
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
RequireVulkanSuccess(
m_graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&m_fault_process_pipeline_layout),
"create fault-buffer pipeline layout");
vk::PipelineShaderStageCreateInfo stage {};
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
stage.stage = vk::ShaderStageFlagBits::eCompute;
stage.module = module;
stage.pName = "main";
vk::ComputePipelineCreateInfo pipeline_info {};
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
pipeline_info.stage = stage;
pipeline_info.layout = m_fault_process_pipeline_layout;
const auto result = m_graphics.device.createComputePipelines(
nullptr, 1, &pipeline_info, nullptr, &m_fault_process_pipeline);
m_graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create fault-buffer pipeline");
SetVulkanObjectNameF(m_graphics.device, m_fault_process_pipeline, "Fault Buffer Parser");
}
FaultManager::~FaultManager() {
m_graphics.device.destroyPipeline(m_fault_process_pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(m_fault_process_pipeline_layout, nullptr);
m_graphics.device.destroyDescriptorSetLayout(m_fault_process_desc_layout, nullptr);
}
void FaultManager::ProcessFaultBuffer() {
if (const auto wait_tick = m_fault_areas[m_current_area]; wait_tick != 0) {
m_scheduler.Wait(wait_tick);
m_scheduler.PopPendingOperations();
}
const auto offset = m_current_area * PageFaultAreaSize;
auto* mapped = m_download_buffer.Mapped().data() + offset;
std::memset(mapped, 0, PageFaultAreaSize);
m_download_buffer.Flush(offset, PageFaultAreaSize);
vk::BufferMemoryBarrier2 pre_barrier {};
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
pre_barrier.buffer = m_fault_buffer.Handle();
pre_barrier.offset = 0;
pre_barrier.size = m_fault_buffer_size;
auto post_barrier = pre_barrier;
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
post_barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
const vk::DescriptorBufferInfo infos[] {
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t index = 0; index < writes.size(); ++index) {
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
writes[index].dstBinding = index;
writes[index].descriptorCount = 1;
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
writes[index].pBufferInfo = &infos[index];
}
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
vk::DependencyInfo dependency {};
dependency.sType = vk::StructureType::eDependencyInfo;
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependency.bufferMemoryBarrierCount = 1;
dependency.pBufferMemoryBarriers = &pre_barrier;
command.pipelineBarrier2(dependency);
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
m_fault_process_pipeline_layout, 0, writes);
const auto num_threads = m_caching_num_pages / 32;
const auto num_workgroups = (num_threads + 63) / 64;
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
dependency.pBufferMemoryBarriers = &post_barrier;
command.pipelineBarrier2(dependency);
const auto area = m_current_area;
m_scheduler.DeferOperation([this, mapped, offset, area] {
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
m_fault_ranges.Clear();
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
const auto count = static_cast<uint32_t>(faults[0]);
for (uint32_t index = 1; index <= count; ++index) {
m_fault_ranges.Add(faults[index], m_caching_pagesize);
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
}
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
(void)m_buffer_cache.FindBuffer(start, end - start);
});
m_fault_areas[area] = 0;
});
m_fault_areas[m_current_area++] = m_scheduler.CurrentTick();
m_current_area %= MaxPendingFaults;
}
} // namespace Libs::Graphics
+46
View File
@@ -0,0 +1,46 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
#include "common/abi.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <array>
#include <cstdint>
namespace Libs::Graphics {
class BufferCache;
class FaultManager {
static constexpr size_t MaxPendingFaults = 8;
public:
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
uint32_t caching_pagebits, uint64_t caching_num_pages);
~FaultManager();
KYTY_CLASS_NO_COPY(FaultManager);
[[nodiscard]] Buffer* GetFaultBuffer() noexcept { return &m_fault_buffer; }
void ProcessFaultBuffer();
private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
BufferCache& m_buffer_cache;
RangeSet m_fault_ranges;
uint64_t m_caching_pagesize;
uint64_t m_caching_num_pages;
uint64_t m_fault_buffer_size;
Buffer m_fault_buffer;
Buffer m_download_buffer;
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
uint32_t m_current_area = 0;
vk::DescriptorSetLayout m_fault_process_desc_layout = nullptr;
vk::Pipeline m_fault_process_pipeline = nullptr;
vk::PipelineLayout m_fault_process_pipeline_layout = nullptr;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
+13 -1
View File
@@ -60,7 +60,7 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick();
m_scheduler.FinishCurrent();
m_scheduler.Finish();
m_scheduler.WaitPriorityOperations(tick);
}
m_buffer_cache.InvalidateMemory(vaddr, size);
@@ -76,7 +76,19 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::PrepareBda() {
std::shared_lock lock(m_mapped_ranges_mutex);
m_mapped_ranges.ForEach([this](uint64_t start, uint64_t end) {
m_buffer_cache.SynchronizeBuffersInRange(start, end - start);
});
m_fault_process_pending = true;
}
void GpuResourceManager::RunGarbageCollector() {
if (m_fault_process_pending) {
m_fault_process_pending = false;
m_buffer_cache.ProcessFaultBuffer();
}
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
@@ -30,6 +30,7 @@ public:
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size);
void UnmapMemory(uint64_t vaddr, uint64_t size);
void PrepareBda();
void RunGarbageCollector();
private:
@@ -40,6 +41,7 @@ private:
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
GuestGpu* m_gpu = nullptr;
bool m_fault_process_pending = false;
};
} // namespace Libs::Graphics
+26 -11
View File
@@ -73,8 +73,12 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
buffer_info.usage = flags;
buffer_info.sharingMode = vk::SharingMode::eExclusive;
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
const VmaAllocationCreateFlags bda_flag =
with_bda ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0;
VmaAllocationCreateInfo allocation_info {};
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
allocation_info.flags =
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | bda_flag | AllocationFlags(usage);
allocation_info.usage = AllocationUsage(usage);
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
? VkMemoryPropertyFlags {}
@@ -99,6 +103,13 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
m_buffer->memory.type = allocation_result.memoryType;
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
vk::BufferDeviceAddressInfo address_info {};
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
address_info.buffer = m_buffer->buffer;
m_device_address = graphics.device.getBufferAddress(address_info);
EXIT_IF(m_device_address == 0);
}
VkMemoryPropertyFlags properties = 0;
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
@@ -122,6 +133,11 @@ vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
EXIT_IF(m_device_address == 0);
return m_device_address;
}
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
}
@@ -141,6 +157,15 @@ void Buffer::Flush(uint64_t offset, uint64_t size) {
}
}
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
if (!m_is_coherent && size != 0) {
const auto result =
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
}
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
vk::AccessFlags destination) const {
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
@@ -310,16 +335,6 @@ void StreamBuffer::Commit() {
watch.tick = tick;
}
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (IsCoherent() || size == 0) {
return;
}
const auto result = vmaInvalidateAllocation(Graphics().allocator,
NativeBuffer().memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
EXIT_IF(source == nullptr);
const auto [data, offset] = Map(size, alignment);
+3 -3
View File
@@ -48,12 +48,14 @@ public:
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void Flush(uint64_t offset, uint64_t size);
void Invalidate(uint64_t offset, uint64_t size);
void CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
@@ -84,6 +86,7 @@ private:
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
vk::DeviceAddress m_device_address = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
@@ -97,9 +100,6 @@ public:
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
bool allow_wait = true);
void Commit();
// Download mappings become visible to the CPU only after their GPU completion tick is free.
// Call this from the scheduler's deferred completion operation before reading Mapped().
void Invalidate(uint64_t offset, uint64_t size);
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
private:
@@ -14,6 +14,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <array>
#include <atomic>
namespace Libs::Graphics {
@@ -43,7 +44,7 @@ static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool h
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
@@ -143,12 +144,23 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
uint32_t pitch = 0;
uint64_t size = 0;
bool tile = false;
const bool volume = rt.attrib3.dimension == 2;
if (rt.attrib3.dimension != 1 && !volume) {
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
Prospero::ImageType::kColor2D,
Prospero::ImageType::kColor3D};
if (rt.attrib3.dimension >= image_types.size()) {
EXIT("unsupported render-target dimension: %u\n", rt.attrib3.dimension);
}
const auto image_type = image_types[rt.attrib3.dimension];
const bool is_1d = image_type == Prospero::ImageType::kColor1D;
const bool volume = image_type == Prospero::ImageType::kColor3D;
if (is_1d && rt.attrib2.height != 0) {
EXIT("1D render target has nonzero height: %u\n", rt.attrib2.height);
}
if (!volume && rt.attrib3.depth != 0) {
EXIT("2D render target has nonzero depth: %u\n", rt.attrib3.depth);
EXIT("non-3D render target has nonzero depth: %u\n", rt.attrib3.depth);
}
if (is_1d && samples != 1) {
EXIT("multisampled 1D render targets are unsupported\n");
}
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
@@ -325,7 +337,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = transfer_format;
desc.info.type = volume ? Prospero::ImageType::kColor3D : Prospero::ImageType::kColor2D;
desc.info.type = image_type;
desc.info.extent = {width, height, depth};
desc.info.resources = {levels, volume ? 1u : view.image_layers};
desc.info.pitch = pitch;
@@ -364,8 +376,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
};
}
desc.view_info.format = target_format.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
if (is_1d) {
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e1D : vk::ImageViewType::e1DArray;
} else {
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
}
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.base_level = rt.view.current_mip_level;
desc.view_info.level_count = 1;
@@ -10,8 +10,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
enum class RenderColorType {
NoColorOutput,
RenderTexture,
@@ -1,96 +1,97 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/host_gpu/graphicContext.h"
#include <algorithm>
#include <cstdio>
#include <optional>
namespace Libs::Graphics {
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
void CommandSlot::Reset() {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
namespace {
void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint32_t debug_op,
uint64_t debug_submit, uint32_t arg0, uint32_t arg1, uint32_t arg2,
uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
CommandScheduler::CommandPool::~CommandPool() {
Destroy();
}
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
graphics.queue_family == static_cast<uint32_t>(-1));
m_graphics = &graphics;
} // namespace
CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemaphore& master)
: m_graphics(graphics), m_master(master) {
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
EXIT_IF(m_graphics == nullptr);
auto& graphics = *m_graphics;
CommandScheduler::CommandPool::~CommandPool() {
m_graphics.device.destroyCommandPool(m_pool, nullptr);
}
size_t CommandScheduler::CommandPool::Grow() {
const auto first = m_ticks.size();
m_ticks.resize(first + GrowStep);
m_buffers.resize(first + GrowStep);
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
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;
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");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
EXIT_IF(m_graphics.device.allocateCommandBuffers(&allocate, m_buffers.data() + first) !=
vk::Result::eSuccess);
return first;
}
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
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;
slot->Reset();
return slot;
}
vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
auto gpu_tick = m_master.KnownGpuTick();
const auto search = [this, &gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
for (size_t index = begin; index < end; ++index) {
if (gpu_tick >= m_ticks[index]) {
m_ticks[index] = m_master.CurrentTick();
return index;
}
}
return std::nullopt;
};
void CommandScheduler::CommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
auto found = search(m_hint, m_ticks.size());
if (!found) {
m_master.Refresh();
gpu_tick = m_master.KnownGpuTick();
found = search(m_hint, m_ticks.size());
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
EXIT_IF(m_graphics == nullptr);
for (const auto& slot: m_slots) {
m_graphics->device.destroyFence(slot.fence, nullptr);
if (!found) {
found = search(0, m_hint);
}
m_graphics->device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
m_graphics = nullptr;
if (!found) {
found = Grow();
m_ticks[*found] = m_master.CurrentTick();
}
m_hint = *found + 1;
if (m_hint == m_ticks.size()) {
m_hint = 0;
}
return m_buffers[*found];
}
bool CommandScheduler::InDeferredOperation() noexcept {
@@ -99,10 +100,8 @@ bool CommandScheduler::InDeferredOperation() noexcept {
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
m_buffers.reserve(CommandBufferGrowStep);
m_buffer_ticks.reserve(CommandBufferGrowStep);
}
m_command_pool(graphics, m_master), m_command(*this),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
CommandScheduler::~CommandScheduler() {
Shutdown();
@@ -128,9 +127,11 @@ void CommandScheduler::Shutdown() {
}
m_operation_state = OperationState::Draining;
}
if (Active() && m_recording) {
Finish();
if (!m_command.IsInvalid()) {
Submit();
}
m_master.Wait(CurrentTick() - 1);
PopPendingOperations();
DrainPriorityOperations();
m_priority_thread.request_stop();
m_operation_available.notify_all();
@@ -156,14 +157,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
m_current = static_cast<int>(GrowCommandBuffers());
}
BindCurrent();
if (!m_recording) {
Current().Begin();
m_recording = true;
if (m_command.IsInvalid()) {
BeginNext();
} else {
BindCurrent();
}
}
@@ -172,7 +169,7 @@ void CommandScheduler::BeginRendering(const RenderState& state) {
}
void CommandScheduler::EndRendering() {
if (Active() && m_recording) {
if (Active() && !m_command.IsInvalid()) {
Current().EndRendering();
}
}
@@ -183,60 +180,40 @@ void CommandScheduler::Flush() {
}
void CommandScheduler::Flush(SubmitInfo& submit) {
SubmitCurrent(submit);
Submit(submit);
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
void CommandScheduler::FlushAndWait() {
const auto tick = Submit();
m_master.Wait(tick);
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
const auto tick = CurrentTick();
if (m_recording) {
SubmitInfo submit;
SubmitCurrent(submit);
if (!m_command.IsInvalid()) {
Submit();
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
m_master.Wait(tick);
m_master.Wait(CurrentTick() - 1);
BeginNext();
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
PopPendingOperations();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::Wait(uint64_t tick) {
CheckActive();
EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) {
if (tick == CurrentTick()) {
CheckActive();
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid. Deferred
// Current(). The wrapper stays stable while its pooled Vulkan buffer is retired. Deferred
// resources are released only at the next GPU operation boundary.
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
submitted.Begin();
m_recording = true;
return;
const auto submitted_tick = Submit();
EXIT_IF(submitted_tick != tick);
m_master.Wait(tick);
BeginNext();
} else {
m_master.Wait(tick);
}
m_master.Wait(tick);
}
void CommandScheduler::PopPendingOperations() {
@@ -365,83 +342,81 @@ bool CommandScheduler::IsFree(uint64_t tick) {
return m_master.IsFree(tick);
}
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
return m_command_pool.Allocate(m_graphics);
}
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
EXIT_IF(!Active());
}
RenderCommandBuffer& CommandScheduler::Current() const {
CommandBuffer& CommandScheduler::Current() {
CheckActive();
EXIT_IF(m_buffers[m_current] == nullptr);
return *m_buffers[m_current];
return m_command;
}
void CommandScheduler::BindCurrent() const {
void CommandScheduler::BindCurrent() {
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
Current().Bind(*m_registers, *m_user_config, *m_shaders);
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
m_recording = false;
return submitted;
CommandBuffer& CommandScheduler::BeginCommand() {
EXIT_IF(!m_command.IsInvalid());
m_command.m_buffer = m_command_pool.Commit();
m_command.Begin();
return m_command;
}
int CommandScheduler::FindReusableBuffer(uint64_t gpu_tick) const {
EXIT_IF(m_buffer_ticks.size() != m_buffers.size());
const auto buffer_count = m_buffers.size();
for (size_t offset = 1; offset <= buffer_count; ++offset) {
const auto candidate = (static_cast<size_t>(m_current) + offset) % buffer_count;
if (gpu_tick >= m_buffer_ticks[candidate]) {
return static_cast<int>(candidate);
}
uint64_t CommandScheduler::Submit(SubmitInfo submit) {
EXIT_IF(m_command.IsInvalid());
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores >= SubmitInfo::MaxSemaphores);
m_command.End();
const auto buffer = m_command.m_buffer;
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
vk::Result result;
uint64_t tick;
{
Common::LockGuard lock(graphics.queue_mutex);
tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), tick);
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
result = graphics.queue.submit(1, &submit_info, nullptr);
}
return -1;
}
size_t CommandScheduler::GrowCommandBuffers() {
const auto first = m_buffers.size();
const auto end = first + CommandBufferGrowStep;
m_buffers.reserve(end);
m_buffer_ticks.reserve(end);
for (size_t i = first; i < end; ++i) {
m_buffers.emplace_back(std::make_unique<RenderCommandBuffer>(*this));
m_buffer_ticks.push_back(0);
if (result != vk::Result::eSuccess) {
ReportVulkanFatal("vkQueueSubmit", result, tick, m_command.m_debug_op,
m_command.m_debug_submit_id, m_command.m_debug_arg0,
m_command.m_debug_arg1, m_command.m_debug_arg2, m_command.m_debug_arg3,
m_command.m_debug_arg4);
}
return first;
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_command.m_buffer = nullptr;
return tick;
}
void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
if (candidate < 0) {
m_master.Refresh();
candidate = FindReusableBuffer(m_master.KnownGpuTick());
}
if (candidate < 0) {
candidate = static_cast<int>(GrowCommandBuffers());
}
m_current = candidate;
Current().WaitForFenceAndReset();
EXIT_IF(!m_command.IsInvalid());
BindCurrent();
Current().Begin();
m_recording = true;
BeginCommand();
}
} // namespace Libs::Graphics
@@ -6,11 +6,7 @@
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
@@ -20,16 +16,6 @@
namespace Libs::Graphics {
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
void Reset();
};
class CommandScheduler {
public:
CommandScheduler(RenderContext& context, GraphicContext& graphics);
@@ -41,9 +27,10 @@ public:
void EndRendering();
void Flush();
void Flush(SubmitInfo& submit);
CommandBuffer& FlushAndGetSubmitted();
void FlushAndWait();
void Finish();
void FinishCurrent();
CommandBuffer& BeginCommand();
uint64_t Submit(SubmitInfo submit = {});
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
@@ -55,9 +42,9 @@ public:
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
CommandBuffer& Current();
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] MasterSemaphore& GetMasterSemaphore() noexcept { return m_master; }
@@ -65,25 +52,25 @@ public:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
static constexpr size_t CommandBufferGrowStep = 4;
class CommandPool {
public:
CommandPool() = default;
CommandPool(GraphicContext& graphics, MasterSemaphore& master);
~CommandPool();
KYTY_CLASS_NO_COPY(CommandPool);
CommandSlot* Allocate(GraphicContext& graphics);
vk::CommandBuffer Commit();
private:
void Create(GraphicContext& graphics);
CommandSlot* CreateSlot();
void Destroy();
static constexpr size_t GrowStep = 4;
GraphicContext* m_graphics = nullptr;
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
size_t Grow();
GraphicContext& m_graphics;
MasterSemaphore& m_master;
vk::CommandPool m_pool = nullptr;
std::vector<vk::CommandBuffer> m_buffers;
std::vector<uint64_t> m_ticks;
size_t m_hint = 0;
};
enum class OperationState { Open, Draining, Closed };
@@ -93,39 +80,28 @@ private:
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
[[nodiscard]] int FindReusableBuffer(uint64_t gpu_tick) const;
[[nodiscard]] size_t GrowCommandBuffers();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
void BindCurrent();
void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
std::vector<uint64_t> m_buffer_ticks;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
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;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
CommandBuffer m_command;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
};
} // namespace Libs::Graphics
+6 -149
View File
@@ -1,7 +1,5 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -16,70 +14,29 @@
#include <algorithm>
#include <bit>
#include <cstdio>
#include <cstring>
namespace Libs::Graphics {
namespace {
void ReportVulkanFatal(const char* what, vk::Result result, uint32_t slot, uint64_t submit_seq,
uint32_t debug_op, uint64_t debug_submit, uint32_t arg0, uint32_t arg1,
uint32_t arg2, uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
what, VulkanToString(result).c_str(), static_cast<int>(result), slot, submit_seq,
debug_op, debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
} // namespace
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {}
CommandBuffer::~CommandBuffer() {
Release();
}
: m_context(scheduler.Context()), m_graphics(scheduler.Graphics()) {}
bool CommandBuffer::IsInvalid() const {
return m_slot == nullptr;
return m_buffer == nullptr;
}
vk::CommandBuffer CommandBuffer::Handle() const {
EXIT_IF(IsInvalid());
const auto handle = m_slot->buffer;
EXIT_IF(handle == nullptr);
return handle;
return m_buffer;
}
void CommandBuffer::Release() {
EXIT_IF(IsInvalid());
Common::LockGuard lock(*m_slot->pool_mutex);
WaitForFence();
m_slot->busy = false;
m_slot->Reset();
m_slot = nullptr;
EXIT_NOT_IMPLEMENTED(!IsInvalid());
}
void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
void CommandBuffer::Begin() {
EXIT_IF(m_rendering || IsInvalid());
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
begin_info.pNext = nullptr;
begin_info.flags = {};
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
begin_info.pInheritanceInfo = nullptr;
auto result = buffer.begin(&begin_info);
@@ -107,106 +64,6 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
m_debug_arg4 = arg4;
}
void CommandBuffer::Execute(const SubmitInfo& submit) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
auto buffer = Handle();
auto fence = m_slot->fence;
vk::TimelineSemaphoreSubmitInfo timeline_info {};
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
vk::SubmitInfo submit_info {};
submit_info.sType = vk::StructureType::eSubmitInfo;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
auto result = graphics.device.resetFences(1, &fence);
if (result != vk::Result::eSuccess) {
ReportVulkanFatal("vkResetFences (before submit)", result, m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
m_debug_arg3, m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
{
Common::LockGuard lock(graphics.queue_mutex);
m_submit_seq = m_scheduler.NextSubmitSequence();
result = graphics.queue.submit(1, &submit_info, fence);
}
m_execute = true;
m_fence_waited = false;
if (result != vk::Result::eSuccess) {
ReportVulkanFatal("vkQueueSubmit", result, m_slot->id, m_submit_seq, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
void CommandBuffer::WaitForFence() {
FinalizeFence(false);
}
void CommandBuffer::WaitForFenceOnly() {
EXIT_IF(IsInvalid());
if (!m_execute || m_fence_waited) {
return;
}
auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
ReportVulkanFatal("vkWaitForFences", result, m_slot->id, m_submit_seq, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_fence_waited = true;
}
void CommandBuffer::WaitForFenceAndReset() {
FinalizeFence(true);
}
void CommandBuffer::FinalizeFence(bool reset_recording) {
const bool was_executed = m_execute;
WaitForFenceOnly();
if (was_executed) {
m_execute = false;
m_fence_waited = false;
if (reset_recording) {
Common::LockGuard lock(*m_slot->pool_mutex);
m_slot->Reset();
}
}
}
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
+11 -14
View File
@@ -27,7 +27,7 @@ static bool RenderTargetMaskHasMrt(uint32_t mask) {
return (mask & ~0x0fu) != 0;
}
static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
static bool RenderTargetMaskHasBoundMrt(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
@@ -45,7 +45,7 @@ static bool RenderTargetMaskHasBoundMrt(const RenderCommandBuffer& buffer) {
return bound_targets > 1;
}
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer) {
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto mask = hw.GetRenderTargetMask();
for (uint32_t i = 0; i < 8; i++) {
@@ -266,11 +266,10 @@ static void RtCheck(const HW::RenderTarget& rt) {
logged = true;
}
}
const bool native_2x_one_frag = rt.info.fmask_compression_enable &&
!rt.info.fmask_data_compression_disable &&
rt.info.cmask_fast_clear_enable && rt.fmask.addr != 0 &&
rt.cmask.addr != 0 && rt.attrib.num_samples == 1 &&
rt.attrib.num_fragments == 1;
const bool native_2x_one_frag =
rt.info.fmask_compression_enable && !rt.info.fmask_data_compression_disable &&
rt.info.cmask_fast_clear_enable && rt.fmask.addr != 0 && rt.cmask.addr != 0 &&
rt.attrib.num_samples == 1 && rt.attrib.num_fragments == 1;
EXIT_NOT_IMPLEMENTED(rt.info.fmask_one_frag_mode && !native_2x_one_frag);
if (rt.info.fmask_compression_enable) {
EXIT_NOT_IMPLEMENTED(rt.attrib.num_samples == 0 && rt.attrib.num_fragments == 0);
@@ -889,12 +888,10 @@ void LogDrawPhase(const char* draw_name, const char* phase) {
}
}
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32) {
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) {
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: %s VS=0x%08" PRIx32 "/0x%08" PRIx32 " PS=0x%08" PRIx32 "/0x%08" PRIx32
"\n",
phase, vs_hash0, vs_crc32, ps_hash0, ps_crc32);
LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id,
pixel_program_id);
}
}
@@ -1158,7 +1155,7 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
return ScissorRectClamp(final, extent.width, extent.height);
}
void hw_check(const RenderCommandBuffer& buffer) {
void hw_check(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
@@ -1231,7 +1228,7 @@ void hw_check(const RenderCommandBuffer& buffer) {
// EXIT_NOT_IMPLEMENTED(hw.GetStencilClearValue() != 0);
}
void hw_print(const RenderCommandBuffer& buffer) {
void hw_print(const CommandBuffer& buffer) {
const auto& hw = buffer.GetRegisters();
const auto rt_slot = render_target_first_bound_slot(buffer);
const auto& rt = hw.GetRenderTarget(rt_slot);
+4 -4
View File
@@ -9,7 +9,7 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
class CommandBuffer;
namespace HW {
class Context;
@@ -28,15 +28,15 @@ struct ScissorRect {
};
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot);
uint32_t render_target_first_bound_slot(const RenderCommandBuffer& buffer);
uint32_t render_target_first_bound_slot(const CommandBuffer& buffer);
bool graphics_debug_dump_enabled();
void uc_print(const char* func, const HW::UserConfig& uc);
void uc_check(const HW::UserConfig& uc);
void sh_print(const char* func, const HW::Shader& uc);
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const RenderCommandBuffer& buffer);
void hw_check(const RenderCommandBuffer& buffer);
void hw_print(const CommandBuffer& buffer);
void hw_check(const CommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
vk::Extent2D extent);
@@ -70,8 +70,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
const DepthFormatPolicy& policy,
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const CommandBuffer& buffer,
const DepthFormatPolicy& policy,
bool has_stencil,
uint32_t samples) {
auto& graphics = buffer.GetGraphics();
@@ -97,7 +97,7 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
@@ -11,8 +11,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool htile_stencil_disabled) {
return htile_stencil_disabled || (has_stencil && has_htile);
@@ -216,48 +216,6 @@ static BufferView NativeStorageBuffer(RenderContext&
return result;
}
static BufferView
NativeAddressBuffer(RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
ShaderType stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
BufferView result;
address_offset = 0;
if (address.binding_base == 0) {
BindNullStorageBuffer(context, result);
return result;
}
if (resource.written) {
EXIT("writable address resources are unsupported\n");
}
const auto limit =
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(
context.GetGraphics().GetPhysicalDeviceProperties().limits.maxStorageBufferRange);
uint64_t size = 0;
const auto access = HostMemoryAccess::Mapped;
if (!HostMemoryQueryRange(address.binding_base, limit, access, size)) {
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
address.binding_base);
}
const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment();
auto [buffer, offset] =
context.GetBufferCache().ObtainBuffer(address.binding_base, size, false, false, id);
const auto aligned_offset = offset - offset % alignment;
const auto adjustment = offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
const auto visible_size = std::min(size, static_cast<uint64_t>(max_range - adjustment));
address_offset = static_cast<uint32_t>(adjustment);
result.buffer = buffer->Handle();
result.offset = aligned_offset;
result.range = static_cast<vk::DeviceSize>(visible_size + adjustment);
SetVulkanObjectNameF(graphics.device, result.buffer,
"Kyty.{}.AddressBuffer[slot={} guest=0x{:016x} size=0x{:x}]",
ShaderStageResourceName(stage), slot, address.binding_base, visible_size);
return result;
}
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
bool supported_dimension = false;
switch (resource.dimension) {
@@ -891,7 +849,7 @@ static vk::Sampler NativeSampler(RenderContext& context,
static BufferView NativeUpload(RenderContext& context, std::span<const uint32_t> data) {
EXIT_IF(data.empty());
auto& command_buffer = context.GetCommandScheduler().Current();
EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute());
EXIT_IF(command_buffer.IsInvalid());
auto& buffer = context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
const auto offset = buffer.Copy(data.data(), data.size_bytes(), 256);
return {.buffer = buffer.Handle(), .offset = offset, .range = data.size_bytes()};
@@ -1000,25 +958,6 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
}
prepared.address_ids.clear();
prepared.address_ids.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
const auto& resource = program.info.addresses[i];
const auto& address = snapshot.addresses[i];
if (address.binding_base == 0) {
prepared.address_ids.emplace_back();
continue;
}
EXIT_IF(resource.written);
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(m_context.GetGraphics()
.GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
EXIT_IF(!HostMemoryQueryRange(address.binding_base, limit, HostMemoryAccess::Mapped, size));
prepared.address_ids.push_back(cache.FindBuffer(address.binding_base, size));
}
}
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
@@ -1028,8 +967,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
const auto& snapshot = *prepared.snapshot;
auto& resources = prepared.resources;
const auto& layout = program.bindings;
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size() ||
prepared.address_ids.size() != program.info.addresses.size());
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
resources.buffers.clear();
resources.buffers.reserve(program.info.buffers.size());
@@ -1049,15 +987,6 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
buffer_offset, prepared.buffer_ids[i]));
pack_memory_offset(i, buffer_offset);
}
resources.addresses.clear();
resources.addresses.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
uint32_t address_offset = 0;
resources.addresses.push_back(NativeAddressBuffer(m_context, program.info.addresses[i],
snapshot.addresses[i], program.stage, i,
address_offset, prepared.address_ids[i]));
pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i, address_offset);
}
if (!prepared.flattened_srt.empty()) {
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
}
@@ -1129,6 +1058,10 @@ RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
if (bindings.pixel) {
FindBuffers(*bindings.pixel);
}
if (bindings.vertex.program->info.uses_dma ||
(bindings.pixel && bindings.pixel->program->info.uses_dma)) {
m_context.GetGpuResources().PrepareBda();
}
RebindBuffers(bindings.vertex);
if (bindings.pixel) {
RebindBuffers(*bindings.pixel);
@@ -1241,13 +1174,15 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
}
break;
case BindingKind::AddressMemory:
for (const auto resource: binding.resources) {
const auto& view = descriptors.addresses.at(resource);
EXIT_IF(view.buffer == nullptr);
m_descriptor_buffers.emplace_back(view.buffer, view.offset, view.range);
}
case BindingKind::BdaPagetable:
case BindingKind::FaultBuffer: {
auto& cache = m_context.GetBufferCache();
const auto* bda_buffer = binding.kind == BindingKind::BdaPagetable
? cache.GetBdaPageTableBuffer()
: cache.GetFaultBuffer();
m_descriptor_buffers.emplace_back(bda_buffer->Handle(), 0, bda_buffer->Size());
break;
}
case BindingKind::FlattenedSrt:
case BindingKind::UserData:
case BindingKind::Gds: {
@@ -38,7 +38,6 @@ struct NativeDescriptors {
std::vector<BufferView> buffers;
std::vector<TextureBinding> images;
std::vector<vk::Sampler> samplers;
std::vector<BufferView> addresses;
BufferView gds;
BufferView flattened_srt;
BufferView user_data;
@@ -49,7 +48,6 @@ struct PreparedBindings {
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<BufferId> address_ids;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
bool committed = false;
@@ -10,11 +10,14 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderCompiler.h"
#include <atomic>
#include <cstdio>
#include <cstring>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
@@ -36,6 +39,88 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
} // namespace
struct PipelineCache::ProgramCache {
struct SourceKey {
ShaderType stage = ShaderType::Unknown;
uint64_t hash = 0;
bool operator==(const SourceKey&) const = default;
};
struct Permutation {
std::vector<uint32_t> static_key;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
ShaderProgram handle;
};
struct SourceKeyHash {
std::size_t operator()(const SourceKey& key) const {
std::size_t hash = static_cast<std::size_t>(key.stage);
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
}
return hash;
}
};
static uint64_t MixId(uint64_t hash, uint64_t value) {
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
}
static constexpr std::size_t MaxStaticKeyWords =
5 + ShaderVertexInputInfo::RES_MAX * 17;
template <typename InputInfo>
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
constexpr ShaderType stage = [] {
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
return ShaderType::Vertex;
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
return ShaderType::Pixel;
} else {
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
return ShaderType::Compute;
}
}();
BuildStageStaticKey(input_info, key_scratch);
auto& permutations = programs[{stage, params.hash}];
for (const auto& permutation: permutations) {
if (permutation.static_key == key_scratch &&
MaterializeProgram(permutation.program, params, input_info)) {
return permutation.handle;
}
}
const auto module = CompileProgram(device, params, input_info);
EXIT_IF(module == nullptr || !input_info.stage);
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
if (id == 0) {
id = 1;
}
const ShaderProgram handle {.id = id, .module = module};
permutations.push_back({key_scratch, input_info.stage.program, handle});
std::printf("Num compiled %u shaders\n", ++num_compiled);
return handle;
}
explicit ProgramCache(vk::Device device): device(device) {
key_scratch.reserve(MaxStaticKeyWords);
}
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
std::vector<uint32_t> key_scratch;
vk::Device device;
uint32_t num_compiled = 0;
};
PipelineCache::PipelineCache(GraphicContext& graphics)
: m_graphics(graphics), m_program_cache(std::make_unique<ProgramCache>(graphics.device)) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
PipelineCache::~PipelineCache() {
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
@@ -47,6 +132,39 @@ PipelineCache::~PipelineCache() {
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
for (const auto& [key, permutations]: m_program_cache->programs) {
(void)key;
for (const auto& permutation: permutations) {
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
}
}
}
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetPixelProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info) {
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info) {
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
const auto params = PrepareProgram(regs, sh, input_info);
Common::LockGuard lock(m_mutex);
return m_program_cache->Get(params, input_info);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
@@ -54,24 +172,22 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, bool ps_active, std::span<const uint32_t> vs_spirv,
std::span<const uint32_t> ps_spirv) {
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors == nullptr);
EXIT_IF(color_count > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(vs_spirv.empty());
EXIT_IF(ps_active && ps_spirv.empty());
EXIT_IF(colors.size() > RENDER_COLOR_ATTACHMENTS_MAX);
EXIT_IF(!vertex_program);
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(ps_active && !pixel_program);
const auto color_count = static_cast<uint32_t>(colors.size());
Common::LockGuard lock(m_mutex);
auto& ctx = command.GetRegisters();
auto& sh_ctx = command.GetShaders();
auto& ctx = command.GetRegisters();
const auto& vertex_info = sh_ctx.GetVs();
const auto& ps_regs = sh_ctx.GetPs();
const HW::BlendColor& bclr = ctx.GetBlendColor();
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
@@ -82,11 +198,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
const HW::ModeControl& mc = ctx.GetModeControl();
auto vs_id = ShaderGetIdVS(vertex_info, vs_input_info, true);
ShaderId ps_id {};
if (ps_active) {
ps_id = ShaderGetIdPS(ps_regs, *ps_input_info, true);
}
const auto vs_id = vertex_program.id;
const auto ps_id = ps_active ? pixel_program.id : 0;
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
@@ -202,20 +315,17 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
if (ps_active) {
ShaderDbgDumpInputInfo(*ps_input_info);
}
LOGF("PipelineTrace: shader binaries VS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " words=%" PRIu64 "\n",
vs_id.hash0, vs_id.crc32, static_cast<uint64_t>(vs_spirv.size()), ps_id.hash0,
ps_id.crc32, static_cast<uint64_t>(ps_spirv.size()));
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
" module=%p\n",
vs_id, static_cast<void*>(vertex_program.module), ps_id,
static_cast<void*>(pixel_program.module));
}
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(m_graphics, *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);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
ps_input_info, pixel_program.module, static_params);
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -227,19 +337,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
PipelineCache::ComputePipeline&
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv) {
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
EXIT_IF(cs_spirv.empty());
EXIT_IF(!compute_program);
Common::LockGuard lock(m_mutex);
auto cs_id = ShaderGetIdCS(cs_regs, input_info, true);
ComputePipeline p {};
p.cs_shader_id = cs_id;
p.cs_shader_id = compute_program.id;
ComputePipelineKey key {};
key.cs_shader_id = p.cs_shader_id;
@@ -253,7 +360,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(m_graphics, *cached, input_info, cs_spirv);
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
@@ -20,7 +20,7 @@ namespace Libs::Graphics {
struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
class RenderCommandBuffer;
class CommandBuffer;
namespace HW {
class Context;
@@ -95,11 +95,18 @@ struct PipelineRenderingState {
bool operator==(const PipelineRenderingState&) const = default;
};
struct ShaderProgram {
uint64_t id = 0;
vk::ShaderModule module = nullptr;
explicit operator bool() const {
return id != 0 && module != nullptr;
}
};
class PipelineCache {
public:
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
explicit PipelineCache(GraphicContext& graphics);
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
@@ -111,29 +118,42 @@ public:
};
struct GraphicsPipeline: Pipeline {
ShaderId vs_shader_id;
ShaderId ps_shader_id;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
};
struct ComputePipeline: Pipeline {
ShaderId cs_shader_id;
uint64_t cs_shader_id = 0;
};
GraphicsPipeline&
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, 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);
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info);
ShaderProgram
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
GraphicsPipeline& CreateGraphicsPipeline(
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, const ShaderProgram& vertex_program,
const ShaderProgram& pixel_program);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const ShaderProgram& compute_program);
private:
struct ProgramCache;
struct GraphicsPipelineKey {
PipelineRenderingState rendering;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
uint64_t vs_shader_id = 0;
uint64_t ps_shader_id = 0;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
@@ -143,7 +163,7 @@ private:
};
struct ComputePipelineKey {
ShaderId cs_shader_id;
uint64_t cs_shader_id = 0;
bool operator==(const ComputePipelineKey& other) const {
return cs_shader_id == other.cs_shader_id;
@@ -156,15 +176,6 @@ private:
(hash >> 2u);
}
static void MixShaderId(std::size_t& hash, const ShaderId& id) {
Mix(hash, id.hash0);
Mix(hash, id.crc32);
Mix(hash, id.ids.size());
for (auto value: id.ids) {
Mix(hash, value);
}
}
static void MixStaticParams(std::size_t& hash, const PipelineStaticParameters& params) {
const auto* bytes = reinterpret_cast<const uint8_t*>(&params);
for (std::size_t i = 0; i < sizeof(params); i++) {
@@ -186,8 +197,8 @@ private:
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
PipelineKeyHash::Mix(hash, key.vs_shader_id);
PipelineKeyHash::Mix(hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(hash, key.static_params);
return hash;
}
@@ -196,12 +207,13 @@ private:
struct ComputePipelineKeyHash {
std::size_t operator()(const ComputePipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::MixShaderId(hash, key.cs_shader_id);
PipelineKeyHash::Mix(hash, key.cs_shader_id);
return hash;
}
};
GraphicContext& m_graphics;
GraphicContext& m_graphics;
std::unique_ptr<ProgramCache> m_program_cache;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
@@ -210,17 +222,15 @@ private:
Common::Mutex m_mutex;
};
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32);
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
void CreatePipelineInternal(
GraphicContext& graphics, 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);
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params);
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
vk::ShaderModule compute_module);
} // namespace Libs::Graphics
@@ -422,43 +422,19 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
void CreatePipelineInternal(
GraphicContext& graphics, 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 vertex_module, const ShaderPixelInputInfo* ps_input_info,
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params) {
const bool ps_active = ps_input_info != nullptr;
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule tess_control_shader_module = nullptr;
vk::ShaderModule tess_eval_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
create_info.codeSize = vs_shader.size() * 4;
create_info.pCode = vs_shader.data();
auto result = graphics.device.createShaderModule(&create_info, nullptr, &vert_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule VS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(vert_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (ps_active) {
create_info.codeSize = ps_shader.size() * 4;
create_info.pCode = ps_shader.data();
result = graphics.device.createShaderModule(&create_info, nullptr, &frag_shader_module);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateShaderModule PS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(frag_shader_module));
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
vk::Result result {};
if (rect_list) {
const auto shaders =
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
@@ -483,10 +459,8 @@ void CreatePipelineInternal(
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
EXIT_NOT_IMPLEMENTED(vert_shader_module == nullptr);
EXIT_NOT_IMPLEMENTED(
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
EXIT_NOT_IMPLEMENTED(ps_active && frag_shader_module == nullptr);
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
@@ -494,7 +468,7 @@ void CreatePipelineInternal(
vert_shader_stage_info.pNext = nullptr;
vert_shader_stage_info.flags = {};
vert_shader_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
vert_shader_stage_info.module = vert_shader_module;
vert_shader_stage_info.module = vertex_module;
vert_shader_stage_info.pName = "main";
vert_shader_stage_info.pSpecializationInfo = nullptr;
@@ -503,7 +477,7 @@ void CreatePipelineInternal(
frag_shader_stage_info.pNext = nullptr;
frag_shader_stage_info.flags = {};
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
frag_shader_stage_info.module = frag_shader_module;
frag_shader_stage_info.module = pixel_module;
frag_shader_stage_info.pName = "main";
frag_shader_stage_info.pSpecializationInfo = nullptr;
@@ -854,9 +828,9 @@ void CreatePipelineInternal(
EXIT_IF(pipeline.pipeline_layout != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=0x%08" PRIx32 "/0x%08" PRIx32
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " set_layouts=1 push_constants=%" PRIu32 "\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, 1u);
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
" set_layouts=1 push_constants=%" PRIu32 "\n",
pipeline.vs_shader_id, pipeline.ps_shader_id, 1u);
}
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
@@ -955,11 +929,12 @@ void CreatePipelineInternal(
EXIT_IF(pipeline.pipeline != nullptr);
if (graphics_debug_dump_enabled()) {
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=0x%08" PRIx32 "/0x%08" PRIx32
" PS=0x%08" PRIx32 "/0x%08" PRIx32 " topology=%" PRIu32 " color_mask=0x%08" PRIx32
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
" depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
" scissor=%d,%d %ux%u\n",
vs_hash0, vs_crc32, ps_hash0, ps_crc32, static_cast<uint32_t>(static_params.topology),
pipeline.vs_shader_id, pipeline.ps_shader_id,
static_cast<uint32_t>(static_params.topology),
static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
(static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
@@ -975,39 +950,19 @@ void CreatePipelineInternal(
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
if (frag_shader_module != nullptr) {
graphics.device.destroyShaderModule(frag_shader_module, nullptr);
}
if (tess_control_shader_module != nullptr) {
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
}
if (tess_eval_shader_module != nullptr) {
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
}
graphics.device.destroyShaderModule(vert_shader_module, nullptr);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
vk::ShaderModule comp_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
create_info.codeSize = cs_shader.size() * 4;
create_info.pCode = cs_shader.data();
LOGF("PipelineTrace: vkCreateShaderModule CS begin words=%" PRIu64 "\n",
static_cast<uint64_t>(cs_shader.size()));
auto result = graphics.device.createShaderModule(&create_info, nullptr, &comp_shader_module);
LOGF("PipelineTrace: vkCreateShaderModule CS done result=%s module=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(comp_shader_module));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(comp_shader_module == nullptr);
vk::ShaderModule compute_module) {
EXIT_IF(compute_module == nullptr);
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
@@ -1015,7 +970,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
comp_shader_stage_info.pNext = nullptr;
comp_shader_stage_info.flags = {};
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
comp_shader_stage_info.module = comp_shader_module;
comp_shader_stage_info.module = compute_module;
comp_shader_stage_info.pName = "main";
comp_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!input_info.stage);
@@ -1048,8 +1003,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
1u);
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
&pipeline.pipeline_layout);
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -1075,8 +1030,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
graphics.device.destroyShaderModule(comp_shader_module, nullptr);
}
} // namespace Libs::Graphics
+34 -54
View File
@@ -25,7 +25,6 @@ class Shader;
struct GraphicContext;
struct ShaderBufferResource;
struct ShaderComputeInputInfo;
struct CommandSlot;
struct RenderDepthInfo;
struct RenderColorInfo;
struct DrawCallInfo;
@@ -77,40 +76,38 @@ struct SubmitInfo {
class CommandBuffer {
public:
explicit CommandBuffer(CommandScheduler& scheduler);
~CommandBuffer();
~CommandBuffer() = default;
KYTY_CLASS_NO_COPY(CommandBuffer);
[[nodiscard]] bool IsInvalid() const;
void Begin() const;
void End() const;
void Execute(const SubmitInfo& submit = {});
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
void BeginRendering(const RenderState& state) const;
void EndRendering() const;
void WaitForFenceOnly();
void WaitForFence();
void WaitForFenceAndReset();
[[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
[[nodiscard]] bool IsExecute() const { return m_execute; }
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
private:
void Release();
void FinalizeFence(bool reset_recording);
explicit CommandBuffer(CommandScheduler& scheduler);
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
}
void Begin();
void End() const;
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
bool m_fence_waited = false;
uint64_t m_submit_seq = 0;
vk::CommandBuffer m_buffer = nullptr;
uint32_t m_debug_op = 0;
uint64_t m_debug_submit_id = 0;
uint32_t m_debug_arg0 = 0;
@@ -119,27 +116,12 @@ private:
uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
};
mutable bool m_rendering = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
class RenderCommandBuffer final: public CommandBuffer {
public:
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
}
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
private:
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
friend class CommandScheduler;
};
class RenderExecutor {
@@ -147,15 +129,14 @@ public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
KYTY_CLASS_NO_COPY(RenderExecutor);
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_type_and_size,
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
void DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset = 0,
uint32_t instance_count = 1, uint32_t first_vertex = 0,
uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
uint32_t first_vertex = 0, uint32_t first_instance = 0);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] PreparedBindings PrepareBindings(const ShaderStageRuntime& runtime);
@@ -177,32 +158,31 @@ private:
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
DrawRenderState& state, vk::PrimitiveTopology topology,
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void TrackImageBinding(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer);
const CommandBuffer& buffer);
RenderContext& m_context;
std::vector<ImageId> m_bound_images;
@@ -44,7 +44,7 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
}
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer) {
const CommandBuffer& buffer) {
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (resources.buffers.size() != program.info.buffers.size()) {
@@ -81,9 +81,8 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
const auto& program = *input.stage.program;
const auto& resources = *input.stage.resources;
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
!program.info.images.empty() || !program.info.samplers.empty() ||
!program.info.addresses.empty() || !resources.images.empty() ||
!resources.samplers.empty() || !resources.addresses.empty()) {
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
!resources.images.empty() || !resources.samplers.empty()) {
return false;
}
const auto& resource = program.info.buffers.front();
@@ -163,7 +162,7 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
return true;
}
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(buffer.IsInvalid());
@@ -208,18 +207,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
const auto& cs_regs = sh_ctx.GetCs();
const auto& sh_regs = ctx.GetShaderRegisters();
ShaderComputeInputInfo input_info {};
std::span<const uint32_t> cs_shader;
if (!ShaderCompileInfoCS(cs_regs, sh_regs,
!m_context.GetGraphics().compute_wave64_supported, input_info,
cs_shader)) {
EXIT("ShaderCompileInfoCS failed for dispatch with CS shader 0x%016" PRIx64 "\n",
cs_regs.cs_regs.data_addr);
}
ShaderComputeInputInfo input_info {};
const bool use_thread_dimensions = (mode & DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0;
input_info.dispatch_thread_dimensions = use_thread_dimensions;
const auto compute_program =
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
if (use_thread_dimensions) {
input_info.dispatch_thread_dimensions = true;
input_info.dispatch_threads_num[0] = thread_group_x;
input_info.dispatch_threads_num[1] = thread_group_y;
input_info.dispatch_threads_num[2] = thread_group_z;
@@ -334,9 +327,12 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
m_context.GetPipelineCache().CreateComputePipeline(input_info, compute_program);
auto bindings = PrepareBindings(input_info.stage);
FindBuffers(bindings);
if (program.info.uses_dma) {
m_context.GetGpuResources().PrepareBda();
}
RebindBuffers(bindings);
RebindImages(bindings);
@@ -38,7 +38,7 @@ void RenderContext::ShutdownGpu() {
}
if (m_video_out != nullptr) {
if (m_command_scheduler.Active()) {
m_command_scheduler.FinishCurrent();
m_command_scheduler.Finish();
}
m_command_scheduler.DrainPriorityOperations();
m_video_out = nullptr;
+45 -47
View File
@@ -103,7 +103,7 @@ static bool IsDualSourceBlendFactor(uint32_t factor) {
}
static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& color,
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
const RenderDepthInfo& depth, const CommandBuffer& buffer,
uint32_t index_count, uint32_t flags) {
const auto& ctx = buffer.GetRegisters();
const auto& ucfg = buffer.GetUserConfig();
@@ -126,7 +126,7 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags);
}
static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer,
static void LogMrtState(const char* draw_name, const CommandBuffer& buffer,
const ShaderPixelInputInfo& ps_input_info) {
const auto& ctx = buffer.GetRegisters();
const auto& sh_regs = ctx.GetShaderRegisters();
@@ -177,7 +177,7 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
}
static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& color,
const RenderDepthInfo& depth, const RenderCommandBuffer& buffer,
const RenderDepthInfo& depth, const CommandBuffer& buffer,
const ShaderPixelInputInfo& ps_input_info, uint32_t index_count,
uint32_t flags) {
const auto& ctx = buffer.GetRegisters();
@@ -231,7 +231,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info);
}
static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderColorInfo& color,
static void LogDrawInputState(const CommandBuffer& buffer, const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) {
@@ -309,7 +309,7 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
}
}
static void SetGraphicsDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const RenderColorInfo* colors, uint32_t color_count,
const RenderDepthInfo& depth) {
KYTY_PROFILER_FUNCTION();
@@ -421,7 +421,7 @@ static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters&
db.shader_execute_on_noop;
}
static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
static bool ShouldSkipGeShader(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto& ucfg = buffer.GetUserConfig();
const auto& sh_ctx = buffer.GetShaders();
@@ -483,15 +483,15 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
}
struct DrawRenderState {
RenderDepthInfo depth_info;
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
std::span<const uint32_t> vs_shader;
std::span<const uint32_t> ps_shader;
RenderDepthInfo depth_info;
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
ShaderProgram vertex_program;
ShaderProgram pixel_program;
};
struct DrawCallInfo {
@@ -706,7 +706,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
return state;
}
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer) {
static bool DrawHasActivePixelShader(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto& sh_regs = ctx.GetShaderRegisters();
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
@@ -723,7 +723,7 @@ enum class CbColorMode : uint8_t {
DccDecompress = 6,
};
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
static bool ConsumeMetadataColorOperation(const CommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto mode = ctx.GetColorControl().mode;
// These special modes run color-buffer metadata or decompression operations. The shader is a
@@ -779,7 +779,7 @@ struct PreparedVertexBuffers {
uint32_t count = 0;
};
static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer,
static PreparedVertexBuffers AcquireVertexBuffers(CommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(vs_input_info.buffers_num < 0 ||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
@@ -869,8 +869,8 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
return prepared;
}
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
const DrawCallInfo& draw, uint32_t phase) {
static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const DrawCallInfo& draw,
uint32_t phase) {
EXIT_IF(draw.name == nullptr);
buffer.SetDebugInfo(static_cast<uint32_t>(draw.debug_op), submit_id, phase, draw.index_count,
@@ -918,8 +918,8 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
return true;
}
static bool ResolvePrimitiveRestart(const RenderCommandBuffer& buffer,
vk::PrimitiveTopology topology, uint32_t index_type_and_size) {
static bool ResolvePrimitiveRestart(const CommandBuffer& buffer, vk::PrimitiveTopology topology,
uint32_t index_type_and_size) {
const auto control = buffer.GetUserConfig().GetPrimitiveResetControl();
EXIT_NOT_IMPLEMENTED((control & ~0x3u) != 0);
if ((control & 0x1u) == 0) {
@@ -953,7 +953,7 @@ static bool ResolvePrimitiveRestart(const RenderCommandBuffer& buffer,
return true;
}
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) {
@@ -993,7 +993,7 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
return true;
}
static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool log_phases,
DrawRenderState& state) {
EXIT_IF(draw.name == nullptr);
auto& ctx = buffer.GetRegisters();
@@ -1003,35 +1003,33 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
const auto& pixel_shader_info = sh_ctx.GetPs();
const auto& shader_regs = ctx.GetShaderRegisters();
state.vs_shader = {};
state.ps_shader = {};
state.ps_input_info = {};
state.vertex_program = {};
state.pixel_program = {};
state.ps_input_info = {};
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
target_export_mapping {};
for (uint32_t i = 0; i < state.color_count; i++) {
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
}
if (log_phases) {
LogDrawPhase(draw.name, "ShaderCompileInfoVS");
}
if (!ShaderCompileInfoVS(vertex_shader_info, shader_regs, state.vs_input_info,
state.vs_shader)) {
EXIT("ShaderCompileInfoVS failed for draw %s\n", draw.name);
LogDrawPhase(draw.name, "GetVertexProgram");
}
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
state.vertex_program =
pipeline_cache.GetVertexProgram(vertex_shader_info, shader_regs, state.vs_input_info);
if (!state.ps_active) {
return;
}
if (log_phases) {
LogDrawPhase(draw.name, "ShaderCompileInfoPS");
}
if (!ShaderCompileInfoPS(pixel_shader_info, shader_regs, state.vs_input_info,
target_export_mapping, state.ps_input_info, state.ps_shader)) {
EXIT("ShaderCompileInfoPS failed for draw %s\n", draw.name);
LogDrawPhase(draw.name, "GetPixelProgram");
}
state.pixel_program =
pipeline_cache.GetPixelProgram(pixel_shader_info, shader_regs, state.vs_input_info,
target_export_mapping, state.ps_input_info);
}
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr);
@@ -1041,7 +1039,7 @@ static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderComm
return AcquireVertexBuffers(buffer, vs_input_info);
}
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
static PreparedIndexBuffer PrepareIndexBuffer(CommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
if (!source.enabled) {
@@ -1091,7 +1089,7 @@ static void CommitIndexBuffer(vk::CommandBuffer vk_buffer, const PreparedIndexBu
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
}
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo& draw,
const DrawRenderState& state, bool always_log,
bool force_legacy_rect_log, uint32_t index_type_and_size,
const void* index_addr) {
@@ -1159,7 +1157,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
}
}
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
@@ -1180,9 +1178,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
LogDrawPhase(draw.name, "CreatePipeline");
}
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
&state.ps_input_info, topology, primitive_restart_enable, state.ps_active, state.vs_shader,
state.ps_shader);
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
state.vertex_program, state.pixel_program);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1243,7 +1241,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
}
}
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr, uint32_t flags, uint32_t type,
uint32_t instance_count, uint32_t render_target_slice_offset,
@@ -1376,7 +1374,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex,
uint32_t first_instance) {
@@ -1475,7 +1473,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
ResetBindings();
}
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) {
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_gpu_shader_int64 : enable
layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
layout(std430, binding = 0) buffer input_buf {
uint fault_buffer[];
};
layout(std430, binding = 1) buffer output_buf {
uint64_t download_buffer[];
};
layout(std430, binding = 1) buffer output_buf32 {
uint download_buffer32[];
};
void main() {
const uint id = gl_GlobalInvocationID.x;
uint word = fault_buffer[id];
fault_buffer[id] = 0u;
const uint base_bit = id * 32u;
while (word != 0u) {
const uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
if (store_index >= MAX_PAGE_FAULTS) {
return;
}
const uint bit = findLSB(word);
word &= word - 1;
const uint page = base_bit + bit;
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
}
}
+4 -1
View File
@@ -72,7 +72,10 @@ bool GraphicContext::CreateAllocator() {
info.device = device;
info.pVulkanFunctions = &functions;
info.vulkanApiVersion = VULKAN_TARGET_API_VERSION;
info.flags = memory_budget_ext_enabled ? VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT : 0;
info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
if (memory_budget_ext_enabled) {
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
if (result != vk::Result::eSuccess) {
-95
View File
@@ -1,95 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <map>
#include <mutex>
#include <tuple>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
struct VulkanInstance {
vk::Instance instance = nullptr;
vk::DebugUtilsMessengerEXT debug_messenger = nullptr;
vk::PhysicalDevice physical_device = nullptr;
vk::PhysicalDeviceProperties physical_device_properties = {};
vk::PhysicalDeviceMemoryProperties physical_device_memory_properties = {};
vk::Device device = nullptr;
VmaAllocator allocator = nullptr;
bool memory_budget_ext_enabled = false;
bool rt_extensions_enabled = false;
bool compute_subgroup_size_control_enabled = false;
bool compute_wave64_supported = false;
bool sample_rate_shading_enabled = false;
uint32_t subgroup_size = 0;
uint32_t min_subgroup_size = 0;
uint32_t max_subgroup_size = 0;
uint32_t max_push_descriptors = 0;
vk::ShaderStageFlags required_subgroup_size_stages = {};
Common::Mutex queue_mutex;
uint32_t queue_family = static_cast<uint32_t>(-1);
vk::Queue queue = nullptr;
[[nodiscard]] const vk::PhysicalDeviceProperties& GetPhysicalDeviceProperties() const {
return physical_device_properties;
}
[[nodiscard]] const vk::PhysicalDeviceMemoryProperties&
GetPhysicalDeviceMemoryProperties() const {
return physical_device_memory_properties;
}
[[nodiscard]] vk::FormatProperties GetFormatProperties(vk::Format format) const {
std::scoped_lock lock(m_format_properties_mutex);
auto [it, inserted] = m_format_properties.try_emplace(format);
if (inserted) {
physical_device.getFormatProperties(format, &it->second);
}
return it->second;
}
[[nodiscard]] vk::Result GetImageFormatProperties(vk::Format format, vk::ImageType type,
vk::ImageTiling tiling,
vk::ImageUsageFlags usage,
vk::ImageCreateFlags flags,
vk::ImageFormatProperties* properties) const {
using Key = std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>;
std::scoped_lock lock(m_image_format_properties_mutex);
auto [it, inserted] =
m_image_format_properties.try_emplace(Key {format, type, tiling, usage, flags});
if (inserted) {
it->second.first = physical_device.getImageFormatProperties(format, type, tiling, usage,
flags, &it->second.second);
}
if (properties != nullptr) {
*properties = it->second.second;
}
return it->second.first;
}
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
return alignment != 0 ? alignment : 1;
}
private:
mutable std::mutex m_format_properties_mutex;
mutable std::map<vk::Format, vk::FormatProperties> m_format_properties;
mutable std::mutex m_image_format_properties_mutex;
mutable std::map<std::tuple<vk::Format, vk::ImageType, vk::ImageTiling, vk::ImageUsageFlags,
vk::ImageCreateFlags>,
std::pair<vk::Result, vk::ImageFormatProperties>>
m_image_format_properties;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_VULKANINSTANCE_H_
+38 -107
View File
@@ -1,30 +1,9 @@
#include "SDL.h"
#include "SDL_error.h"
#include "SDL_events.h"
#include "SDL_gamecontroller.h"
#include "SDL_hints.h"
#include "SDL_joystick.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
#include "SDL_pixels.h"
#include "SDL_rwops.h"
#include "SDL_stdinc.h"
#include "SDL_surface.h"
#include "SDL_thread.h"
#include "SDL_touch.h"
#include "SDL_video.h"
#include "SDL_vulkan.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -34,19 +13,11 @@
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <fmt/format.h>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include <vulkan/vk_platform.h>
@@ -58,10 +29,10 @@
namespace Libs::Graphics {
struct Presenter::Frame {
VulkanImage image;
std::unique_ptr<CommandBuffer> present_commands;
bool busy = false;
bool reusing_last = false;
VulkanImage image;
uint64_t present_tick = 0;
bool busy = false;
bool reusing_last = false;
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
@@ -71,12 +42,11 @@ struct Presenter::Frame {
class FramePool final {
public:
explicit FramePool(WindowContext& window): m_window(window) {}
FramePool(WindowContext& window, CommandScheduler& scheduler)
: m_window(window), m_scheduler(scheduler) {}
~FramePool() {
m_scheduler.Wait(m_scheduler.CurrentTick() - 1);
for (auto& frame: m_frames) {
if (frame->present_commands != nullptr) {
frame->present_commands->WaitForFenceOnly();
}
if (frame->image.image != nullptr) {
m_window.graphic_ctx.DeleteImage(frame->image);
}
@@ -188,15 +158,10 @@ public:
}
private:
static void WaitForFrame(Presenter::Frame& frame) {
// The producer only waits here. Reset stays on the presentation thread that owns the
// allocating Vulkan command pool.
if (frame.present_commands != nullptr) {
frame.present_commands->WaitForFenceOnly();
}
}
void WaitForFrame(Presenter::Frame& frame) { m_scheduler.Wait(frame.present_tick); }
WindowContext& m_window;
CommandScheduler& m_scheduler;
Common::Mutex m_mutex;
Common::CondVar m_available;
std::vector<std::unique_ptr<Presenter::Frame>> m_frames;
@@ -330,7 +295,7 @@ public:
[[nodiscard]] Status AcquireNextImage();
[[nodiscard]] bool PrepareImeOverlay();
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
void Submit(CommandBuffer& command);
uint64_t Submit(CommandScheduler& scheduler);
[[nodiscard]] Status Present();
[[nodiscard]] uint32_t ImageCount() const noexcept {
@@ -340,7 +305,6 @@ public:
private:
void Destroy();
void RefreshSurfaceSize();
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
@@ -358,7 +322,7 @@ private:
struct Presenter::Impl {
explicit Impl(WindowContext& owner)
: renderer(*owner.render_context), window(owner), swapchain(owner),
present_scheduler(renderer, owner.graphic_ctx), frames(owner) {
present_scheduler(renderer, owner.graphic_ctx), frames(owner, present_scheduler) {
EXIT_IF(owner.render_context == nullptr);
swapchain.Create();
frames.Initialize(swapchain.ImageCount(), swapchain.Format());
@@ -402,12 +366,12 @@ struct Presenter::Impl {
void Swapchain::Create() {
auto& graphics = m_window.graphic_ctx;
EXIT_IF(graphics.screen_width == 0);
EXIT_IF(graphics.screen_height == 0);
EXIT_IF(graphics.device == nullptr);
EXIT_IF(m_window.surface == nullptr);
Common::LockGuard lock(m_window.mutex);
EXIT_IF(graphics.screen_width == 0);
EXIT_IF(graphics.screen_height == 0);
const auto& surface = m_window.surface_capabilities;
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
@@ -465,7 +429,21 @@ void Swapchain::Create() {
create_info.imageSharingMode = vk::SharingMode::eExclusive;
create_info.preTransform = transform;
create_info.compositeAlpha = composite;
create_info.presentMode = vk::PresentModeKHR::eFifo;
switch (Config::GetPresentMode()) {
case Config::PresentMode::Mailbox:
create_info.presentMode = vk::PresentModeKHR::eMailbox;
break;
case Config::PresentMode::Immediate:
create_info.presentMode = vk::PresentModeKHR::eImmediate;
break;
case Config::PresentMode::Fifo:
default: create_info.presentMode = vk::PresentModeKHR::eFifo; break;
}
if (std::find(surface.present_modes.begin(), surface.present_modes.end(),
create_info.presentMode) == surface.present_modes.end()) {
LOGF("warning: requested present mode is unavailable; falling back to Fifo\n");
create_info.presentMode = vk::PresentModeKHR::eFifo;
}
create_info.clipped = VK_TRUE;
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
"vkCreateSwapchainKHR");
@@ -560,30 +538,18 @@ void Swapchain::Destroy() {
m_render_complete.clear();
}
void Swapchain::RefreshSurfaceSize() {
int width = 0;
int height = 0;
SDL_Vulkan_GetDrawableSize(m_window.window, &width, &height);
if (width > 0 && height > 0) {
m_window.graphic_ctx.screen_width = static_cast<uint32_t>(width);
m_window.graphic_ctx.screen_height = static_cast<uint32_t>(height);
}
m_window.RefreshSurfaceCapabilities();
}
void Swapchain::Recreate(bool surface_lost) {
Destroy();
if (surface_lost) {
#if defined(__APPLE__)
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
// window's view/layer and must run on the main thread on macOS.
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); }, true);
m_window.RunOnMainThread([this] { m_window.RecreateSurface(); });
#else
m_window.RecreateSurface();
#endif
}
RefreshSurfaceSize();
m_window.RefreshSurfaceCapabilities();
Create();
}
@@ -628,7 +594,6 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
}
EXIT_IF(m_image_index >= m_images.size());
auto vk_command = command.Handle();
command.Begin();
vk::ImageMemoryBarrier to_transfer {};
to_transfer.sType = vk::StructureType::eImageMemoryBarrier;
@@ -699,15 +664,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
&to_present);
}
command.End();
}
void Swapchain::Submit(CommandBuffer& command) {
uint64_t Swapchain::Submit(CommandScheduler& scheduler) {
EXIT_IF(m_frame_index >= m_image_acquired.size() || m_image_index >= m_render_complete.size());
SubmitInfo submit;
submit.AddWait(m_image_acquired[m_frame_index], 1, vk::PipelineStageFlagBits::eTransfer);
submit.AddSignal(m_render_complete[m_image_index]);
command.Execute(submit);
return scheduler.Submit(submit);
}
Swapchain::Status Swapchain::Present() {
@@ -782,15 +746,9 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
EXIT_IF(producer->IsInvalid());
frame->Clear(*producer, clear);
} else {
if (frame->present_commands == nullptr) {
frame->present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
}
auto& command = *frame->present_commands;
command.WaitForFenceAndReset();
command.Begin();
auto& command = m_impl->present_scheduler.BeginCommand();
frame->Clear(command, clear);
command.End();
command.Execute();
frame->present_tick = m_impl->present_scheduler.Submit();
}
return *frame;
}
@@ -817,29 +775,6 @@ void Presenter::Present(Frame& frame, bool reuse) {
KYTY_PROFILER_FUNCTION();
m_impl->frames.ValidateForPresent(&frame, reuse);
auto& window = m_impl->window;
if (window.window_hidden) {
#if defined(__APPLE__)
// AppKit traps if a window is shown off the main thread; marshal and wait so the
// swapchain below is recreated against a visible window.
window.RunOnMainThread(
[&window] {
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
},
true);
#else
window.UpdateIcon();
SDL_ShowWindow(window.window);
SDL_RaiseWindow(window.window);
#endif
window.window_hidden = false;
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
}
const auto ime_visual = GetImeVisualState();
auto& swapchain = m_impl->swapchain;
for (uint32_t attempt = 0; attempt < 2; attempt++) {
@@ -848,16 +783,12 @@ void Presenter::Present(Frame& frame, bool reuse) {
m_impl->RecoverSwapchain(status);
continue;
}
if (frame.present_commands == nullptr) {
frame.present_commands = std::make_unique<CommandBuffer>(m_impl->present_scheduler);
}
{
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
frame.present_commands->WaitForFenceAndReset();
auto& command = *frame.present_commands;
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
auto& command = m_impl->present_scheduler.BeginCommand();
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
swapchain.Submit(command);
frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
}
status = swapchain.Present();
if (status != Swapchain::Status::Success) {
@@ -866,7 +797,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
}
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
window.UpdateTitle();
m_impl->window.UpdateTitle();
m_impl->frames.Release(&frame, true);
return;
}
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
@@ -594,20 +593,16 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
#if !defined(__APPLE__)
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
#endif
features12.timelineSemaphore = VK_TRUE;
features12.shaderOutputLayer = VK_TRUE;
if (Config::GpuAssistedValidationEnabled()) {
if (supported_features12.bufferDeviceAddress != VK_TRUE) {
EXIT("--gpu-assisted-validation requires bufferDeviceAddress, unsupported by this "
"device\n");
}
features12.bufferDeviceAddress = VK_TRUE;
}
features12.bufferDeviceAddress = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {};
device_features.fragmentStoresAndAtomics = VK_TRUE;
@@ -625,12 +620,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.shaderClipDistance = VK_TRUE;
device_features.shaderCullDistance = VK_TRUE;
device_features.largePoints = VK_TRUE;
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics;
if (Config::GpuAssistedValidationEnabled()) {
device_features.shaderInt64 = supported_features2.features.shaderInt64;
}
device_features.shaderInt64 = VK_TRUE;
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
@@ -1044,10 +1036,6 @@ void WindowContext::CreateVulkan() {
}
}
memcpy(device_name, device_properties.deviceName, sizeof(device_name));
std::snprintf(processor_name, sizeof(processor_name), "%s",
Common::GetSystemInfo().ProcessorName.c_str());
VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx);
graphic_ctx.device = VulkanCreateDevice(graphic_ctx.physical_device, r, queue_family,
@@ -1086,7 +1074,6 @@ void WindowContext::RecreateSurface() {
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
}
surface = native_surface;
RefreshSurfaceCapabilities();
}
WindowContext::~WindowContext() {
+18 -30
View File
@@ -23,7 +23,6 @@
#include "common/file.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
@@ -39,21 +38,18 @@
#include "libs/controller.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <vulkan/vk_platform.h>
#include <fmt/format.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_SIMD
#include "stb_image.h"
#include <fmt/format.h>
// IWYU pragma: no_include <intrin.h>
#define KYTY_ENABLE_DEBUG_PRINTF
@@ -197,8 +193,6 @@ std::unique_ptr<WindowContext> g_window;
} // namespace
constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game";
constexpr uint32_t KYTY_SDL_WINDOW_FLAGS =
(static_cast<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(SDL_WINDOW_VULKAN));
constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/
static void SetPause(WindowLoopState& game, bool flag) {
@@ -407,6 +401,7 @@ static void GameEventDidEnterForeground(WindowLoopState& game) {
void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
EXIT_IF(new_width == 0 || new_height == 0);
Common::LockGuard lock(mutex);
graphic_ctx.screen_width = new_width;
graphic_ctx.screen_height = new_height;
}
@@ -728,8 +723,7 @@ void WindowContext::ProcessEvent(double time_s) {
}
}
#if defined(__APPLE__)
void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
void WindowContext::RunOnMainThread(std::function<void()> task) {
if (Common::Thread::IsMainThread()) {
task();
return;
@@ -747,9 +741,6 @@ void WindowContext::RunOnMainThread(std::function<void()> task, bool wait) {
event.type = SDL_USEREVENT;
SDL_PushEvent(&event);
if (!wait) {
return;
}
Common::LockGuard lock(main_task_mutex);
while (main_tasks_run < ticket) {
main_task_done.Wait(&main_task_mutex);
@@ -772,7 +763,6 @@ void WindowContext::DrainMainThreadTasks() {
main_tasks_run += tasks.size();
main_task_done.SignalAll();
}
#endif
void WindowContext::Run() {
Common::Timer timer;
@@ -783,9 +773,7 @@ void WindowContext::Run() {
loop.paused.store(false, std::memory_order_release);
while (!loop.need_exit) {
#if defined(__APPLE__)
DrainMainThreadTasks();
#endif
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
timer.Pause();
@@ -821,10 +809,7 @@ static void WindowCreate(WindowContext& context) {
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
uint32_t window_flags = KYTY_SDL_WINDOW_FLAGS;
if (Config::FullscreenEnabled()) {
window_flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
}
uint32_t window_flags = WindowContext::InitialWindowFlags(Config::FullscreenEnabled());
#if defined(__APPLE__)
// macOS 26 window chrome (CoreUI asset decode, SwiftUI titlebar) has been observed
// throwing NSExceptions under Rosetta during the first CATransaction commit. A
@@ -836,13 +821,20 @@ static void WindowCreate(WindowContext& context) {
context.window = SDL_CreateWindow(KYTY_SDL_WINDOW_CAPTION, KYTY_SDL_WINDOWPOS_CENTERED,
KYTY_SDL_WINDOWPOS_CENTERED, width, height, window_flags);
context.window_hidden = true;
if (context.window == nullptr) {
EXIT("%s\n", SDL_GetError());
}
SDL_SetWindowResizable(context.window, SDL_FALSE);
context.UpdateIcon();
}
uint32_t WindowContext::InitialWindowFlags(bool fullscreen) noexcept {
auto flags = static_cast<uint32_t>(SDL_WINDOW_VULKAN);
if (fullscreen) {
flags |= static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
}
return flags;
}
Presenter& WindowInit(uint32_t width, uint32_t height) {
@@ -959,6 +951,7 @@ 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 const std::string processor_name = Common::GetSystemInfo().ProcessorName;
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
static uint64_t frame_num = 0;
static uint64_t fps_frames = 0;
@@ -983,19 +976,14 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format(
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
auto text = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
(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.
RunOnMainThread([this, fps = std::move(fps)] { SDL_SetWindowTitle(window, fps.c_str()); },
false);
#else
SDL_SetWindowTitle(window, fps.c_str());
#endif
RunOnMainThread([this, text = std::move(text)] { SDL_SetWindowTitle(window, text.c_str()); });
}
} // namespace Libs::Graphics
@@ -9,13 +9,10 @@
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#if defined(__APPLE__)
#include <functional>
#endif
namespace Libs::Graphics {
class Presenter;
@@ -39,6 +36,7 @@ struct WindowContext {
KYTY_CLASS_NO_COPY(WindowContext);
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
[[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept;
void CreateVulkan();
void RecreateSurface();
void RefreshSurfaceCapabilities();
@@ -49,36 +47,25 @@ struct WindowContext {
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
void ProcessEvent(double time_seconds);
void Run();
#if defined(__APPLE__)
// AppKit only allows window operations (show, icon, title, view/layer changes) on the
// main thread; the present thread marshals them through the SDL main loop with these.
// wait=true blocks until the task has run on the main thread.
void RunOnMainThread(std::function<void()> task, bool wait);
// SDL window operations must complete on the main thread.
void RunOnMainThread(std::function<void()> task);
void DrainMainThreadTasks();
#endif
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;
char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0};
char processor_name[64] = {0};
Common::Mutex mutex;
#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
uint64_t main_tasks_queued = 0; // guarded by main_task_mutex
uint64_t main_tasks_run = 0; // guarded by main_task_mutex
#endif
};
} // namespace Libs::Graphics
@@ -665,7 +665,6 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
runtime.read_memory = ReadZeroMemory;
}
runtime.userdata = options.read_memory_data;
runtime.flat_memory_base = options.flat_memory_base;
if (!IR::MaterializeResources(ir, runtime, resources, error)) {
return false;
}
@@ -6,7 +6,6 @@
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include <optional>
#include <span>
#include <vector>
@@ -20,7 +19,6 @@ struct CompileOptions {
uint32_t scratch_dwords = 0;
uint64_t shader_hash = 0;
uint64_t shader_base = 0;
std::optional<uint64_t> flat_memory_base;
uint32_t push_constant_offset = 0;
bool dump_ir = true;
bool early_dump = false;
@@ -70,8 +70,9 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
if (uses_gds) {
Expect(Kind::Gds);
}
if (!program.info.addresses.empty()) {
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
if (program.info.uses_dma) {
Expect(Kind::BdaPagetable);
Expect(Kind::FaultBuffer);
}
const bool uses_flattened_runtime =
!program.srt_reads.empty() ||
@@ -104,8 +105,7 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
program.bindings.push_constant_offset + program.bindings.push_constant_size >
IR::NativePushConstantSize ||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.memory_offset_count !=
program.info.buffers.size() + program.info.addresses.size() ||
program.bindings.memory_offset_count != program.info.buffers.size() ||
(!has_user_storage &&
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
(has_user_storage && program.bindings.push_constant_size != 0) ||
@@ -146,8 +146,8 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
if (planning_only_handle(inst)) {
break;
}
if (dense >= program.info.addresses.size()) {
return Fail(error, "typed address handle has an invalid dense resource");
if (inst.NumArgs() != 2 || !program.info.uses_dma) {
return Fail(error, "typed address handle has invalid DMA metadata");
}
break;
case IR::ValueOpcode::GetScratchResource:
@@ -188,7 +188,8 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
const auto MarkBallot = [&] { requirements.subgroup_ballot = true; };
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (IR::AddressOpcodeInfoOf(inst.GetOpcode()).access != IR::AddressAccess::None) {
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
if (address_access != IR::AddressAccess::None) {
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
if (memory_index >= program.memory_info.size()) {
return Fail(error, "address operation has invalid memory metadata");
@@ -198,6 +199,8 @@ bool AnalyzeProgramRequirements(IR::Program& program, std::string* error) {
return Fail(error, "scratch operation has no per-thread storage");
}
requirements.function_scratch = true;
} else if (address_access == IR::AddressAccess::Write) {
return Fail(error, "writable FLAT/GLOBAL addresses require GPU ownership tracking");
}
}
if (IR::BufferAccessOf(inst.GetOpcode()) != IR::BufferAccess::None) {
@@ -36,8 +36,10 @@ enum : uint32_t {
ExecutionModeSignedZeroInfNanPreserve = 4461,
ExecutionModeDerivativeGroupQuadsKHR = 5289,
AddressingModelLogical = 0,
AddressingModelPhysicalStorageBuffer64 = 5348,
MemoryModelGLSL450 = 1,
CapabilityShader = 1,
CapabilityInt64 = 11,
CapabilityImageGatherExtended = 25,
CapabilityClipDistance = 32,
CapabilityCullDistance = 33,
@@ -52,6 +54,7 @@ enum : uint32_t {
CapabilityShaderViewportIndexLayerEXT = 5254,
CapabilityFragmentBarycentricKHR = 5284,
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
CapabilityPhysicalStorageBufferAddresses = 5347,
StorageClassUniformConstant = 0,
StorageClassInput = 1,
StorageClassOutput = 3,
@@ -60,6 +63,7 @@ enum : uint32_t {
StorageClassPushConstant = 9,
StorageClassImage = 11,
StorageClassStorageBuffer = 12,
StorageClassPhysicalStorageBuffer = 5349,
FunctionControlNone = 0,
SelectionControlNone = 0,
LoopControlNone = 0,
@@ -148,7 +152,9 @@ enum : uint32_t {
OpConstantComposite = 44,
OpUndef = 1,
OpFunction = 54,
OpFunctionParameter = 55,
OpFunctionEnd = 56,
OpFunctionCall = 57,
OpVariable = 59,
OpImageTexelPointer = 60,
OpLoad = 61,
@@ -178,6 +184,8 @@ enum : uint32_t {
OpConvertFToS = 110,
OpConvertSToF = 111,
OpConvertUToF = 112,
OpUConvert = 113,
OpConvertUToPtr = 120,
OpBitcast = 124,
OpSNegate = 126,
OpFNegate = 127,
@@ -256,11 +264,16 @@ enum : uint32_t {
OpSwitch = 251,
OpKill = 252,
OpReturn = 253,
OpReturnValue = 254,
OpGroupNonUniformBallot = 339,
OpGroupNonUniformBallotFindLSB = 343,
OpGroupNonUniformShuffle = 345,
};
enum : uint32_t {
MemoryAccessAlignedMask = 0x2,
};
enum : uint32_t {
GlslRoundEven = 2,
GlslTrunc = 3,
@@ -345,9 +358,10 @@ struct EmitterState {
ShaderType stage = ShaderType::Unknown;
uint32_t wave_size = 64;
uint32_t storage_buffer_variable = 0;
std::array<uint32_t, IR::ShaderInfo::MaxBuffers + IR::ShaderInfo::MaxAddresses>
memory_byte_offsets {};
uint32_t address_memory_variable = 0;
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
uint32_t bda_pagetable_variable = 0;
uint32_t fault_buffer_variable = 0;
uint32_t bda_pointer_function = 0;
uint32_t gds_variable = 0;
uint32_t gds_length = 0;
uint32_t push_constant_variable = 0;
@@ -384,6 +398,7 @@ uint32_t TypeBool(EmitterState& state);
uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
uint32_t TypeU32(EmitterState& state);
uint32_t TypeU64(EmitterState& state);
uint32_t TypeDeviceAddress(EmitterState& state);
uint32_t TypeU32Pair(EmitterState& state);
uint32_t TypeI32(EmitterState& state);
uint32_t TypeI32Pair(EmitterState& state);
@@ -397,6 +412,8 @@ uint32_t TypePointer(EmitterState& state, uint32_t storage_class, uint32_t point
uint32_t TypeFunction(EmitterState& state);
uint32_t TypeStorageBufferPointer(EmitterState& state);
uint32_t TypeStorageBufferElementPointer(EmitterState& state);
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state);
uint32_t TypePhysicalU32Pointer(EmitterState& state);
uint32_t TypePushConstantElementPointer(EmitterState& state);
uint32_t TypeU32ArrayPointer(EmitterState& state, uint32_t storage_class, uint32_t dwords);
uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class);
@@ -849,6 +866,8 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* error);
void DefineGetBdaPointer(EmitterState& state);
// These templates accept local lambdas from several emitter translation units.
template <typename Fn>
void EmitIfCondition(EmitterState& state, uint32_t condition, Fn&& fn) {
@@ -1,5 +1,7 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -95,60 +97,146 @@ uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t ad
return result;
}
uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t low, uint32_t high) {
uint32_t ScratchByteAddress(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t low,
uint32_t high) {
auto& state = ctx.state;
auto immediate = static_cast<int32_t>(mem.offset);
if (mem.kind == IR::ResourceKind::ScalarAddress) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
}
const auto immediate_low = ConstantU32(state, static_cast<uint32_t>(immediate));
const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u);
if (mem.kind == IR::ResourceKind::Scratch) {
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
const auto valid = Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
}
const auto base = state.program.info.addresses[mem.resource].specialized_base;
if (mem.kind == IR::ResourceKind::Flat ||
(mem.kind == IR::ResourceKind::Global &&
state.program.info.addresses[mem.resource].unbased)) {
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
const auto base_low = ConstantU32(state, static_cast<uint32_t>(base));
const auto base_high = ConstantU32(state, static_cast<uint32_t>(base >> 32u));
const auto relative = Binary(state, OpISub, TypeU32(state), low, base_low);
const auto borrow = Binary(state, OpULessThan, TypeBool(state), low, base_low);
const auto relative_high = Binary(
state, OpISub, TypeU32(state), Binary(state, OpISub, TypeU32(state), high, base_high),
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0)));
const auto valid =
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
}
const auto initial = base + static_cast<uint64_t>(static_cast<int64_t>(immediate));
auto relative_high = ConstantU32(state, static_cast<uint32_t>(initial >> 32u));
const auto relative = AddU64Low(state, ConstantU32(state, static_cast<uint32_t>(initial)),
relative_high, low, ConstantU32(state, 0), relative_high);
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
const auto valid =
Binary(state, OpIEqual, TypeBool(state), relative_high, ConstantU32(state, 0));
return Select(state, TypeU32(state), valid, relative, ConstantU32(state, UINT32_MAX));
Binary(state, OpIEqual, TypeBool(state), high, ConstantU32(state, 0));
return Select(state, TypeU32(state), valid, low, ConstantU32(state, UINT32_MAX));
}
uint32_t AddAddressMemoryOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
uint32_t address) {
auto& state = ctx.state;
const auto binding =
ResourceForDescriptor(state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
const auto offset =
state.memory_byte_offsets[state.program.info.buffers.size() + binding.array_index];
const auto corrected = Binary(state, OpIAdd, TypeU32(state), address, offset);
const auto not_invalid =
Binary(state, OpINotEqual, TypeBool(state), address, ConstantU32(state, UINT32_MAX));
const auto no_overflow =
Binary(state, OpUGreaterThanEqual, TypeBool(state), corrected, address);
return Select(state, TypeU32(state), AndCondition(state, not_invalid, no_overflow), corrected,
ConstantU32(state, UINT32_MAX));
uint32_t ConstantDeviceAddress(EmitterState& state, uint64_t value) {
return state.builder.Constant(OpConstant, TypeDeviceAddress(state),
{static_cast<uint32_t>(value),
static_cast<uint32_t>(value >> 32u)});
}
uint32_t DeviceAddressFromWords(EmitterState& state, uint32_t low, uint32_t high) {
const auto low64 = Unary(state, OpUConvert, TypeDeviceAddress(state), low);
const auto high64 = Binary(state, OpShiftLeftLogical, TypeDeviceAddress(state),
Unary(state, OpUConvert, TypeDeviceAddress(state), high),
ConstantDeviceAddress(state, 32));
return Binary(state, OpBitwiseOr, TypeDeviceAddress(state), low64, high64);
}
uint32_t GuestAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
auto& state = ctx.state;
auto low = ctx.Arg(inst, 1);
if (mem.kind == IR::ResourceKind::ScalarAddress) {
low = Binary(state, OpBitwiseAnd, TypeU32(state), low, ConstantU32(state, ~3u));
}
uint32_t address = 0;
if (mem.address_is_full) {
address = DeviceAddressFromWords(state, low, ctx.Arg(inst, 2));
} else {
const auto* handle = inst.Arg(0).Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetAddressResource ||
handle->NumArgs() != 2) {
ctx.Fail(inst, "has no address base pair");
return ConstantDeviceAddress(state, 0);
}
const auto base = DeviceAddressFromWords(state, ctx.Arg(*handle, 0), ctx.Arg(*handle, 1));
address = Binary(state, OpIAdd, TypeDeviceAddress(state), base,
Unary(state, OpUConvert, TypeDeviceAddress(state), low));
}
auto immediate = static_cast<int32_t>(mem.offset);
if (mem.kind == IR::ResourceKind::ScalarAddress) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
}
return immediate == 0
? address
: Binary(state, OpIAdd, TypeDeviceAddress(state), address,
ConstantDeviceAddress(state,
static_cast<uint64_t>(static_cast<int64_t>(immediate))));
}
uint32_t FaultElementPointer(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.fault_buffer_variable, ConstantU32(state, 0), index});
return pointer;
}
void RecordBdaFault(EmitterState& state, uint32_t page) {
const auto word = Binary(state, OpShiftRightLogical, TypeU32(state), page,
ConstantU32(state, 5));
const auto bit = Binary(
state, OpShiftLeftLogical, TypeU32(state), ConstantU32(state, 1),
Binary(state, OpBitwiseAnd, TypeU32(state), page, ConstantU32(state, 31)));
const auto pointer = FaultElementPointer(state, word);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
state.builder.AddFunction(
{OpStore, pointer, Binary(state, OpBitwiseOr, TypeU32(state), value, bit)});
}
uint32_t GetBdaPointer(ValueEmitContext& ctx, uint32_t address) {
auto& state = ctx.state;
const auto result = state.builder.AllocateId();
state.builder.AddFunction(
{OpFunctionCall, TypeDeviceAddress(state), result, state.bda_pointer_function, address});
return result;
}
uint32_t LoadBdaDword(ValueEmitContext& ctx, uint32_t address) {
auto& state = ctx.state;
const auto bda = GetBdaPointer(ctx, address);
const auto present = Binary(state, OpINotEqual, TypeBool(state), bda,
ConstantDeviceAddress(state, 0));
return EmitValueOrZeroIfCondition(state, present, [&]() {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpConvertUToPtr, TypePhysicalU32Pointer(state), pointer, bda});
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, TypeU32(state), value, pointer, MemoryAccessAlignedMask, sizeof(uint32_t)});
return value;
});
}
uint32_t LoadBda(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t bits) {
auto& state = ctx.state;
const auto address = GuestAddress(ctx, inst, mem);
const auto active = ctx.Arg(inst, inst.NumArgs() - 1);
return EmitValueOrZeroIfCondition(state, active, [&]() {
const auto aligned = Binary(state, OpBitwiseAnd, TypeDeviceAddress(state), address,
ConstantDeviceAddress(state, ~uint64_t {3}));
const auto first = LoadBdaDword(ctx, aligned);
const auto byte = Binary(state, OpBitwiseAnd, TypeU32(state),
Unary(state, OpUConvert, TypeU32(state), address),
ConstantU32(state, 3));
const auto crosses = bits == 8u
? ConstantBool(state, false)
: Binary(state, bits == 16u ? OpUGreaterThan : OpINotEqual,
TypeBool(state), byte,
ConstantU32(state, bits == 16u ? 2u : 0u));
const auto second = EmitValueOrZeroIfCondition(state, crosses, [&]() {
return LoadBdaDword(
ctx, Binary(state, OpIAdd, TypeDeviceAddress(state), aligned,
ConstantDeviceAddress(state, sizeof(uint32_t))));
});
const auto shift = Binary(state, OpShiftLeftLogical, TypeU32(state), byte,
ConstantU32(state, 3));
const auto upper_shift = Binary(
state, OpShiftLeftLogical, TypeU32(state),
Binary(state, OpBitwiseAnd, TypeU32(state),
Binary(state, OpISub, TypeU32(state), ConstantU32(state, 4), byte),
ConstantU32(state, 3)),
ConstantU32(state, 3));
const auto merged = Binary(
state, OpBitwiseOr, TypeU32(state),
Binary(state, OpShiftRightLogical, TypeU32(state), first, shift),
Binary(state, OpShiftLeftLogical, TypeU32(state), second, upper_shift));
return bits == 32u
? merged
: Binary(state, OpBitwiseAnd, TypeU32(state), merged,
ConstantU32(state, bits == 8u ? 0xffu : 0xffffu));
});
}
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
@@ -163,11 +251,10 @@ uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memo
return Binary(ctx.state, OpIAdd, TypeU32(ctx.state), ctx.Arg(inst, 0),
ConstantU32(ctx.state, mem.offset));
}
const auto address = AddressByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
if (mem.kind == IR::ResourceKind::Scratch) {
return address;
if (mem.kind != IR::ResourceKind::Scratch) {
EXIT("physical address memory must use the BDA emitter\n");
}
return AddAddressMemoryOffset(ctx, mem, address);
return ScratchByteAddress(ctx, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
}
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
@@ -923,6 +1010,58 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
} // namespace
void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) {
return;
}
const auto type = TypeDeviceAddress(state);
const auto function_type = state.builder.Type(OpTypeFunction, {type, type});
state.bda_pointer_function = state.builder.AllocateId();
const auto address = state.builder.AllocateId();
const auto entry_label = state.builder.AllocateId();
state.builder.AddName(state.bda_pointer_function, "get_bda_pointer");
state.builder.AddFunction(
{OpFunction, type, state.bda_pointer_function, FunctionControlNone, function_type});
state.builder.AddFunction({OpFunctionParameter, type, address});
EmitLabel(state, entry_label);
const auto page64 = Binary(
state, OpShiftRightLogical, type, address,
ConstantDeviceAddress(state, BufferCache::CACHING_PAGEBITS));
const auto page = Unary(state, OpUConvert, TypeU32(state), page64);
const auto entry_pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypeDeviceAddressStoragePointer(state), entry_pointer,
state.bda_pagetable_variable, ConstantU32(state, 0), page});
const auto base = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, type, base, entry_pointer});
const auto missing =
Binary(state, OpIEqual, TypeBool(state), base, ConstantDeviceAddress(state, 0));
const auto fault_label = state.builder.AllocateId();
const auto available_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction(
{OpBranchConditional, missing, fault_label, available_label});
EmitLabel(state, fault_label);
RecordBdaFault(state, page);
state.builder.AddFunction({OpBranch, merge_label});
EmitLabel(state, available_label);
const auto offset = Binary(
state, OpBitwiseAnd, type, address,
ConstantDeviceAddress(state, BufferCache::CACHING_PAGESIZE - 1));
const auto available = Binary(state, OpIAdd, type, base, offset);
state.builder.AddFunction({OpBranch, merge_label});
EmitLabel(state, merge_label);
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpPhi, type, result, ConstantDeviceAddress(state, 0), fault_label,
available, available_label});
state.builder.AddFunction({OpReturnValue, result});
state.builder.AddFunction({OpFunctionEnd});
}
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto op = inst.GetOpcode();
@@ -981,6 +1120,10 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
}
const auto address_info = IR::AddressOpcodeInfoOf(op);
const bool load_address = address_info.access == IR::AddressAccess::Read;
if (load_address && ctx.Memory(inst).kind != IR::ResourceKind::Scratch) {
ctx.Define(inst, LoadBda(ctx, inst, ctx.Memory(inst), address_info.data_bits));
return true;
}
const bool load_buffer = op == IR::ValueOpcode::LoadBufferU8 ||
op == IR::ValueOpcode::LoadBufferU16 ||
op == IR::ValueOpcode::LoadBufferU32;
@@ -108,20 +108,8 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
return access;
case IR::ResourceKind::ScalarAddress:
case IR::ResourceKind::Flat:
case IR::ResourceKind::Global: {
if (state.address_memory_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
mem.resource,
"address memory binding was not emitted");
}
const auto binding = ResourceForDescriptor(
state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
access.object_pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, TypeStorageBufferPointer(state),
access.object_pointer, state.address_memory_variable,
ConstantU32(state, binding.array_index)});
break;
}
case IR::ResourceKind::Global:
EXIT("physical address memory must use the BDA emitter\n");
case IR::ResourceKind::ScalarBuffer:
case IR::ResourceKind::Buffer: {
if (state.storage_buffer_variable == 0) {
@@ -28,6 +28,10 @@ uint32_t TypeU64(EmitterState& state) {
return TypeU32Vector(state, 2);
}
uint32_t TypeDeviceAddress(EmitterState& state) {
return state.builder.Type(OpTypeInt, {64, 0});
}
uint32_t TypeU32Pair(EmitterState& state) {
const auto element = TypeU32(state);
return state.builder.Type(OpTypeStruct, {element, element});
@@ -90,6 +94,14 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
return TypePointer(state, StorageClassStorageBuffer, TypeU32(state));
}
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state) {
return TypePointer(state, StorageClassStorageBuffer, TypeDeviceAddress(state));
}
uint32_t TypePhysicalU32Pointer(EmitterState& state) {
return TypePointer(state, StorageClassPhysicalStorageBuffer, TypeU32(state));
}
uint32_t TypePushConstantElementPointer(EmitterState& state) {
return TypePointer(state, StorageClassPushConstant, TypeU32(state));
}
@@ -130,6 +142,15 @@ uint32_t SampleMaskArrayType(EmitterState& state) {
return state.builder.Type(OpTypeArray, {TypeI32(state), ConstantU32(state, 1)});
}
uint32_t BdaPagetableType(EmitterState& state) {
const auto array = state.builder.DecoratedType(
OpTypeRuntimeArray, {TypeDeviceAddress(state)},
{{OpDecorate, {DecorationArrayStride, sizeof(uint64_t)}}});
return state.builder.DecoratedType(
OpTypeStruct, {array},
{{OpMemberDecorate, {0, DecorationOffset, 0}}, {OpDecorate, {DecorationBlock}}});
}
uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
return state.builder.Type(OpTypeArray, {TypeF32(state), ConstantU32(state, count)});
}
@@ -143,13 +164,14 @@ void DefineDescriptorVariables(EmitterState& state) {
state.storage_buffer_variable =
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::AddressMemory) != nullptr) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::AddressMemory));
const auto array_type = state.builder.Type(OpTypeArray, {StorageBufferType(state), count});
const auto pointer_type = TypePointer(state, StorageClassStorageBuffer, array_type);
state.address_memory_variable =
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
if (DescriptorBinding(state, IR::DescriptorBindingKind::BdaPagetable) != nullptr) {
state.bda_pagetable_variable = state.builder.DefineGlobalVariable(
TypePointer(state, StorageClassStorageBuffer, BdaPagetableType(state)),
StorageClassStorageBuffer);
}
if (DescriptorBinding(state, IR::DescriptorBindingKind::FaultBuffer) != nullptr) {
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
}
if (state.program.bindings.push_constant_size != 0) {
const auto pointer_type =
@@ -564,9 +586,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
if (state.storage_buffer_variable != 0) {
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
}
if (state.address_memory_variable != 0) {
Decorate(state.address_memory_variable, "address_memory",
IR::DescriptorBindingKind::AddressMemory);
if (state.bda_pagetable_variable != 0) {
Decorate(state.bda_pagetable_variable, "bda_pagetable",
IR::DescriptorBindingKind::BdaPagetable);
}
if (state.fault_buffer_variable != 0) {
Decorate(state.fault_buffer_variable, "fault_buffer",
IR::DescriptorBindingKind::FaultBuffer);
}
constexpr const char* SampledNames[] = {"sampled_1d",
"sampled_1d_array",
@@ -644,6 +670,11 @@ void DefineModule(EmitterState& state) {
state.builder.RequireCapability(CapabilityShader);
state.builder.RequireCapability(CapabilitySignedZeroInfNanPreserve);
if (state.program.info.uses_dma) {
state.builder.RequireCapability(CapabilityInt64);
state.builder.RequireCapability(CapabilityPhysicalStorageBufferAddresses);
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
}
if (state.clip_distance_variable != 0) {
state.builder.RequireCapability(CapabilityClipDistance);
}
@@ -682,7 +713,10 @@ void DefineModule(EmitterState& state) {
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
}
state.builder.RequireExtension("SPV_KHR_float_controls");
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
state.builder.AddMemoryModel(
{state.program.info.uses_dma ? AddressingModelPhysicalStorageBuffer64
: AddressingModelLogical,
MemoryModelGLSL450});
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
state.interface_variables);
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
@@ -566,6 +566,7 @@ bool EmitProgram(EmitterState& state, const IR::Program& program, std::string* e
SetError(error, ctx.error.c_str());
return false;
}
DefineGetBdaPointer(state);
for (const auto* block: program.blocks) {
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
@@ -366,6 +366,7 @@ enum class Opcode {
V_CMPX_LE_F16,
V_CMPX_GT_F16,
V_CMPX_GE_F16,
V_CMPX_NGT_F16,
V_CMPX_NEQ_F16,
V_CMPX_NLT_F16,
V_CMPX_LT_I32,
@@ -378,6 +379,7 @@ enum class Opcode {
V_CMP_EQ_U16,
V_CMP_LE_U16,
V_CMP_GT_U16,
V_CMPX_GT_U16,
V_CMP_NE_U16,
V_CMP_GE_U16,
V_CMP_F_U32,
@@ -224,7 +224,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0x96u, Opcode::V_CMPX_GE_I32}, {0xa9u, Opcode::V_CMP_LT_U16},
{0xaau, Opcode::V_CMP_EQ_U16}, {0xabu, Opcode::V_CMP_LE_U16},
{0xacu, Opcode::V_CMP_GT_U16}, {0xadu, Opcode::V_CMP_NE_U16},
{0xaeu, Opcode::V_CMP_GE_U16}, {0xc0u, Opcode::V_CMP_F_U32},
{0xaeu, Opcode::V_CMP_GE_U16}, {0xbcu, Opcode::V_CMPX_GT_U16},
{0xc0u, Opcode::V_CMP_F_U32},
{0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32},
{0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32},
{0xc5u, Opcode::V_CMP_NE_U32}, {0xc6u, Opcode::V_CMP_GE_U32},
@@ -241,7 +242,8 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xedu, Opcode::V_CMP_NEQ_F16}, {0xd9u, Opcode::V_CMPX_LT_F16},
{0xdau, Opcode::V_CMPX_EQ_F16}, {0xdbu, Opcode::V_CMPX_LE_F16},
{0xdcu, Opcode::V_CMPX_GT_F16}, {0xdeu, Opcode::V_CMPX_GE_F16},
{0xfdu, Opcode::V_CMPX_NEQ_F16}, {0xfeu, Opcode::V_CMPX_NLT_F16},
{0xfbu, Opcode::V_CMPX_NGT_F16}, {0xfdu, Opcode::V_CMPX_NEQ_F16},
{0xfeu, Opcode::V_CMPX_NLT_F16},
};
constexpr auto VOPC_OPS = Detail::MakeOpcodeTable<0x100>(VOPC_OPCODE_LIST);
@@ -873,6 +875,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
case Opcode::V_CMPX_LE_F16:
case Opcode::V_CMPX_GT_F16:
case Opcode::V_CMPX_GE_F16:
case Opcode::V_CMPX_NGT_F16:
case Opcode::V_CMPX_NEQ_F16:
case Opcode::V_CMPX_NLT_F16:
case Opcode::V_CMP_CLASS_F32: return true;
@@ -1506,11 +1509,13 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_GE_U32:
case Opcode::V_CMPX_NE_I64:
case Opcode::V_CMPX_NE_U64:
case Opcode::V_CMPX_GT_U16:
case Opcode::V_CMPX_LT_F16:
case Opcode::V_CMPX_EQ_F16:
case Opcode::V_CMPX_LE_F16:
case Opcode::V_CMPX_GT_F16:
case Opcode::V_CMPX_GE_F16:
case Opcode::V_CMPX_NGT_F16:
case Opcode::V_CMPX_NEQ_F16:
case Opcode::V_CMPX_NLT_F16: return true;
default: return false;
@@ -136,12 +136,10 @@ IR::MemoryInfo MemoryInfoFromDecoded(const Decoder::Instruction& decoded) {
memory.offen = decoded.offen;
memory.resource = ResourceIndexFromOperand(decoded.src1);
memory.sampler = ResourceIndexFromOperand(decoded.src2);
if (memory.kind == ResourceKind::ScalarAddress) {
memory.resource = RawScalarLoadBase(decoded.src0);
} else if (memory.kind == ResourceKind::ScalarBuffer) {
if (memory.kind == ResourceKind::ScalarBuffer) {
memory.resource = ResourceIndexFromOperand(decoded.src0);
} else if (memory.kind == ResourceKind::Lds || memory.kind == ResourceKind::Gds ||
memory.kind == ResourceKind::Scratch) {
} else if (IsAddressResourceKind(memory.kind) || memory.kind == ResourceKind::Lds ||
memory.kind == ResourceKind::Gds) {
memory.resource = 0;
memory.sampler = 0;
}
@@ -421,7 +419,7 @@ IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
const auto memory = MemoryInfoFromDecoded(inst);
const auto resource =
raw ? GetScalarAddressResource(memory.resource) : GetBufferResource(memory);
raw ? GetScalarAddressResource(RawScalarLoadBase(inst.src0)) : GetBufferResource(memory);
const auto offset = ReadU32(inst.src1);
std::array<IR::Value, 16> loaded {};
for (uint32_t component = 0; component < memory.data_dwords; component++) {
@@ -124,6 +124,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
case O::V_CMP_GT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
return true;
case O::V_CMPX_GT_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
return true;
case O::V_CMP_GE_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
return true;
@@ -253,6 +256,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error
case O::V_CMPX_LE_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
return true;
case O::V_CMPX_NGT_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
return true;
case O::V_CMP_NEQ_F16:
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
return true;
+3 -17
View File
@@ -176,20 +176,6 @@ struct SampledResourcePair {
bool operator==(const SampledResourcePair& other) const = default;
};
struct AddressResource {
uint32_t source = UINT32_MAX;
uint32_t first_use_pc = 0;
ResourceKind kind = ResourceKind::Flat;
int32_t min_offset = 0;
uint64_t specialized_base = 0;
bool unbased = true;
bool read = false;
bool written = false;
bool atomic = false;
bool operator==(const AddressResource& other) const = default;
};
enum class StageInputKind {
VertexIndex,
InstanceIndex,
@@ -321,7 +307,8 @@ enum class DescriptorBindingKind {
StorageAtomic3D,
Samplers,
Gds,
AddressMemory,
BdaPagetable,
FaultBuffer,
FlattenedSrt,
UserData,
Count,
@@ -431,13 +418,11 @@ struct BindingLayout {
struct ShaderInfo {
static constexpr uint32_t MaxBuffers = 32;
static constexpr uint32_t MaxAddresses = 32;
static constexpr uint32_t MaxImages = 32;
static constexpr uint32_t MaxSamplers = 32;
static constexpr uint32_t MaxSampledPairs = 64;
std::vector<BufferResource> buffers;
std::vector<AddressResource> addresses;
std::vector<ImageResource> images;
std::vector<SamplerResource> samplers;
std::vector<SampledResourcePair> sampled_pairs;
@@ -446,6 +431,7 @@ struct ShaderInfo {
std::array<uint8_t, 32> vertex_fetch_components {};
int32_t vertex_offset_sgpr = -1;
bool has_bitwise_xor = false;
bool uses_dma = false;
bool operator==(const ShaderInfo& other) const = default;
};
@@ -93,8 +93,7 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
return false;
}
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
next.memory_offset_count =
static_cast<uint32_t>(program.info.buffers.size() + program.info.addresses.size());
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
if (!program.info.buffers.empty()) {
std::vector<uint32_t> resources(program.info.buffers.size());
@@ -157,12 +156,9 @@ bool AllocateBindings(Program& program, uint32_t push_constant_offset, std::stri
if (uses_gds) {
AddBinding(next, DescriptorBindingKind::Gds);
}
if (!program.info.addresses.empty()) {
std::vector<uint32_t> resources(program.info.addresses.size());
for (uint32_t i = 0; i < resources.size(); i++) {
resources[i] = i;
}
AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources));
if (program.info.uses_dma) {
AddBinding(next, DescriptorBindingKind::BdaPagetable);
AddBinding(next, DescriptorBindingKind::FaultBuffer);
}
const bool uses_flattened_runtime =
!program.srt_reads.empty() ||
@@ -267,13 +267,6 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
return true;
}
uint64_t AddressSpecialization(const AddressResource& resource,
const ResourceSnapshot::Address& snapshot) {
return resource.kind == ResourceKind::Flat || resource.unbased
? snapshot.binding_base
: snapshot.guest_base - snapshot.binding_base;
}
size_t FlattenedRuntimeDwords(const Program& program) {
size_t size = program.srt_reads.size();
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
@@ -298,8 +291,7 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
}
if (snapshot.buffers.size() != program.info.buffers.size() ||
snapshot.images.size() != program.info.images.size() ||
snapshot.samplers.size() != program.info.samplers.size() ||
snapshot.addresses.size() != program.info.addresses.size()) {
snapshot.samplers.size() != program.info.samplers.size()) {
if (error != nullptr) {
*error = "resource snapshot does not match dense shader topology";
}
@@ -343,14 +335,6 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
}
return true;
};
for (uint32_t i = 0; i < snapshot.addresses.size(); i++) {
if (snapshot.addresses[i].binding_base > snapshot.addresses[i].guest_base) {
if (error != nullptr) {
*error = fmt::format("address resource {} binds above its guest base", i);
}
return false;
}
}
std::unordered_set<uint32_t> indirect_resources;
for (const auto& table: snapshot.indirect_images) {
const auto* source = table.resource < program.info.images.size()
@@ -526,15 +510,6 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
}
}
}
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
if (program.info.addresses[i].specialized_base !=
AddressSpecialization(program.info.addresses[i], snapshot.addresses[i])) {
if (error != nullptr) {
*error = fmt::format("address resource {} no longer matches specialization", i);
}
return false;
}
}
return true;
}
@@ -550,7 +525,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
std::vector<DescriptorSourceRequest> requests;
std::vector<uint8_t> clean_flat_slots(program.srt_reads.size());
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
program.info.samplers.size() + program.info.addresses.size());
program.info.samplers.size());
for (const auto& buffer: program.info.buffers) {
requests.push_back({buffer.source, buffer.first_use_pc});
}
@@ -576,12 +551,6 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
for (const auto& sampler: program.info.samplers) {
requests.push_back({sampler.source, sampler.first_use_pc});
}
for (const auto& address: program.info.addresses) {
if (!address.unbased) {
requests.push_back({address.source, address.first_use_pc});
}
}
std::vector<DescriptorValue> values;
std::vector<uint32_t> flattened_srt;
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, clean_flat_slots,
@@ -686,37 +655,6 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
return false;
}
next.samplers.assign(cursor, cursor + program.info.samplers.size());
cursor += program.info.samplers.size();
for (const auto& address: program.info.addresses) {
if (!address.unbased) {
const auto value = *cursor++;
auto base = (static_cast<uint64_t>(value.dwords[0]) |
static_cast<uint64_t>(value.dwords[1]) << 32u) &
AddressMask;
if (address.kind == ResourceKind::ScalarAddress) {
base &= ~uint64_t {3};
}
const auto before = static_cast<uint64_t>(-static_cast<int64_t>(address.min_offset));
uint64_t binding_base = 0;
if (address.kind == ResourceKind::Flat) {
binding_base = base & ~(FlatAddressWindowSize - 1u);
} else if (base >= before) {
binding_base = base - before;
}
next.addresses.push_back({base, binding_base});
} else {
if (!runtime.flat_memory_base.has_value()) {
if (error != nullptr) {
*error = fmt::format("unbased {} address at pc 0x{:08x} requires runtime "
"guest-address translation",
address.kind == ResourceKind::Flat ? "FLAT" : "global",
address.first_use_pc);
}
return false;
}
next.addresses.push_back({*runtime.flat_memory_base, *runtime.flat_memory_base});
}
}
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
if (!ValidateResourceSnapshot(program, next, error)) {
return false;
@@ -791,10 +729,6 @@ bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::stri
next.buffers[i].descriptor_format = descriptor.Format();
next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW();
}
for (uint32_t i = 0; i < next.addresses.size(); i++) {
next.addresses[i].specialized_base =
AddressSpecialization(next.addresses[i], next_snapshot.addresses[i]);
}
for (uint32_t i = 0; i < next.images.size(); i++) {
const auto& descriptor = next_snapshot.images[i];
auto& image = next.images[i];
@@ -5,15 +5,7 @@
namespace Libs::Graphics::ShaderRecompiler::IR {
constexpr uint64_t FlatAddressWindowSize = 0x10000;
struct ResourceSnapshot {
struct Address {
uint64_t guest_base = 0;
uint64_t binding_base = 0;
bool operator==(const Address& other) const = default;
};
struct IndirectImage {
uint32_t resource = 0;
uint32_t capacity = 0;
@@ -25,7 +17,6 @@ struct ResourceSnapshot {
std::vector<DescriptorValue> buffers;
std::vector<DescriptorValue> images;
std::vector<DescriptorValue> samplers;
std::vector<Address> addresses;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
std::vector<IndirectImage> indirect_images;
@@ -7,7 +7,6 @@
#include <algorithm>
#include <fmt/format.h>
#include <span>
#include <unordered_set>
#include <utility>
namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -96,10 +95,10 @@ class Tracker {
public:
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
m_info.buffers.clear();
m_info.addresses.clear();
m_info.images.clear();
m_info.samplers.clear();
m_info.sampled_pairs.clear();
m_info.uses_dma = false;
}
bool Run(std::string* error) {
@@ -180,90 +179,6 @@ private:
m_program.shader_hash, StageName(m_program.stage), pc, reason));
}
struct AddressPart {
Value value;
bool rooted = false;
};
AddressPart FindAddressPart(Value value, Value active,
std::unordered_set<const Inst*>& visiting) const {
value = value.Resolve();
if (value.IsImmediate()) {
return {value, false};
}
const auto* inst = value.TryInstruction();
if (inst == nullptr || !visiting.insert(inst).second) {
return {};
}
const auto finish = [&](AddressPart part) {
visiting.erase(inst);
return part;
};
std::string reason;
if (ValidateRuntimeValue(m_program, value, reason)) {
return finish({value, true});
}
const auto merge = [&](AddressPart left, AddressPart right, bool require_both) {
if (left.value.IsEmpty() || right.value.IsEmpty()) {
return require_both ? AddressPart {} : (left.value.IsEmpty() ? right : left);
}
return EquivalentValue(m_program, left.value, right.value)
? AddressPart {left.value, left.rooted || right.rooted}
: AddressPart {};
};
switch (inst->GetOpcode()) {
case ValueOpcode::SelectU32:
if (EquivalentValue(m_program, inst->Arg(0), active)) {
return finish(FindAddressPart(inst->Arg(1), active, visiting));
}
return finish(merge(FindAddressPart(inst->Arg(1), active, visiting),
FindAddressPart(inst->Arg(2), active, visiting), true));
case ValueOpcode::Phi: {
const auto invariant = ResolveInvariantPhi(m_program, value);
return finish(invariant.IsEmpty() ? AddressPart {}
: FindAddressPart(invariant, active, visiting));
}
case ValueOpcode::IAdd32:
case ValueOpcode::ISub32: {
const auto left = FindAddressPart(inst->Arg(0), active, visiting);
const auto right = FindAddressPart(inst->Arg(1), active, visiting);
if (left.rooted == right.rooted ||
(inst->GetOpcode() == ValueOpcode::ISub32 && right.rooted)) {
return finish({});
}
return finish(left.rooted ? left : right);
}
case ValueOpcode::CompositeExtractU32x2: {
const auto* source = inst->Arg(0).ResolveInstruction();
if (source == nullptr || source->GetOpcode() != ValueOpcode::IAddCarry32) {
return finish({});
}
const auto left = FindAddressPart(source->Arg(0), active, visiting);
const auto right = FindAddressPart(source->Arg(1), active, visiting);
if (left.rooted == right.rooted) {
return finish({});
}
return finish(left.rooted ? left : right);
}
default: return finish({});
}
}
bool MakeFlatAddressSource(const Inst& handle, Value active,
DescriptorSource& descriptor) const {
std::unordered_set<const Inst*> visiting;
auto low = FindAddressPart(handle.Arg(0), active, visiting);
visiting.clear();
auto high = FindAddressPart(handle.Arg(1), active, visiting);
if ((!low.rooted && !high.rooted) || low.value.IsEmpty() || high.value.IsEmpty()) {
return false;
}
descriptor.dword_count = 2;
descriptor.dwords[0] = low.value;
descriptor.dwords[1] = high.value;
return true;
}
bool MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
DescriptorSource& descriptor, uint32_t pc, std::string* error) const {
if (handle.NumArgs() != width) {
@@ -577,42 +492,14 @@ private:
return true;
}
bool GetAddressHandle(const Inst& memory_inst, Value value, uint32_t pc, Inst*& handle,
uint32_t& source, bool& unbased, std::string* error) {
handle = value.Resolve().TryInstruction();
bool ValidateAddressHandle(Value value, uint32_t pc, std::string* error) const {
const auto* handle = value.Resolve().TryInstruction();
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) {
return Fail(pc, error, "address operation requires GetAddressResource");
}
if (handle->NumArgs() != 2) {
return Fail(pc, error, "GetAddressResource must have two address dwords");
}
DescriptorSource descriptor;
const auto& memory = m_program.memory_info[memory_inst.Flags<MemoryFlags>().index];
if (memory.address_is_full) {
const auto active = memory_inst.Arg(memory_inst.NumArgs() - 1u);
if (memory.kind != ResourceKind::Flat ||
!MakeFlatAddressSource(*handle, active, descriptor)) {
unbased = true;
source = UINT32_MAX;
return true;
}
} else if (!MakeSource(*handle, 2, false, false, descriptor, pc, error)) {
return false;
}
std::string reason;
uint32_t bad_dword = 0;
if (!ValidateSource(descriptor, reason, bad_dword)) {
if (memory.address_is_full) {
unbased = true;
source = UINT32_MAX;
return true;
}
return Fail(
pc, error,
fmt::format("scalar memory base dword {} is unresolved: {}", bad_dword, reason));
}
unbased = false;
source = InternSource(descriptor);
return true;
}
@@ -706,39 +593,6 @@ private:
return static_cast<uint32_t>(m_info.samplers.size() - 1);
}
uint32_t AddAddress(uint32_t source, bool unbased, const MemoryInfo& memory,
AddressAccess access, uint32_t pc) {
auto immediate = static_cast<int32_t>(memory.offset);
if (memory.kind == ResourceKind::ScalarAddress) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
}
const auto min_offset = unbased ? 0 : std::min(immediate, 0);
for (uint32_t i = 0; i < m_info.addresses.size(); i++) {
auto& address = m_info.addresses[i];
if (address.source == source && address.unbased == unbased &&
address.kind == memory.kind) {
address.first_use_pc = std::min(address.first_use_pc, pc);
address.min_offset = std::min(address.min_offset, min_offset);
address.read = address.read || access == AddressAccess::Read;
address.written = address.written || access == AddressAccess::Write;
return i;
}
}
if (m_info.addresses.size() >= ShaderInfo::MaxAddresses) {
return UINT32_MAX;
}
AddressResource address;
address.source = source;
address.first_use_pc = pc;
address.kind = memory.kind;
address.min_offset = min_offset;
address.unbased = unbased;
address.read = access == AddressAccess::Read;
address.written = access == AddressAccess::Write;
m_info.addresses.push_back(address);
return static_cast<uint32_t>(m_info.addresses.size() - 1);
}
bool AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc, std::string* error) {
for (auto& pair: m_info.sampled_pairs) {
if (pair.image == image && pair.sampler == sampler) {
@@ -839,16 +693,11 @@ private:
}
return true;
}
bool unbased = false;
if (!GetAddressHandle(inst, inst.Arg(0), flags.pc, handle, source, unbased, error)) {
if (!ValidateAddressHandle(inst.Arg(0), flags.pc, error)) {
return false;
}
resource = AddAddress(source, unbased, memory, address_info.access, flags.pc);
if (resource == UINT32_MAX) {
return Fail(flags.pc, error, "address resource limit exceeded");
}
return AddHandlePatch(handle, resource, flags.pc, error) &&
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
m_info.uses_dma = true;
return true;
}
if (!ImageResourceKindMatches(memory.kind, image_info.resource_class)) {
@@ -3,7 +3,6 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <optional>
#include <span>
namespace Libs::Graphics::ShaderRecompiler::IR {
@@ -17,7 +16,6 @@ struct SrtRuntime {
uint64_t shader_base = 0;
SrtMemoryReader read_memory = nullptr;
void* userdata = nullptr;
std::optional<uint64_t> flat_memory_base;
SrtMemoryReader read_specialization_memory = nullptr;
};
File diff suppressed because it is too large. Load diff
-33
View File
@@ -60,17 +60,6 @@ bool ShaderMaterializeStageRuntime(
std::string* error, ShaderSpecializationMemoryReader read_specialization_memory = nullptr,
void* read_memory_data = nullptr);
struct ShaderId {
uint32_t hash0 = 0;
uint32_t crc32 = 0;
std::vector<uint32_t> ids;
bool operator==(const ShaderId& other) const {
return hash0 == other.hash0 && crc32 == other.crc32 && ids == other.ids;
}
bool operator!=(const ShaderId& other) const { return !(*this == other); }
};
constexpr uint32_t DstSel(uint32_t x, uint32_t y = 0, uint32_t z = 0, uint32_t w = 0) {
return x | (y << 3u) | (z << 6u) | (w << 9u);
}
@@ -254,28 +243,6 @@ void ShaderMapUserData(uint64_t addr, const ShaderMappedData& data);
void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info);
void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info);
void ShaderDbgDumpInputInfo(const ShaderComputeInputInfo& info);
ShaderId ShaderGetIdVS(const HW::VertexShaderInfo& regs, const ShaderVertexInputInfo& input_info,
bool include_bind_specialization);
ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputInfo& input_info,
bool include_bind_specialization);
ShaderId ShaderGetIdCS(const HW::ComputeShaderInfo& regs, const ShaderComputeInputInfo& input_info,
bool include_bind_specialization);
// Returned SPIR-V spans are read-only views backed by the shader program cache.
bool ShaderCompileInfoVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info, std::span<const uint32_t>& spirv);
bool ShaderCompileInfoPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vs_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info, std::span<const uint32_t>& spirv);
bool ShaderCompileInfoCS(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
bool needs_lds_barriers, ShaderComputeInputInfo& input_info,
std::span<const uint32_t>& spirv);
bool ShaderCompileSpirvVS(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info, std::vector<uint32_t>& spirv);
bool ShaderCompileSpirvPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderPixelInputInfo& input_info, std::vector<uint32_t>& spirv);
bool ShaderCompileSpirvCS(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info, std::vector<uint32_t>& spirv);
bool ShaderAddressValid(uint64_t addr);
} // namespace Libs::Graphics
+53
View File
@@ -0,0 +1,53 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shader.h"
#include <memory>
#include <span>
#include <vector>
namespace Libs::Graphics {
struct ShaderParams {
std::span<const uint32_t> code;
std::span<const uint32_t> user_data;
uint64_t hash = 0;
[[nodiscard]] uint64_t Base() const {
return reinterpret_cast<uint64_t>(code.data());
}
};
void BuildStageStaticKey(const ShaderVertexInputInfo& input_info, std::vector<uint32_t>& key);
void BuildStageStaticKey(const ShaderPixelInputInfo& input_info, std::vector<uint32_t>& key);
void BuildStageStaticKey(const ShaderComputeInputInfo& input_info, std::vector<uint32_t>& key);
ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderVertexInputInfo& input_info);
ShaderParams PrepareProgram(
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
const ShaderVertexInputInfo& vertex_info,
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
ShaderPixelInputInfo& input_info);
ShaderParams PrepareProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
ShaderComputeInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderVertexInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderPixelInputInfo& input_info);
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
const ShaderParams& params, ShaderComputeInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderVertexInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderPixelInputInfo& input_info);
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
ShaderComputeInputInfo& input_info);
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_ */
+50 -19
View File
@@ -102,27 +102,58 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_user_name">
<property name="text">
<string>User name:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_user_name">
<property name="toolTip">
<string>Local user name exposed to games</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Screen resolution:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_screen_resolution">
<property name="toolTip">
<string>Window resolution</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_present_mode">
<property name="text">
<string>Present mode:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_present_mode">
<property name="toolTip">
<string>Vulkan swapchain presentation mode</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_vblank_frequency">
<property name="text">
<string>Vblank frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="4" column="1">
<widget class="QSpinBox" name="spinBox_vblank_frequency">
<property name="toolTip">
<string>Virtual display refresh rate used for frame pacing</string>
@@ -138,56 +169,56 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_console_language">
<property name="text">
<string>Console language:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="comboBox_console_language">
<property name="toolTip">
<string>Language</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Shader optimization type:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QComboBox" name="comboBox_shader_optimization_type">
<property name="toolTip">
<string>Optimize shaders for code size or performance</string>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Shader log direction:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="comboBox_shader_log_direction">
<property name="toolTip">
<string>Dump shaders to file or console window. If enabled may decrease emulator performance</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Shader log folder:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="8" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_shader_log_folder">
<property name="toolTip">
<string>Specify directory to dump shaders</string>
@@ -197,14 +228,14 @@
</property>
</widget>
</item>
<item row="7" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Command buffer dump folder:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="9" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_cmd_dump_folder">
<property name="toolTip">
<string>Specify directory to dump command buffers</string>
@@ -214,28 +245,28 @@
</property>
</widget>
</item>
<item row="8" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Printf direction:</string>
</property>
</widget>
</item>
<item row="8" column="1">
<item row="10" column="1">
<widget class="QComboBox" name="comboBox_printf_direction">
<property name="toolTip">
<string>Print logs to file or console window. If enabled may decrease emulator performance</string>
</property>
</widget>
</item>
<item row="9" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_26">
<property name="text">
<string>Printf output file:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="11" column="1">
<widget class="MandatoryLineEdit" name="lineEdit_printf_file">
<property name="toolTip">
<string>Specify file to dump logs</string>
@@ -245,14 +276,14 @@
</property>
</widget>
</item>
<item row="10" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_27">
<property name="text">
<string>Profiler direction:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="12" column="1">
<widget class="QComboBox" name="comboBox_profiler_direction">
<property name="toolTip">
<string>Enable or disable profiler. If enabled may decrease emulator performance</string>
+16
View File
@@ -54,12 +54,17 @@ public:
enum class Resolution {
R1280X720,
R1920X1080,
R2560X1440,
R3840X2160,
};
Q_ENUM(Resolution)
enum class ShaderOptimizationType { None, Size, Performance };
Q_ENUM(ShaderOptimizationType)
enum class PresentMode { Fifo, Mailbox, Immediate };
Q_ENUM(PresentMode)
enum class ShaderLogDirection { Silent, Console, File };
Q_ENUM(ShaderLogDirection)
@@ -85,6 +90,8 @@ public:
QString game_comment;
Resolution screen_resolution = Resolution::R1280X720;
QString user_name = "Kyty";
PresentMode present_mode = PresentMode::Fifo;
bool fullscreen_enabled = false;
int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE;
@@ -108,6 +115,8 @@ public:
void CopyEmulatorSettingsFrom(const Configuration& other) {
screen_resolution = other.screen_resolution;
user_name = other.user_name;
present_mode = other.present_mode;
fullscreen_enabled = other.fullscreen_enabled;
vblank_frequency = other.vblank_frequency;
console_language = other.console_language;
@@ -148,6 +157,8 @@ public:
KYTY_CFG_SET(game_path);
KYTY_CFG_SET(custom_settings);
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(user_name);
KYTY_CFG_SET(present_mode);
KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language);
@@ -175,6 +186,11 @@ public:
KYTY_CFG_GET(game_path);
KYTY_CFG_GET(custom_settings);
KYTY_CFG_GET(screen_resolution);
user_name = s->value("user_name", user_name).toString();
KYTY_CFG_GET(present_mode);
if (EnumToText(present_mode).isEmpty()) {
present_mode = PresentMode::Fifo;
}
KYTY_CFG_GET(fullscreen_enabled);
vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt();
console_language = s->value("console_language", console_language).toInt();
@@ -1,5 +1,6 @@
#include "configurationEditDialog.h"
#include "common/emulatorConfig.h"
#include "configuration.h"
#include "mandatoryLineEdit.h"
@@ -12,6 +13,7 @@
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLayout>
#include <QLineEdit>
#include <QListView>
#include <QListWidget>
#include <QMessageBox>
@@ -152,7 +154,10 @@ static void ListInit(QComboBox* combo, T value) {
}
void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
m_ui->lineEdit_user_name->setText(info.user_name);
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
ListInit(m_ui->comboBox_present_mode, info.present_mode);
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
m_ui->comboBox_console_language->clear();
@@ -283,8 +288,11 @@ void ConfigurationEditDialog::moveEvent(QMoveEvent* event) {
}
static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.user_name = ui.lineEdit_user_name->text().trimmed();
info.screen_resolution =
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
info.present_mode =
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
info.console_language = ui.comboBox_console_language->currentIndex();
@@ -322,6 +330,14 @@ void ConfigurationEditDialog::save() {
return;
}
const auto user_name = m_ui->lineEdit_user_name->text().trimmed();
if (user_name.isEmpty() || user_name.toUtf8().size() > Config::MAX_USER_NAME_LENGTH) {
QMessageBox::critical(this, tr("Save failed"),
tr("User name must contain 1-16 UTF-8 bytes"));
return;
}
m_ui->lineEdit_user_name->setText(user_name);
update_info();
emit accept();
+2
View File
@@ -204,6 +204,8 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--screen-width" << r.at(0);
args << "--screen-height" << r.at(1);
args << "--user-name" << info.user_name;
args << "--present-mode" << EnumToText(info.present_mode);
if (info.fullscreen_enabled) {
args << "--fullscreen";
}
+57 -39
View File
@@ -327,25 +327,8 @@ static KYTY_SYSV_ABI int setenv(const char* name, const char* value, int overwri
return -1;
}
if (overwrite == 0 && std::getenv(name) != nullptr) {
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const auto ret = ::_putenv_s(name, value);
if (ret != 0) {
*Posix::GetErrorAddr() = ret;
return -1;
}
// Guest environment changes must not leak into emulator host libraries.
return 0;
#else
const auto ret = ::setenv(name, value, overwrite);
if (ret != 0) {
*Posix::GetErrorAddr() = errno;
return -1;
}
return 0;
#endif
}
static KYTY_SYSV_ABI int64_t libc_time(int64_t* timer) {
@@ -360,75 +343,110 @@ static KYTY_SYSV_ABI double libc_difftime(int64_t time1, int64_t time0) {
return std::difftime(static_cast<std::time_t>(time1), static_cast<std::time_t>(time0));
}
static KYTY_SYSV_ABI std::tm* libc_gmtime(const int64_t* timer) {
struct GuestTm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
static_assert(sizeof(GuestTm) == 36);
static GuestTm ToGuestTm(const std::tm& time) {
return {time.tm_sec, time.tm_min, time.tm_hour, time.tm_mday, time.tm_mon,
time.tm_year, time.tm_wday, time.tm_yday, time.tm_isdst};
}
static std::tm ToHostTm(const GuestTm& time) {
std::tm result {};
result.tm_sec = time.tm_sec;
result.tm_min = time.tm_min;
result.tm_hour = time.tm_hour;
result.tm_mday = time.tm_mday;
result.tm_mon = time.tm_mon;
result.tm_year = time.tm_year;
result.tm_wday = time.tm_wday;
result.tm_yday = time.tm_yday;
result.tm_isdst = time.tm_isdst;
return result;
}
static KYTY_SYSV_ABI GuestTm* libc_gmtime(const int64_t* timer) {
if (timer == nullptr) {
return nullptr;
}
thread_local std::tm result {};
thread_local GuestTm result {};
std::tm host_result {};
const auto t = static_cast<std::time_t>(*timer);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (_gmtime64_s(&result, &t) != 0) {
if (_gmtime64_s(&host_result, &t) != 0) {
return nullptr;
}
#else
if (gmtime_r(&t, &result) == nullptr) {
if (gmtime_r(&t, &host_result) == nullptr) {
return nullptr;
}
#endif
result = ToGuestTm(host_result);
return &result;
}
static KYTY_SYSV_ABI std::tm* libc_localtime(const int64_t* timer) {
static KYTY_SYSV_ABI GuestTm* libc_localtime(const int64_t* timer) {
if (timer == nullptr) {
return nullptr;
}
thread_local std::tm result {};
thread_local GuestTm result {};
std::tm host_result {};
const auto t = static_cast<std::time_t>(*timer);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (_localtime64_s(&result, &t) != 0) {
if (_localtime64_s(&host_result, &t) != 0) {
return nullptr;
}
#else
if (localtime_r(&t, &result) == nullptr) {
if (localtime_r(&t, &host_result) == nullptr) {
return nullptr;
}
#endif
result = ToGuestTm(host_result);
return &result;
}
static KYTY_SYSV_ABI int64_t libc_mktime(std::tm* timeptr) {
static KYTY_SYSV_ABI int64_t libc_mktime(GuestTm* timeptr) {
if (timeptr == nullptr) {
return -1;
}
auto host_time = ToHostTm(*timeptr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return static_cast<int64_t>(_mktime64(timeptr));
const auto result = static_cast<int64_t>(_mktime64(&host_time));
#else
return static_cast<int64_t>(std::mktime(timeptr));
const auto result = static_cast<int64_t>(std::mktime(&host_time));
#endif
*timeptr = ToGuestTm(host_time);
return result;
}
static KYTY_SYSV_ABI size_t libc_strftime(char* str, size_t count, const char* format,
const std::tm* timeptr) {
const GuestTm* timeptr) {
if (str == nullptr || format == nullptr || timeptr == nullptr) {
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
// Guest-created tm values do not initialize glibc's tm_zone pointer.
std::tm sanitized = *timeptr;
sanitized.tm_zone = nullptr;
return std::strftime(str, count, format, &sanitized);
#else
return std::strftime(str, count, format, timeptr);
#endif
const auto host_time = ToHostTm(*timeptr);
return std::strftime(str, count, format, &host_time);
}
static KYTY_SYSV_ABI void catchReturnFromMain(int status) {
+48 -39
View File
@@ -2458,47 +2458,40 @@ static void FiberSetContextValid(FiberObject* fiber, bool valid) {
}
#if defined(__x86_64__) || defined(_M_X64)
__attribute__((noinline, returns_twice)) static int FiberSaveContext(FiberCpuContext* ctx) {
int ret = 0;
asm volatile("movq %[ctx], %%r10\n\t"
"movq %%rbx, 0(%%r10)\n\t"
"movq %%rbp, 8(%%r10)\n\t"
"movq %%rdi, 16(%%r10)\n\t"
"movq %%rsi, 24(%%r10)\n\t"
"movq %%r12, 32(%%r10)\n\t"
"movq %%r13, 40(%%r10)\n\t"
"movq %%r14, 48(%%r10)\n\t"
"movq %%r15, 56(%%r10)\n\t"
"leaq 8(%%rsp), %%r11\n\t"
"movq %%r11, 64(%%r10)\n\t"
"movq (%%rsp), %%r11\n\t"
"movq %%r11, 72(%%r10)\n\t"
"xorl %%eax, %%eax\n\t"
: "=a"(ret)
: [ctx] "r"(ctx)
: "memory", "r10", "r11");
return ret;
[[gnu::naked,
gnu::returns_twice]] static KYTY_SYSV_ABI int FiberSaveContext(FiberCpuContext* /*ctx*/) {
asm volatile("movq %rdi, %r10\n\t"
"movq %rbx, 0(%r10)\n\t"
"movq %rbp, 8(%r10)\n\t"
"movq %rdi, 16(%r10)\n\t"
"movq %rsi, 24(%r10)\n\t"
"movq %r12, 32(%r10)\n\t"
"movq %r13, 40(%r10)\n\t"
"movq %r14, 48(%r10)\n\t"
"movq %r15, 56(%r10)\n\t"
"leaq 8(%rsp), %r11\n\t"
"movq %r11, 64(%r10)\n\t"
"movq (%rsp), %r11\n\t"
"movq %r11, 72(%r10)\n\t"
"xorl %eax, %eax\n\t"
"retq\n");
}
__attribute__((noreturn, noinline)) static void FiberRestoreContext(FiberCpuContext* ctx,
uint64_t ret) {
asm volatile("movq %[ctx], %%r10\n\t"
"movq 72(%%r10), %%r11\n\t"
"movq 0(%%r10), %%rbx\n\t"
"movq 8(%%r10), %%rbp\n\t"
"movq 16(%%r10), %%rdi\n\t"
"movq 24(%%r10), %%rsi\n\t"
"movq 32(%%r10), %%r12\n\t"
"movq 40(%%r10), %%r13\n\t"
"movq 48(%%r10), %%r14\n\t"
"movq 56(%%r10), %%r15\n\t"
"movq 64(%%r10), %%rsp\n\t"
"movq %[ret], %%rax\n\t"
"jmp *%%r11\n\t"
:
: [ctx] "r"(ctx), [ret] "r"(ret)
: "memory", "rax", "r10", "r11");
__builtin_unreachable();
[[gnu::naked, gnu::noreturn]] static KYTY_SYSV_ABI void
FiberRestoreContext(FiberCpuContext* /*ctx*/, uint64_t /*ret*/) {
asm volatile("movq %rdi, %r10\n\t"
"movq %rsi, %rax\n\t"
"movq 72(%r10), %r11\n\t"
"movq 0(%r10), %rbx\n\t"
"movq 8(%r10), %rbp\n\t"
"movq 16(%r10), %rdi\n\t"
"movq 24(%r10), %rsi\n\t"
"movq 32(%r10), %r12\n\t"
"movq 40(%r10), %r13\n\t"
"movq 48(%r10), %r14\n\t"
"movq 56(%r10), %r15\n\t"
"movq 64(%r10), %rsp\n\t"
"jmp *%r11\n");
}
#else
static int FiberSaveContext(FiberCpuContext* ctx) {
@@ -2969,6 +2962,21 @@ int KYTY_SYSV_ABI KernelAioSubmitWriteCommands(KernelAioRwRequest* req, int32_t
return OK;
}
int KYTY_SYSV_ABI KernelAioPollRequest(int32_t id, int32_t* state) {
PRINT_NAME();
if (state == nullptr) {
return LibKernel::KERNEL_ERROR_EFAULT;
}
if (!kernel_aio_is_valid_id(id)) {
return LibKernel::KERNEL_ERROR_EINVAL;
}
*state = g_kernel_aio_state[id].load(std::memory_order_acquire);
return OK;
}
int KYTY_SYSV_ABI KernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) {
PRINT_NAME();
@@ -3337,6 +3345,7 @@ LIB_DEFINE(InitLibKernel_1) {
LIB_FUNC("lLMT9vJAck0", LibKernel::clock_gettime);
LIB_FUNC("5TgME6AYty4", KernelAioDeleteRequest);
LIB_FUNC("HgX7+AORI58", KernelAioSubmitReadCommands);
LIB_FUNC("2pOuoWoCxdk", KernelAioPollRequest);
LIB_FUNC("KOF-oJbQVvc", KernelAioWaitRequest);
LIB_FUNC("XQ8C8y+de+E", KernelAioSubmitWriteCommands);
LIB_FUNC("nu4a0-arQis", KernelAioInitializeParam);
+3 -3
View File
@@ -1,11 +1,10 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/stringUtils.h"
#include "common/emulatorConfig.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <cinttypes>
#include <cstdio>
#include <cstring>
@@ -96,7 +95,8 @@ static KYTY_SYSV_ABI int UserServiceGetUserName(int user_id, char* name, size_t
EXIT_NOT_IMPLEMENTED(user_id != 1000);
EXIT_NOT_IMPLEMENTED(size < 5);
int s = snprintf(name, size, "%s", "Kyty");
const auto& user_name = Config::GetUserName();
int s = snprintf(name, size, "%s", user_name.c_str());
EXIT_NOT_IMPLEMENTED(static_cast<size_t>(s) >= size);
+73 -63
View File
@@ -16,11 +16,6 @@
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
// POSIX uses plain int file descriptors for sockets; provide the Winsock spellings
// the shared (non-guarded) code paths reference.
using SOCKET = int;
static constexpr SOCKET INVALID_SOCKET = -1;
#endif
#include "common/assert.h"
@@ -823,9 +818,11 @@ struct NetEtherAddr {
#if defined(_WIN32)
using NativeSocket = SOCKET;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
using SocketLength = int;
#else
using NativeSocket = int;
static constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
using SocketLength = socklen_t;
#endif
struct SocketSlot {
@@ -898,7 +895,12 @@ static bool EnsureSocketBackend() {
return true;
}
static int SetPosixSocketError(int error) {
static int SetGuestSocketError(int error) {
*Posix::GetErrorAddr() = error;
return -1;
}
static int SetHostSocketError(int error) {
int posix_error = Posix::POSIX_EIO;
#if defined(_WIN32)
switch (error) {
@@ -933,17 +935,31 @@ static int SetPosixSocketError(int error) {
default: break;
}
#else
posix_error = error;
switch (error) {
case EACCES: posix_error = Posix::POSIX_EACCES; break;
case EADDRINUSE: posix_error = Posix::POSIX_EADDRINUSE; break;
case EADDRNOTAVAIL: posix_error = Posix::POSIX_EADDRNOTAVAIL; break;
case EAFNOSUPPORT: posix_error = Posix::POSIX_EAFNOSUPPORT; break;
case EBADF: posix_error = Posix::POSIX_EBADF; break;
case EFAULT: posix_error = Posix::POSIX_EFAULT; break;
case EINVAL: posix_error = Posix::POSIX_EINVAL; break;
case EMFILE: posix_error = Posix::POSIX_EMFILE; break;
case ENFILE: posix_error = Posix::POSIX_ENFILE; break;
case ENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
case ENOMEM: posix_error = Posix::POSIX_ENOMEM; break;
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
default: break;
}
#endif
*Posix::GetErrorAddr() = posix_error;
return -1;
return SetGuestSocketError(posix_error);
}
static int SetPosixSocketError() {
static int SetHostSocketError() {
#if defined(_WIN32)
return SetPosixSocketError(WSAGetLastError());
return SetHostSocketError(WSAGetLastError());
#else
return SetPosixSocketError(errno);
return SetHostSocketError(errno);
#endif
}
@@ -991,9 +1007,8 @@ static int ConvertMessageFlags(int flags) {
#endif
}
#if defined(_WIN32)
static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_storage* out,
int* out_len) {
SocketLength* out_len) {
EXIT_IF(out == nullptr);
EXIT_IF(out_len == nullptr);
@@ -1028,6 +1043,7 @@ static int ConvertGuestSockaddr(const void* addr, uint32_t addrlen, sockaddr_sto
return 0;
}
#if defined(_WIN32)
static int ConvertHostSockaddr(const sockaddr_storage* addr, int addrlen, void* out,
uint32_t* out_len) {
EXIT_IF(addr == nullptr);
@@ -1364,7 +1380,7 @@ int KYTY_SYSV_ABI NetGetSockInfo(int s, void* info, int n, int flags) {
int KYTY_SYSV_ABI EpollCreate(const char* name, int flags) {
if (name == nullptr || flags != 0) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::lock_guard lock(g_epoll_mutex);
@@ -1378,7 +1394,7 @@ int KYTY_SYSV_ABI EpollCreate(const char* name, int flags) {
}
}
return SetPosixSocketError(Posix::POSIX_EMFILE);
return SetGuestSocketError(Posix::POSIX_EMFILE);
}
int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* event) {
@@ -1387,22 +1403,22 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
constexpr int EPOLL_CTL_DEL = 3;
if ((op == EPOLL_CTL_ADD || op == EPOLL_CTL_MOD) && event == nullptr) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op == EPOLL_CTL_DEL && event != nullptr) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (op < EPOLL_CTL_ADD || op > EPOLL_CTL_DEL) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if (!IsSocket(id)) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
auto registration = std::find_if(slot->registrations.begin(), slot->registrations.end(),
@@ -1410,19 +1426,19 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
switch (op) {
case EPOLL_CTL_ADD:
if (registration != slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_EEXIST);
return SetGuestSocketError(Posix::POSIX_EEXIST);
}
slot->registrations.push_back({id, *event});
break;
case EPOLL_CTL_MOD:
if (registration == slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_ENOENT);
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
registration->event = *event;
break;
case EPOLL_CTL_DEL:
if (registration == slot->registrations.end()) {
return SetPosixSocketError(Posix::POSIX_ENOENT);
return SetGuestSocketError(Posix::POSIX_ENOENT);
}
slot->registrations.erase(registration);
break;
@@ -1435,10 +1451,10 @@ int KYTY_SYSV_ABI EpollControl(int eid, int op, int id, const NetEpollEvent* eve
int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) {
if (events == nullptr) {
return SetPosixSocketError(Posix::POSIX_EFAULT);
return SetGuestSocketError(Posix::POSIX_EFAULT);
}
if (maxevents <= 0 || timeout < -1) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
std::vector<EpollRegistration> registrations;
@@ -1446,7 +1462,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
std::unique_lock lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
if (slot->registrations.empty() && timeout != 0) {
@@ -1461,7 +1477,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
}
slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
}
registrations = slot->registrations;
@@ -1492,7 +1508,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
continue;
}
if (host_registrations.size() >= FD_SETSIZE) {
return SetPosixSocketError(Posix::POSIX_EINVAL);
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
if ((registration.event.events & EPOLL_IN) != 0) {
FD_SET(socket, &host_read);
@@ -1518,7 +1534,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
const int result = ::select(0, &host_read, &host_write, &host_except, host_timeout_ptr);
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
if (result == 0) {
return 0;
@@ -1552,7 +1568,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
return count;
#else
(void)timeout;
return SetPosixSocketError(Posix::POSIX_ENOSYS);
return SetGuestSocketError(Posix::POSIX_ENOSYS);
#endif
}
@@ -1560,7 +1576,7 @@ int KYTY_SYSV_ABI EpollDestroy(int eid) {
std::lock_guard lock(g_epoll_mutex);
auto* slot = GetEpollSlot(eid);
if (slot == nullptr) {
return SetPosixSocketError(Posix::POSIX_EBADF);
return SetGuestSocketError(Posix::POSIX_EBADF);
}
slot->used = false;
@@ -1582,14 +1598,15 @@ int KYTY_SYSV_ABI SocketClose(int s) {
RemoveSocketFromEpolls(s);
#if defined(_WIN32)
if (closesocket(socket) == SOCKET_ERROR) {
const int result = closesocket(socket);
#else
const int result = ::close(socket);
#endif
if (result != 0) {
return NET_ERROR_EBADF;
}
return OK;
#else
return NET_ERROR_ENOSYS;
#endif
}
int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
@@ -1601,7 +1618,7 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
family, type, protocol);
if (!EnsureSocketBackend()) {
return SetPosixSocketError(Posix::POSIX_ENETDOWN);
return SetGuestSocketError(Posix::POSIX_ENETDOWN);
}
const int host_family = ConvertFamily(family);
@@ -1610,25 +1627,24 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
return -1;
}
#if defined(_WIN32)
NativeSocket socket = ::socket(host_family, type, protocol);
if (socket == INVALID_NATIVE_SOCKET) {
return SetPosixSocketError();
return SetHostSocketError();
}
const int fd = AllocSocketFd(socket);
if (fd < 0) {
#if defined(_WIN32)
closesocket(socket);
#else
::close(socket);
#endif
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
return -1;
}
LOGF("\t fd = %d\n", fd);
return fd;
#else
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
return -1;
#endif
}
int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
@@ -1645,23 +1661,17 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
return -1;
}
#if defined(_WIN32)
sockaddr_storage host_addr {};
int host_addrlen = 0;
SocketLength host_addrlen = 0;
if (ConvertGuestSockaddr(addr, addrlen, &host_addr, &host_addrlen) != 0) {
return -1;
}
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
return SetHostSocketError();
}
return 0;
#else
*Posix::GetErrorAddr() = Posix::POSIX_ENOSYS;
return -1;
#endif
}
int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
@@ -1687,7 +1697,7 @@ int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
if (::connect(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1712,7 +1722,7 @@ int KYTY_SYSV_ABI Listen(int s, int backlog) {
#if defined(_WIN32)
if (::listen(socket, backlog) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1742,7 +1752,7 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
NativeSocket accepted =
::accept(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen);
if (accepted == INVALID_NATIVE_SOCKET) {
return SetPosixSocketError();
return SetHostSocketError();
}
const int fd = AllocSocketFd(accepted);
@@ -1791,7 +1801,7 @@ int KYTY_SYSV_ABI Shutdown(int s, int how) {
#if defined(_WIN32)
if (::shutdown(socket, how) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1821,7 +1831,7 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
int host_addrlen = sizeof(host_addr);
if (::getsockname(socket, reinterpret_cast<sockaddr*>(&host_addr), &host_addrlen) ==
SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
@@ -1850,7 +1860,7 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
int len = static_cast<int>(*optlen);
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(optval),
&len) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
*optlen = static_cast<uint32_t>(len);
return 0;
@@ -1881,14 +1891,14 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
optlen >= sizeof(int)) {
u_long enabled = (*static_cast<const int*>(optval) != 0 ? 1 : 0);
if (ioctlsocket(socket, FIONBIO, &enabled) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
}
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
static_cast<const char*>(optval), static_cast<int>(optlen)) == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return 0;
@@ -1941,7 +1951,7 @@ int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, co
reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen);
}
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return result;
@@ -2000,7 +2010,7 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
}
}
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
return result;
@@ -2101,7 +2111,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
const int result = ::select(0, read_ptr, write_ptr, except_ptr, host_timeout_ptr);
if (result == SOCKET_ERROR) {
return SetPosixSocketError();
return SetHostSocketError();
}
GuestFdZero(readfds, nfds);
+15
View File
@@ -47,6 +47,9 @@ static void PrintUsage() {
" --game-patch <json> Validated patch plan to apply before entry.\n");
::printf(" --screen-width <num> Window width. Default: 1280.\n");
::printf(" --screen-height <num> Window height. Default: 720.\n");
::printf(
" --user-name <name> Local user name (1-16 bytes). Default: Kyty.\n");
::printf(" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Fifo.\n");
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
@@ -201,6 +204,18 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
} else if (arg == "--screen-height") {
options.config.screen_height = static_cast<uint32_t>(Common::ToInt32(value));
} else if (arg == "--user-name") {
if (value.empty() || value.size() > Config::MAX_USER_NAME_LENGTH) {
::printf("invalid user name: must contain 1-%zu bytes\n",
Config::MAX_USER_NAME_LENGTH);
return false;
}
options.config.user_name = value;
} else if (arg == "--present-mode") {
if (!ParseEnum(value, options.config.present_mode)) {
::printf("invalid present mode: %s\n", value.c_str());
return false;
}
} else if (arg == "--vblank-frequency") {
const int32_t vblank_frequency = Common::ToInt32(value);
options.config.vblank_frequency =
+23 -36
View File
@@ -295,7 +295,7 @@ void TestInvariantIndirectImageMaterialization() {
const auto same_snapshot = [](const ResourceSnapshot &lhs,
const ResourceSnapshot &rhs) {
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
lhs.samplers == rhs.samplers && lhs.addresses == rhs.addresses &&
lhs.samplers == rhs.samplers &&
lhs.flattened_srt == rhs.flattened_srt &&
lhs.user_data == rhs.user_data &&
lhs.indirect_images.empty() == rhs.indirect_images.empty();
@@ -848,7 +848,7 @@ void TestSrtFlatteningAndRuntimeMemoization() {
Check(fixture.program.srt_reads.size() == 1,
"shared typed scalar read did not receive one flat SRT slot");
Check(fixture.program.info.buffers.size() == 1 &&
fixture.program.info.addresses.empty(),
!fixture.program.info.uses_dma,
"planning-only scalar reads leaked into resource topology");
Check(fixture.program.memory_info[0].planning_only,
"canonical runtime scalar read was not marked planning-only");
@@ -910,7 +910,7 @@ void TestDynamicSrtReadRemainsExplicit() {
Check(fixture.program.srt_reads.empty() &&
fixture.program.dynamic_reads.size() == 1 &&
fixture.program.info.addresses.size() == 1,
fixture.program.info.uses_dma,
"dynamic scalar read was incorrectly flattened or lost");
std::array<uint32_t, 3> user_data{0x1000u, 0u, 4u};
TestMemory memory;
@@ -932,11 +932,13 @@ void TestDynamicSrtReadRemainsExplicit() {
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FlattenedSrt) == nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::AddressMemory) != nullptr,
DescriptorBindingKind::BdaPagetable) != nullptr &&
FindBinding(fixture.program.bindings,
DescriptorBindingKind::FaultBuffer) != nullptr,
"dynamic scalar read received the wrong resource bindings");
Check(fixture.program.bindings.memory_offset_dword ==
fixture.program.bindings.user_data_registers.size() &&
fixture.program.bindings.memory_offset_count == 2u &&
fixture.program.bindings.memory_offset_count == 1u &&
fixture.program.bindings.ShaderDataDwords() ==
fixture.program.bindings.memory_offset_dword + 1u,
"unified memory-offset layout is inconsistent");
@@ -1029,7 +1031,7 @@ void TestInvariantLoopPhi() {
"loop-invariant descriptor phi was not evaluated through typed SSA");
}
void TestAddressMaterializationAndSpecialization() {
void TestDmaAddressMaterialization() {
Fixture fixture;
const auto based =
fixture.Address(fixture.UserData(0), fixture.UserData(1), 4);
@@ -1050,29 +1052,19 @@ void TestAddressMaterializationAndSpecialization() {
fixture.AddMemory(flat, 8));
fixture.PlanAndTrack();
Check(fixture.program.info.addresses.size() == 2 &&
!fixture.program.info.addresses[0].unbased &&
fixture.program.info.addresses[0].min_offset == -8 &&
fixture.program.info.addresses[1].unbased,
"typed based and unbased addresses were classified incorrectly");
Check(fixture.program.info.uses_dma,
"typed address operations did not enable DMA");
std::array<uint32_t, 2> user_data{0x2008u, 0u};
SrtRuntime runtime{.user_data = user_data, .flat_memory_base = 0x9000u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
"address resources did not materialize");
Check(snapshot.addresses.size() == 2 &&
snapshot.addresses[0].guest_base == 0x2008u &&
snapshot.addresses[0].binding_base == 0x2000u &&
snapshot.addresses[1].binding_base == 0x9000u,
"materialized address windows are incorrect");
Check(SpecializeResources(fixture.program, snapshot, &error) &&
fixture.program.info.addresses[0].specialized_base == 8u &&
fixture.program.info.addresses[1].specialized_base == 0x9000u,
"typed address specialization was not applied");
"DMA shader resources did not materialize");
Check(SpecializeResources(fixture.program, snapshot, &error),
"DMA shader resources were not specialized");
}
void TestExecMaskedFlatAddressProvenance() {
void TestDynamicFlatAddressesUseDma() {
Fixture fixture;
const auto low_root = fixture.UserData(0);
const auto high_root = fixture.UserData(1);
@@ -1092,18 +1084,14 @@ void TestExecMaskedFlatAddressProvenance() {
fixture.AddMemory(flat, 0xa4));
fixture.PlanAndTrack();
Check(fixture.program.info.addresses.size() == 1 &&
!fixture.program.info.addresses[0].unbased,
"exec-masked FLAT address lost its active user-data root");
Check(fixture.program.info.uses_dma,
"exec-masked FLAT address did not enable DMA");
std::array<uint32_t, 3> user_data{0x23456780u, 1u, 1u};
SrtRuntime runtime{.user_data = user_data};
ResourceSnapshot snapshot;
std::string error;
Check(MaterializeResources(fixture.program, runtime, snapshot, &error) &&
snapshot.addresses.size() == 1 &&
snapshot.addresses[0].guest_base == 0x0000000123456780ull &&
snapshot.addresses[0].binding_base == 0x0000000123450000ull,
"exec-masked FLAT address materialized the wrong user-data root");
Check(MaterializeResources(fixture.program, runtime, snapshot, &error),
"exec-masked FLAT shader resources did not materialize");
Fixture mismatch;
const auto mismatch_active = mismatch.Emit(ValueOpcode::INotEqual32,
@@ -1122,9 +1110,8 @@ void TestExecMaskedFlatAddressProvenance() {
{mismatch_address, mismatch_low, mismatch_high, other_active},
mismatch.AddMemory(flat, 0xa4));
mismatch.PlanAndTrack();
Check(mismatch.program.info.addresses.size() == 1 &&
mismatch.program.info.addresses[0].unbased,
"FLAT address used a select arm guarded by a different active mask");
Check(mismatch.program.info.uses_dma,
"dynamic FLAT address did not enable DMA");
}
void TestBufferSwizzleSpecialization() {
@@ -1393,8 +1380,8 @@ int main() {
Run("phi validation", TestPhiValidation);
Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue);
Run("invariant loop phi", TestInvariantLoopPhi);
Run("address materialization", TestAddressMaterializationAndSpecialization);
Run("exec-masked FLAT address", TestExecMaskedFlatAddressProvenance);
Run("DMA address materialization", TestDmaAddressMaterialization);
Run("dynamic FLAT address", TestDynamicFlatAddressesUseDma);
Run("buffer swizzle specialization", TestBufferSwizzleSpecialization);
Run("shader info and bindings", TestShaderInfoAndBindingLayout);
Run("graphics push constants", TestGraphicsPushConstantLayout);
File diff suppressed because it is too large. Load diff
+6 -14
View File
@@ -73,12 +73,7 @@ UnbasedFlatProgram() {
program.srt_plan_complete = true;
program.resource_tracking_complete = true;
AddValueBlock(program);
AddressResource address;
address.source = UINT32_MAX;
address.first_use_pc = 0x80;
address.kind = ResourceKind::Flat;
address.unbased = true;
program.info.addresses.push_back(address);
program.info.uses_dma = true;
return std::make_shared<const Program>(std::move(program));
}
@@ -102,7 +97,7 @@ void TestMappedSrtUsesDirectReaderByDefault() {
"cache rematerialization did not use the direct reader by default");
}
void TestUnbasedFlatCacheHitFailsClosed() {
void TestUnbasedFlatCacheHitMaterializes() {
using namespace Libs::Graphics;
auto cached_program = UnbasedFlatProgram();
auto prior_program = std::make_shared<const ShaderRecompiler::IR::Program>();
@@ -110,12 +105,9 @@ void TestUnbasedFlatCacheHitFailsClosed() {
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
ShaderStageRuntime stage{prior_program, prior_resources};
std::string error;
Check(!ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error) &&
error.find("requires runtime guest-address translation") !=
std::string::npos,
"unbased FLAT cache hit did not fail without a runtime translator");
Check(stage.program == prior_program && stage.resources == prior_resources,
"unbased FLAT cache rejection replaced the prior stage snapshot");
Check(ShaderMaterializeStageRuntime(cached_program, {}, 0, stage, &error), error.c_str());
Check(stage.program == cached_program && stage.resources != prior_resources,
"unbased FLAT cache hit did not publish its BDA stage snapshot");
}
} // namespace
@@ -130,7 +122,7 @@ void DbgExit(int) { std::abort(); }
int main() {
TestMappedSrtUsesDirectReaderByDefault();
TestUnbasedFlatCacheHitFailsClosed();
TestUnbasedFlatCacheHitMaterializes();
std::puts("ShaderStageRuntimeTests: all cases passed");
return 0;
}
+196 -147
View File
@@ -25,6 +25,7 @@
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include "graphics/shader/shader.h"
#include "graphics/shader/shaderCompiler.h"
#include "libs/agc.h"
#include "spirv-tools/libspirv.hpp"
@@ -76,6 +77,46 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
return options;
}
bool ReadHostTestMemory(void *, uint64_t address, uint32_t *value) {
if (address == 0 || value == nullptr) {
return false;
}
std::memcpy(value, reinterpret_cast<const void *>(address), sizeof(*value));
return true;
}
bool CompilePixelRuntime(const ShaderParams &params,
ShaderPixelInputInfo &input_info,
std::string *error) {
auto options = MakeCompileOptions(ShaderType::Pixel);
options.shader_hash = params.hash;
options.shader_base = params.Base();
options.user_data = params.user_data.data();
options.user_data_count = static_cast<uint32_t>(params.user_data.size());
options.scratch_dwords = input_info.scratch_size_dwords;
options.push_constant_offset = input_info.push_constant_offset;
options.read_specialization_memory = ReadHostTestMemory;
options.input_info.pixel = &input_info;
ShaderRecompiler::CompileResult result;
if (!ShaderRecompiler::TryRecompile(params.code, options, result, error)) {
return false;
}
input_info.stage.program =
std::make_shared<const ShaderRecompiler::IR::Program>(
std::move(result.program));
input_info.stage.resources =
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(
std::move(result.resources));
return true;
}
template <typename InputInfo>
std::vector<uint32_t> MakeStageStaticKey(const InputInfo &input_info) {
std::vector<uint32_t> key;
BuildStageStaticKey(input_info, key);
return key;
}
std::vector<uint32_t>
CfgInstructionCoverage(const ShaderRecompiler::CFG::Graph &graph,
size_t instruction_count) {
@@ -4319,14 +4360,14 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
error.c_str());
const auto *address_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory);
Check(raw.program.info.addresses.size() == 1u &&
raw.program.info.addresses[0].kind ==
ShaderRecompiler::IR::ResourceKind::ScalarAddress &&
raw.program.info.addresses[0].read &&
!raw.program.info.addresses[0].written &&
raw.program.info.buffers.empty() && address_binding != nullptr &&
address_binding->resources == std::vector<uint32_t>{0u} &&
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable);
const auto *fault_binding = ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer);
Check(raw.program.info.uses_dma && raw.program.info.buffers.empty() &&
address_binding != nullptr && fault_binding != nullptr &&
address_binding->resources.empty() &&
fault_binding->resources.empty() &&
ShaderRecompiler::IR::FindBinding(
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::Buffers) ==
@@ -4335,17 +4376,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
raw.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) ==
nullptr &&
raw.program.bindings.memory_offset_count == 1u,
"raw scalar load did not use only the address-memory domain");
raw.program.bindings.memory_offset_count == 0u,
"raw scalar load did not use only the DMA domain");
Check(count_live_memory_ops(
raw.program, ShaderRecompiler::IR::ValueOpcode::LoadAddressU32,
ShaderRecompiler::IR::ResourceKind::ScalarAddress) == 1u,
"raw scalar load did not remain a live typed address operation");
Check(raw.resources.addresses.size() == 1u &&
raw.resources.addresses[0].guest_base == 0x1000u &&
raw.resources.addresses[0].binding_base == 0x1000u &&
raw.program.info.addresses[0].specialized_base == 0u,
"raw scalar address specialization was incorrect");
Check(SpirvContainsOpcode(raw.spirv, 199),
"raw scalar SOFFSET alignment was not emitted");
CheckSpirvBinaryValidates(raw.spirv);
@@ -4377,12 +4413,12 @@ void TestNewShaderRecompilerScalarMemoryBindingDomains() {
ShaderRecompiler::IR::DescriptorBindingKind::Buffers);
Check(buffer.program.info.buffers.size() == 1u &&
buffer.program.info.buffers[0].scalar &&
buffer.program.info.addresses.empty() &&
!buffer.program.info.uses_dma &&
buffer_binding != nullptr &&
buffer_binding->resources == std::vector<uint32_t>{0u} &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::AddressMemory) ==
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) ==
nullptr &&
ShaderRecompiler::IR::FindBinding(
buffer.program.bindings,
@@ -6201,7 +6237,6 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -6224,23 +6259,32 @@ void TestNewShaderRecompilerFlatOldBackedTranslation() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerUnbasedFlatRequiresTranslator() {
void TestNewShaderRecompilerUnbasedFlatUsesBda() {
const uint32_t shader[] = {
EncodeFlat0(0x0c, 0, 0),
EncodeFlat1(0, 0x7d, 0, 1),
EncodeFlat0(0x1c, 0, 0),
EncodeFlat1(0, 0x7d, 0, 2),
0xbf810000u,
EncodeExp0(0x00, 0x1),
EncodeExp1(0, 0, 0, 0),
EncodeSopp(0x01),
};
auto options = MakeCompileOptions(ShaderType::Compute);
auto options = MakeCompileOptions(ShaderType::Pixel);
ShaderRecompiler::CompileResult result;
std::string error;
Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
Common::ContainsStr(error,
"requires runtime guest-address translation"),
"unbased FLAT compiled without an explicit translator");
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(result.program.info.uses_dma &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::BdaPagetable) !=
nullptr &&
ShaderRecompiler::IR::FindBinding(
result.program.bindings,
ShaderRecompiler::IR::DescriptorBindingKind::FaultBuffer) !=
nullptr,
"unbased FLAT did not compile through BDA");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerFlatSignedLoadTranslation() {
@@ -6256,7 +6300,6 @@ void TestNewShaderRecompilerFlatSignedLoadTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -6311,7 +6354,6 @@ void TestNewShaderRecompilerFlatStoreTranslation() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
@@ -9106,7 +9148,7 @@ void TestNewShaderRecompilerAuxPositionExports() {
ShaderVertexInputInfo key0{};
ShaderVertexInputInfo key1{};
key1.pa_cl_vs_out_cntl = 1;
Check(ShaderGetIdVS(regs, key0, false) != ShaderGetIdVS(regs, key1, false),
Check(MakeStageStaticKey(key0) != MakeStageStaticKey(key1),
"PA_CL_VS_OUT_CNTL is absent from the vertex shader cache key");
}
@@ -10512,8 +10554,7 @@ void TestRenderTargetReverseExportMapping() {
reversed_info.target_export_mapping[0] = gr32.export_mapping;
HW::PixelShaderInfo regs{};
Check(ShaderGetIdPS(regs, identity_info, false) !=
ShaderGetIdPS(regs, reversed_info, false),
Check(MakeStageStaticKey(identity_info) != MakeStageStaticKey(reversed_info),
"pixel shader cache identity omitted the render-target export mapping");
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
@@ -10526,18 +10567,13 @@ void TestRenderTargetReverseExportMapping() {
mappings[0] = gr32.export_mapping;
ShaderPixelInputInfo compiled_info{};
ShaderVertexInputInfo vertex_info{};
std::span<const uint32_t> compiled_spirv;
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
PrepareProgram(regs, sh, vertex_info, mappings, compiled_info);
Check(compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
"cache key");
sh.target_output_mode[0] = 4;
Check(
ShaderCompileInfoPS(regs, sh, vertex_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0] == gr32.export_mapping,
PrepareProgram(regs, sh, vertex_info, mappings, compiled_info);
Check(compiled_info.target_export_mapping[0] == gr32.export_mapping,
"active reverse MRT mapping was lost before shader specialization");
}
@@ -11516,15 +11552,12 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
ShaderMapUserData(regs.ps_regs.data_addr, mapped);
HW::ShaderRegisters sh{};
const std::array<Prospero::ColorComponentMapping, 8> mappings{};
ShaderVertexInputInfo vertex_info{};
ShaderPixelInputInfo input_info{};
std::vector<uint32_t> spirv;
Check(ShaderCompileSpirvPS(regs, sh, input_info, spirv),
"new pixel shader recompiler wrapper did not produce SPIR-V");
Check(!spirv.empty(),
"new pixel shader recompiler wrapper returned empty SPIR-V");
Check(spirv.front() == 0x07230203u,
"new pixel shader recompiler wrapper did not emit SPIR-V binary");
CheckSpirvBinaryValidates(spirv);
const auto params = PrepareProgram(regs, sh, vertex_info, mappings, input_info);
Check(params.hash == regs.ps_regs.chksum && params.code.data() == shader,
"pixel shader program parameters lost source identity");
const uint32_t vcc_load_shader[] = {
EncodeSMovB32(106, 27), EncodeSMovB32(107, 28), EncodeSmem0(0x02, 44, 53),
@@ -11550,10 +11583,10 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
ShaderMapUserData(vcc_regs.ps_regs.data_addr, vcc_mapped);
ShaderPixelInputInfo vcc_input{};
std::vector<uint32_t> vcc_spirv;
Check(ShaderCompileSpirvPS(vcc_regs, sh, vcc_input, vcc_spirv),
"VCC raw-load pointer was lost");
CheckSpirvBinaryValidates(vcc_spirv);
const auto vcc_params =
PrepareProgram(vcc_regs, sh, vertex_info, mappings, vcc_input);
std::string error;
Check(CompilePixelRuntime(vcc_params, vcc_input, &error), error.c_str());
}
void TestComputeLdsAllocationIdentity() {
@@ -11576,19 +11609,28 @@ void TestComputeLdsAllocationIdentity() {
uint32_t expected_dwords) {
regs.cs_regs.lds_size = encoded_lds_size;
ShaderComputeInputInfo input_info{};
std::span<const uint32_t> spirv;
Check(ShaderCompileInfoCS(regs, sh, false, input_info, spirv),
"compute LDS allocation shader did not compile");
const auto params = PrepareProgram(regs, sh, input_info);
Check(input_info.lds_size_dwords == expected_dwords,
"COMPUTE_PGM_RSRC2 LDS allocation units were not decoded");
const std::vector<uint32_t> binary(spirv.begin(), spirv.end());
Check(MeasureSpirv(binary).workgroup_variables == 1u,
auto options = MakeCompileOptions(ShaderType::Compute);
options.shader_hash = params.hash;
options.shader_base = params.Base();
options.user_data = params.user_data.data();
options.user_data_count = static_cast<uint32_t>(params.user_data.size());
options.input_info.compute = &input_info;
options.wave_size = input_info.wave_size;
options.scratch_dwords = input_info.scratch_size_dwords;
ShaderRecompiler::CompileResult result;
std::string error;
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(MeasureSpirv(result.spirv).workgroup_variables == 1u,
"LDS shader did not declare exactly one Workgroup variable");
Check(SpirvArrayLengthCount(binary, expected_dwords) == 1u,
Check(SpirvArrayLengthCount(result.spirv, expected_dwords) == 1u,
"SPIR-V workgroup array did not use the declared LDS allocation");
Check(SpirvUnsignedLessThanBoundCount(binary, expected_dwords) == 1u,
Check(SpirvUnsignedLessThanBoundCount(result.spirv, expected_dwords) == 1u,
"LDS bounds check did not use the declared allocation");
CheckSpirvBinaryValidates(binary);
CheckSpirvBinaryValidates(result.spirv);
return input_info;
};
@@ -11601,21 +11643,19 @@ void TestComputeLdsAllocationIdentity() {
};
const auto lds_1152 = compile(decode_lds_field(lds_1152_rsrc2), 1152u);
const auto lds_896 = compile(decode_lds_field(lds_896_rsrc2), 896u);
Check(ShaderGetIdCS(regs, lds_1152, true) !=
ShaderGetIdCS(regs, lds_896, true),
Check(MakeStageStaticKey(lds_1152) != MakeStageStaticKey(lds_896),
"compute pipeline identity omitted the LDS allocation");
auto split_wave = lds_896;
split_wave.needs_lds_barriers = true;
Check(ShaderGetIdCS(regs, lds_896, true) !=
ShaderGetIdCS(regs, split_wave, true),
Check(MakeStageStaticKey(lds_896) != MakeStageStaticKey(split_wave),
"compute shader identity omitted split-wave LDS synchronization");
auto tg_size_disabled = lds_896;
auto tg_size_enabled = lds_896;
tg_size_enabled.tg_size_en = true;
Check(ShaderGetIdCS(regs, tg_size_disabled, false) !=
ShaderGetIdCS(regs, tg_size_enabled, false),
Check(MakeStageStaticKey(tg_size_disabled) !=
MakeStageStaticKey(tg_size_enabled),
"compute shader identity omitted TG_SIZE semantics");
auto dispatch_disabled = lds_896;
@@ -11624,32 +11664,42 @@ void TestComputeLdsAllocationIdentity() {
dispatch_disabled.dispatch_threads_num[2] = 1;
auto dispatch_enabled = dispatch_disabled;
dispatch_enabled.dispatch_thread_dimensions = true;
Check(ShaderGetIdCS(regs, dispatch_disabled, false) ==
ShaderGetIdCS(regs, dispatch_enabled, false),
"runtime dispatch enable created a compute shader variant");
Check(MakeStageStaticKey(dispatch_disabled) !=
MakeStageStaticKey(dispatch_enabled),
"dispatch-dimension mode did not select a compute shader variant");
auto second_extent = dispatch_enabled;
second_extent.dispatch_threads_num[0] = 1920;
second_extent.dispatch_threads_num[1] = 1080;
second_extent.dispatch_threads_num[2] = 4;
Check(ShaderGetIdCS(regs, dispatch_enabled, false) ==
ShaderGetIdCS(regs, second_extent, false),
Check(MakeStageStaticKey(dispatch_enabled) ==
MakeStageStaticKey(second_extent),
"runtime dispatch extent created a compute shader variant");
mapped.scratch_size_dwords = 7;
ShaderMapUserData(regs.cs_regs.data_addr, mapped);
regs.cs_regs.lds_size = decode_lds_field(lds_896_rsrc2);
ShaderComputeInputInfo scratch_info{};
std::span<const uint32_t> scratch_spirv;
Check(ShaderCompileInfoCS(regs, sh, false, scratch_info, scratch_spirv),
"compute scratch metadata shader did not compile");
const auto scratch_params = PrepareProgram(regs, sh, scratch_info);
Check(scratch_info.scratch_size_dwords == 7,
"AGC per-thread scratch size was not propagated");
Check(scratch_info.stage.program != nullptr &&
scratch_info.stage.program->scratch_dwords == 7,
auto scratch_options = MakeCompileOptions(ShaderType::Compute);
scratch_options.shader_hash = scratch_params.hash;
scratch_options.shader_base = scratch_params.Base();
scratch_options.user_data = scratch_params.user_data.data();
scratch_options.user_data_count =
static_cast<uint32_t>(scratch_params.user_data.size());
scratch_options.input_info.compute = &scratch_info;
scratch_options.wave_size = scratch_info.wave_size;
scratch_options.scratch_dwords = scratch_info.scratch_size_dwords;
ShaderRecompiler::CompileResult scratch_result;
std::string scratch_error;
Check(ShaderRecompiler::TryRecompile(scratch_params.code, scratch_options,
scratch_result, &scratch_error),
scratch_error.c_str());
Check(scratch_result.program.scratch_dwords == 7,
"AGC per-thread scratch size did not reach the compiler program");
Check(ShaderGetIdCS(regs, scratch_info, true) !=
ShaderGetIdCS(regs, lds_896, true),
Check(MakeStageStaticKey(scratch_info) != MakeStageStaticKey(lds_896),
"compute pipeline identity omitted the scratch allocation");
const uint32_t append_shader[] = {
@@ -11809,20 +11859,18 @@ void TestPixelProgramCacheBindingIdentity() {
const uint32_t shader_10[] = {0xbf810000u};
HW::ShaderRegisters sh{};
HW::PixelShaderInfo identity_regs{};
ShaderPixelInputInfo no_depth_export{};
auto depth_export = no_depth_export;
depth_export.ps_depth_export_enable = true;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, depth_export, false),
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(depth_export),
"pixel shader identity omitted depth-export semantics");
auto shifted_push = no_depth_export;
shifted_push.push_constant_offset = 36;
Check(ShaderGetIdPS(identity_regs, no_depth_export, false) !=
ShaderGetIdPS(identity_regs, shifted_push, false),
Check(MakeStageStaticKey(no_depth_export) != MakeStageStaticKey(shifted_push),
"pixel shader identity omitted graphics push-bank placement");
auto check_cache_identity = [&](const uint32_t *shader, uint64_t checksum) {
auto check_program_identity = [&](const uint32_t *shader,
uint64_t checksum) {
HW::PixelShaderInfo regs{};
regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
regs.ps_regs.chksum = checksum;
@@ -11833,26 +11881,29 @@ void TestPixelProgramCacheBindingIdentity() {
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderVertexInputInfo vertex_info{};
ShaderPixelInputInfo first_info{};
std::span<const uint32_t> first_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, first_info,
first_spirv),
"pixel program-cache fixture failed to compile");
const auto first_params =
PrepareProgram(regs, sh, vertex_info, identity_mappings, first_info);
const auto first_key = MakeStageStaticKey(first_info);
std::string error;
Check(CompilePixelRuntime(first_params, first_info, &error), error.c_str());
ShaderPixelInputInfo second_info{};
std::span<const uint32_t> second_spirv;
Check(ShaderCompileInfoPS(regs, sh, vertex_info, identity_mappings, second_info,
second_spirv),
"pixel program-cache lookup failed");
const auto second_params =
PrepareProgram(regs, sh, vertex_info, identity_mappings, second_info);
Check(
first_info.stage.program != nullptr &&
second_info.stage.program == first_info.stage.program &&
second_spirv.data() == first_spirv.data() &&
second_spirv.size() == first_spirv.size(),
"pixel program cache did not reuse an identical graphics placement");
first_params.hash == second_params.hash &&
first_key == MakeStageStaticKey(second_info) &&
first_info.stage.program != nullptr &&
MaterializeProgram(first_info.stage.program, second_params, second_info) &&
second_info.stage.program == first_info.stage.program,
"pixel program matching did not reuse an identical graphics placement");
return std::pair {first_params.hash, first_key};
};
check_cache_identity(shader_01, 0x91a27e6300000001ull);
check_cache_identity(shader_10, 0x91a27e6300000002ull);
const auto request_01 = check_program_identity(shader_01, 0);
const auto request_10 = check_program_identity(shader_10, 0);
Check(request_01 == request_10,
"relocated identical pixel programs did not share their source identity");
const uint32_t push_shader[] = {
EncodeVop1(0x01, 0, 0), EncodeVop1(0x01, 1, 1),
@@ -11882,24 +11933,36 @@ void TestPixelProgramCacheBindingIdentity() {
ShaderPixelInputInfo at_zero{};
ShaderPixelInputInfo at_36{};
ShaderPixelInputInfo at_zero_again{};
std::span<const uint32_t> spirv_at_zero;
std::span<const uint32_t> spirv_at_36;
std::span<const uint32_t> spirv_at_zero_again;
Check(ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero, spirv_at_zero) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_36, mappings,
at_36, spirv_at_36) &&
ShaderCompileInfoPS(push_regs, push_sh, vertex_at_zero, mappings,
at_zero_again, spirv_at_zero_again),
"pixel program-cache placement fixture failed to compile");
const auto params_at_zero =
PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings, at_zero);
const auto key_at_zero = MakeStageStaticKey(at_zero);
std::string error;
Check(CompilePixelRuntime(params_at_zero, at_zero, &error), error.c_str());
const auto params_at_36 =
PrepareProgram(push_regs, push_sh, vertex_at_36, mappings, at_36);
const auto key_at_36 = MakeStageStaticKey(at_36);
Check(CompilePixelRuntime(params_at_36, at_36, &error), error.c_str());
const auto params_at_zero_again =
PrepareProgram(push_regs, push_sh, vertex_at_zero, mappings,
at_zero_again);
auto shifted_options = MakeCompileOptions(ShaderType::Pixel);
shifted_options.input_info.pixel = &at_36;
shifted_options.push_constant_offset = at_36.push_constant_offset;
ShaderRecompiler::CompileResult shifted_result;
std::string shifted_error;
Check(ShaderRecompiler::TryRecompile(push_shader, shifted_options,
shifted_result, &shifted_error),
shifted_error.c_str());
Check(at_zero.push_constant_offset == 0 &&
at_36.push_constant_offset == 36 &&
key_at_zero != key_at_36 &&
MakeStageStaticKey(at_zero_again) == key_at_zero &&
at_zero.stage.program != at_36.stage.program &&
spirv_at_zero.data() != spirv_at_36.data() &&
MaterializeProgram(at_zero.stage.program, params_at_zero_again,
at_zero_again) &&
at_zero_again.stage.program == at_zero.stage.program &&
spirv_at_zero_again.data() == spirv_at_zero.data() &&
SpirvHasMemberDecorationValue(spirv_at_36, 35u, 36u),
"pixel program cache did not partition and reuse graphics push placement");
SpirvHasMemberDecorationValue(shifted_result.spirv, 35u, 36u),
"pixel program matching did not partition and reuse graphics push placement");
}
void TestGraphicsPushConstantPlacement() {
@@ -11966,49 +12029,40 @@ void TestNewShaderRecompilerUnsupportedMemoryDecode() {
"MTBUF", "opcode=0x08");
}
void TestNewShaderRecompilerFlatUserPointerProvenance() {
void TestNewShaderRecompilerFlatUserPointerUsesDma() {
const uint32_t shader[] = {
EncodeVop2(0x25, 0, 0, 2), // v_add_nc_u32 v0, s0, v2
EncodeVop1(0x01, 1, 1), // v_mov_b32 v1, s1
EncodeFlat0(0x1c, 0, 0),
EncodeFlat1(0, 0x7d, 3, 0), // flat_store_dword v3, v[0:1]
0xbf810000u,
EncodeFlat0(0x0c, 0, 0),
EncodeFlat1(3, 0x7d, 0, 0), // flat_load_dword v3, v[0:1]
EncodeExp0(0x00, 0x1),
EncodeExp1(3, 0, 0, 0),
EncodeSopp(0x01),
};
const uint32_t user_data[] = {0x34567000u, 0x00000012u};
auto options = MakeCompileOptions(ShaderType::Compute);
auto options = MakeCompileOptions(ShaderType::Pixel);
options.user_data = user_data;
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
options.flat_memory_base = 0;
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(shader, options, result, &error);
Check(compiled, error.c_str());
Check(result.program.info.addresses.size() == 1 &&
!result.program.info.addresses[0].unbased &&
result.program.info.addresses[0].source <
result.program.descriptor_sources.size(),
"FLAT user pointer provenance was not attached");
Check(result.resources.addresses.size() == 1 &&
result.resources.addresses[0].guest_base == 0x0000001234567000ull &&
result.resources.addresses[0].binding_base ==
0x0000001234560000ull &&
result.program.info.addresses[0].specialized_base ==
0x0000001234560000ull,
"FLAT user pointer did not materialize its guest base");
Check(result.program.info.uses_dma,
"FLAT user pointer did not enable DMA");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
void TestNewShaderRecompilerFlatAddressDomainsUseDma() {
const uint32_t segmented_shader[] = {
EncodeVop2(0x25, 0, 0, 2),
EncodeVop1(0x01, 1, 1),
EncodeFlat0(0x1c, 2, 0),
EncodeFlat1(0, 0x7d, 3, 0), // global_store_dword v3, v[0:1]
EncodeFlat0(0x0c, 2, 0),
EncodeFlat1(3, 0x7d, 0, 0), // global_load_dword v3, v[0:1]
EncodeFlat0(0x1c, 1, 0),
EncodeFlat1(0, 0x7d, 4, 0), // scratch_store_dword v4, v[0:1]
EncodeFlat1(0, 0x7d, 3, 0), // scratch_store_dword v3, v[0:1]
0xbf810000u,
};
const uint32_t user_data[] = {0x34567000u, 0u, 0x1000u};
@@ -12016,19 +12070,14 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
auto options = MakeCompileOptions(ShaderType::Compute);
options.user_data = user_data;
options.user_data_count = static_cast<uint32_t>(std::size(user_data));
options.flat_memory_base = 0;
options.scratch_dwords = 1;
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
Check(compiled, error.c_str());
Check(result.program.info.addresses.size() == 1 &&
result.program.info.addresses[0].kind ==
ShaderRecompiler::IR::ResourceKind::Global &&
result.program.info.addresses[0].unbased &&
result.program.info.addresses[0].source == UINT32_MAX,
"GLOBAL null-SADDR did not remain an unbased guest address");
Check(result.program.info.uses_dma,
"GLOBAL null-SADDR did not enable DMA");
Check(Common::ContainsStr(result.ir_dump, "GetScratchResource") &&
result.program.scratch_dwords == 1,
"SCRATCH incorrectly entered guest address tracking");
@@ -12324,9 +12373,9 @@ int main() {
TestNewShaderRecompilerRejectsDppOn64BitCompares();
TestNewShaderRecompilerIrLookupMissFailsExplicitly();
TestPsInputCountRegisterDecode();
TestNewShaderRecompilerUnbasedFlatRequiresTranslator();
TestNewShaderRecompilerFlatUserPointerProvenance();
TestNewShaderRecompilerFlatAddressProvenanceBoundaries();
TestNewShaderRecompilerUnbasedFlatUsesBda();
TestNewShaderRecompilerFlatUserPointerUsesDma();
TestNewShaderRecompilerFlatAddressDomainsUseDma();
TestNewShaderRecompilerCfgStraightLine();
TestNewShaderRecompilerCfgIfElse();
TestNewShaderRecompilerCfgConsecutiveNativePhis();