#include "vulkan_marshal.h" #include #include #include namespace raven { size_t Marshal::capture_descriptor_size(VkObjectType type) const { VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT}; VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &descriptor}; const auto physical = bridge.devices.at(device_token).physical; vkGetPhysicalDeviceProperties2(bridge.physical.at(physical).handle, &properties); switch (type) { case VK_OBJECT_TYPE_BUFFER: return descriptor.bufferCaptureReplayDescriptorDataSize; case VK_OBJECT_TYPE_IMAGE: return descriptor.imageCaptureReplayDescriptorDataSize; case VK_OBJECT_TYPE_IMAGE_VIEW: return descriptor.imageViewCaptureReplayDescriptorDataSize; case VK_OBJECT_TYPE_SAMPLER: return descriptor.samplerCaptureReplayDescriptorDataSize; case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR: return descriptor.accelerationStructureCaptureReplayDescriptorDataSize; default: throw std::runtime_error("invalid Vulkan capture descriptor object type"); } } void Marshal::context(H type, uint64_t token) { switch (type) { case H::VkDevice: { const auto found = bridge.devices.find(token); if (found == bridge.devices.end()) throw std::runtime_error("invalid Vulkan device handle"); device_token = token; host_device = found->second.handle; break; } case H::VkQueue: { const auto found = bridge.queues.find(token); if (found == bridge.queues.end()) throw std::runtime_error("invalid Vulkan queue handle"); context(H::VkDevice, found->second.device); break; } case H::VkCommandBuffer: { const auto found = bridge.command_buffers.find(token); if (found == bridge.command_buffers.end()) throw std::runtime_error("invalid Vulkan command buffer handle"); context(H::VkDevice, found->second.device); break; } case H::VkInstance: { const auto found = bridge.instances.find(token); if (found == bridge.instances.end()) throw std::runtime_error("invalid Vulkan instance handle"); host_instance = found->second; break; } case H::VkPhysicalDevice: { const auto found = bridge.physical.find(token); if (found == bridge.physical.end()) throw std::runtime_error("invalid Vulkan physical device handle"); context(H::VkInstance, found->second.instance); break; } default: break; } } std::optional Marshal::kind_of(H type) { using Kind = VulkanBridge::Kind; switch (type) { case H::VkShaderModule: return Kind::shader; case H::VkDescriptorSetLayout: return Kind::set_layout; case H::VkPipelineLayout: return Kind::pipeline_layout; case H::VkPipeline: return Kind::pipeline; case H::VkDescriptorPool: return Kind::pool; case H::VkDescriptorSet: return Kind::set; case H::VkImage: return Kind::image; case H::VkImageView: return Kind::image_view; case H::VkSemaphore: return Kind::semaphore; case H::VkRenderPass: return Kind::render_pass; case H::VkFramebuffer: return Kind::framebuffer; case H::VkSampler: return Kind::sampler; default: return std::nullopt; } } uint64_t Marshal::host(H type, uint64_t token) const { if (!token) return 0; const auto fail = [&]() -> uint64_t { throw std::runtime_error("invalid Vulkan handle for type " + std::to_string(static_cast(type))); }; switch (type) { case H::VkInstance: { const auto f = bridge.instances.find(token); return f == bridge.instances.end() ? fail() : reinterpret_cast(f->second); } case H::VkPhysicalDevice: { const auto f = bridge.physical.find(token); return f == bridge.physical.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkDevice: { const auto f = bridge.devices.find(token); return f == bridge.devices.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkQueue: { const auto f = bridge.queues.find(token); return f == bridge.queues.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkCommandBuffer: { const auto f = bridge.command_buffers.find(token); return f == bridge.command_buffers.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkCommandPool: { const auto f = bridge.pools.find(token); return f == bridge.pools.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkBuffer: { const auto f = bridge.buffers.find(token); return f == bridge.buffers.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkDeviceMemory: { const auto f = bridge.memories.find(token); return f == bridge.memories.end() ? fail() : reinterpret_cast(f->second.handle); } case H::VkFence: { const auto f = bridge.fences.find(token); return f == bridge.fences.end() ? fail() : reinterpret_cast(f->second.handle); } default: break; } if (kind_of(type)) { const auto f = bridge.objects.find(token); if (f == bridge.objects.end() || f->second.kind != *kind_of(type)) return fail(); return f->second.handle; } const auto f = bridge.generic.find(token); if (f == bridge.generic.end() || f->second.type != static_cast(type)) return fail(); return f->second.host; } uint64_t Marshal::new_token(H type, uint64_t host_handle) { if (!host_handle) return 0; const auto token_value = bridge.next_handle++; if (type == H::VkQueue) { bridge.queues.emplace(token_value, VulkanBridge::Queue{reinterpret_cast(host_handle), device_token, 0, 0}); } else if (type == H::VkBuffer) { bridge.buffers.emplace(token_value, VulkanBridge::Buffer{reinterpret_cast(host_handle), device_token}); } else if (type == H::VkCommandPool) { bridge.pools.emplace(token_value, VulkanBridge::Pool{reinterpret_cast(host_handle), device_token}); } else if (type == H::VkFence) { bridge.fences.emplace(token_value, VulkanBridge::Fence{reinterpret_cast(host_handle), device_token}); } else if (const auto kind = kind_of(type)) { bridge.objects.emplace(token_value, VulkanBridge::Object{host_handle, device_token, *kind}); } else { bridge.generic.emplace(token_value, VulkanBridge::Generic{host_handle, static_cast(type), device_token}); } return token_value; } uint64_t Marshal::swapchain_image_token(H, uint64_t host_handle) { if (!host_handle) return 0; for (const auto &[key, object] : bridge.objects) if (object.handle == host_handle && object.kind == VulkanBridge::Kind::image && object.borrowed) return key; const auto token_value = bridge.next_handle++; bridge.objects.emplace(token_value, VulkanBridge::Object{host_handle, device_token, VulkanBridge::Kind::image, 0, true}); return token_value; } VkDescriptorImageInfo *Marshal::descriptor_image_infos(uint64_t guest, size_t count, VkDescriptorType type) { auto *result = make(count); const bool uses_sampler = type == VK_DESCRIPTOR_TYPE_SAMPLER || type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; const bool uses_view = type != VK_DESCRIPTOR_TYPE_SAMPLER; for (size_t index = 0; index < count; ++index) { auto info = load(guest + index * sizeof(VkDescriptorImageInfo)); try { info.sampler = uses_sampler ? reinterpret_cast(host(H::VkSampler, reinterpret_cast(info.sampler))) : VK_NULL_HANDLE; info.imageView = uses_view ? reinterpret_cast(host(H::VkImageView, reinterpret_cast(info.imageView))) : VK_NULL_HANDLE; } catch (const std::runtime_error &error) { throw std::runtime_error(std::string(error.what()) + " in image descriptor " + std::to_string(index) + " of type " + std::to_string(int(type)) + " (sampler " + std::to_string(reinterpret_cast(info.sampler)) + ", view " + std::to_string(reinterpret_cast(info.imageView)) + ")"); } result[index] = info; } return result; } uint64_t Marshal::token(H type, uint64_t host_handle) { if (!host_handle) return 0; if (type == H::VkQueue) { for (const auto &[key, queue] : bridge.queues) if (reinterpret_cast(queue.handle) == host_handle) return key; } else if (type == H::VkBuffer || type == H::VkCommandPool || type == H::VkFence || type == H::VkDeviceMemory) { throw std::runtime_error("a handle of this type is never returned by the driver"); } else if (const auto kind = kind_of(type)) { for (const auto &[key, object] : bridge.objects) if (object.handle == host_handle && object.kind == *kind) return key; } else { for (const auto &[key, entry] : bridge.generic) if (entry.host == host_handle && entry.type == static_cast(type)) return key; } return new_token(type, host_handle); } void Marshal::forget(uint64_t token_value) { if (const auto surface = bridge.surface_windows.find(token_value); surface != bridge.surface_windows.end()) { const auto window = surface->second; bridge.surface_windows.erase(surface); // A program may make several surfaces over one window; they all render to the window's one host surface, which goes with the last. const bool shared = std::any_of(bridge.surface_windows.begin(), bridge.surface_windows.end(), [window](const auto &entry) { return entry.second == window; }); if (bridge.surface_source.release && !shared) bridge.surface_source.release(window); bridge.surface_instances.erase(token_value); bridge.foreign_surfaces.erase(token_value); } bridge.templates.erase(token_value); bridge.foreign_swapchains.erase(token_value); bridge.swapchains.erase(token_value); bridge.generic.erase(token_value); bridge.objects.erase(token_value); bridge.buffers.erase(token_value); bridge.fences.erase(token_value); bridge.pools.erase(token_value); } void Marshal::read_into(uint64_t address, void *target, size_t size) const { const auto bytes = process.read_memory(address, size); std::memcpy(target, bytes.data(), size); } void Marshal::write(uint64_t address, const void *source, size_t size) const { process.write_memory(address, std::span(static_cast(source), size)); } void Marshal::check_writable(uint64_t address, size_t size) const { process.write_memory(address, process.read_memory(address, size)); } std::string Marshal::read_string(uint64_t address) const { std::string result; for (size_t index = 0; index < 4096; ++index) { const auto byte = process.read_memory(address + index, 1)[0]; if (!byte) return result; result.push_back(static_cast(byte)); } throw std::runtime_error("invalid Vulkan guest string"); } const char *Marshal::string(const std::string &value) { auto *copy = make(value.size() + 1); std::memcpy(copy, value.c_str(), value.size() + 1); return copy; } const char *const *Marshal::string_array(uint64_t address, size_t count) { if (count > 4096) throw std::runtime_error("Vulkan string array is too long"); auto *result = make(count); for (size_t index = 0; index < count; ++index) result[index] = string(read_string(load(address + index * 8))); return result; } void *Marshal::copy_bytes(uint64_t address, size_t size) { if (size > (64U << 20)) throw std::runtime_error("Vulkan data block is too large"); auto *copy = make(size); if (size) read_into(address, copy, size); return copy; } void Marshal::require_null(uint64_t pointer) const { if (pointer) throw std::runtime_error("Vulkan guest allocation callbacks are not implemented"); } float Marshal::float_argument(const std::array ®isters, const std::vector &words, size_t index) const { const uint32_t bits = static_cast(index < 4 ? registers[index] : words[index]); float value; std::memcpy(&value, &bits, 4); return value; } double Marshal::double_argument(const std::array ®isters, const std::vector &words, size_t index) const { const uint64_t bits = index < 4 ? registers[index] : words[index]; double value; std::memcpy(&value, &bits, 8); return value; } }