diff --git a/.github/mac-bundle.sh b/.github/mac-bundle.sh index c932c905..314b30f7 100755 --- a/.github/mac-bundle.sh +++ b/.github/mac-bundle.sh @@ -36,6 +36,15 @@ PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleIconFile string AppIcon PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool true" PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber" +PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleExecutable string Alber" +PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleDevelopmentRegion string en" +PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleInfoDictionaryVersion string 6.0" +PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundleName string Panda3DS" +PlistBuddy Alber.app/Contents/Info.plist -c "add CFBundlePackageType string APPL" +PlistBuddy Alber.app/Contents/Info.plist -c "add NSHumanReadableCopyright string Copyright 2023 Panda3DS Team" + +PlistBuddy Alber.app/Contents/Info.plist -c "add LSMinimumSystemVersion string 10.15" + # Bundle dylibs dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib diff --git a/CMakeLists.txt b/CMakeLists.txt index f68617c9..a32382a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ # We need to be able to use enable_language(OBJC) on Mac, so we need CMake 3.16 vs the 3.10 we use otherwise. Blame Apple. if (APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") cmake_minimum_required(VERSION 3.16) else() cmake_minimum_required(VERSION 3.10) @@ -147,9 +147,10 @@ set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp src/core/loader/lz77.cpp) set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp src/core/fs/archive_ext_save_data.cpp src/core/fs/archive_ncch.cpp src/core/fs/romfs.cpp - src/core/fs/ivfc.cpp + src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp ) +set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp @@ -178,7 +179,8 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/config.hpp include/services/ir_user.hpp include/http_server.hpp include/cheats.hpp include/action_replay.hpp include/renderer_sw/renderer_sw.hpp include/compiler_builtins.hpp include/fs/romfs.hpp include/fs/ivfc.hpp include/discord_rpc.hpp include/services/http.hpp include/result/result_cfg.hpp - include/math_util.hpp include/services/soc.hpp include/services/news_u.hpp + include/applets/applet.hpp include/applets/mii_selector.hpp include/math_util.hpp include/services/soc.hpp + include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp ) cmrc_add_resource_library( @@ -203,6 +205,7 @@ source_group("Source Files\\Core\\Filesystem" FILES ${FS_SOURCE_FILES}) source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES}) source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES}) source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES}) +source_group("Source Files\\Core\\Applets" FILES ${APPLET_SOURCE_FILES}) source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES}) source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_FILES}) source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) @@ -246,26 +249,54 @@ if(ENABLE_VULKAN) ) set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp - include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp + include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp + include/renderer_vk/vk_descriptor_heap.hpp + include/renderer_vk/vk_descriptor_update_batch.hpp + include/renderer_vk/vk_sampler_cache.hpp + include/renderer_vk/vk_memory.hpp include/renderer_vk/vk_pica.hpp ) set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp - src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp + src/core/renderer_vk/vk_api.cpp src/core/renderer_vk/vk_debug.cpp + src/core/renderer_vk/vk_descriptor_heap.cpp + src/core/renderer_vk/vk_descriptor_update_batch.cpp + src/core/renderer_vk/vk_sampler_cache.cpp + src/core/renderer_vk/vk_memory.cpp src/core/renderer_vk/vk_pica.cpp ) set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES}) source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES}) + + set(RENDERER_VK_HOST_SHADERS_SOURCE + "src/host_shaders/vulkan_display.frag" + "src/host_shaders/vulkan_display.vert" + ) + +foreach( HOST_SHADER_SOURCE ${RENDERER_VK_HOST_SHADERS_SOURCE} ) + get_filename_component( FILE_NAME ${HOST_SHADER_SOURCE} NAME ) + set( HOST_SHADER_SPIRV "${PROJECT_BINARY_DIR}/host_shaders/${FILE_NAME}.spv" ) + add_custom_command( + OUTPUT ${HOST_SHADER_SPIRV} + COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/host_shaders/" + COMMAND Vulkan::glslangValidator -t --target-env vulkan1.1 -g -V "${PROJECT_SOURCE_DIR}/${HOST_SHADER_SOURCE}" -o ${HOST_SHADER_SPIRV} + #COMMAND ${SPIRV_OPT} -O ${HOST_SHADER_SPIRV} -o ${HOST_SHADER_SPIRV} + DEPENDS ${HOST_SHADER_SOURCE} + ) + list( APPEND RENDERER_VK_HOST_SHADERS_SPIRV ${HOST_SHADER_SPIRV} ) +endforeach() + cmrc_add_resource_library( resources_renderer_vk NAMESPACE RendererVK - WHENCE "src/host_shaders/" + WHENCE "${PROJECT_BINARY_DIR}/host_shaders/" + ${RENDERER_VK_HOST_SHADERS_SPIRV} ) endif() source_group("Header Files\\Core" FILES ${HEADER_FILES}) set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} - ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) + ${APPLET_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) if(ENABLE_OPENGL) # Add the OpenGL source files to ALL_SOURCES diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp new file mode 100644 index 00000000..0c3ab519 --- /dev/null +++ b/include/applets/applet.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include "helpers.hpp" +#include "memory.hpp" +#include "result/result.hpp" + +namespace Applets { + namespace AppletIDs { + enum : u32 { + None = 0, + SysAppletMask = 0x100, + HomeMenu = 0x101, + AltMenu = 0x103, + Camera = 0x110, + Friends = 0x112, + GameNotes = 0x113, + Browser = 0x114, + InstructionManual = 0x115, + Notifications = 0x116, + Miiverse = 0x117, + MiiversePosting = 0x118, + AmiiboSettings = 0x119, + SysLibraryAppletMask = 0x200, + SoftwareKeyboard = 0x201, + MiiSelector = 0x202, + PNote = 0x204, // TODO: What dis? + SNote = 0x205, // What is this too? + ErrDisp = 0x206, + EshopMint = 0x207, + CirclePadProCalib = 0x208, + Notepad = 0x209, + Application = 0x300, + EshopTiger = 0x301, + LibraryAppletMask = 0x400, + SoftwareKeyboard2 = 0x401, + MiiSelector2 = 0x402, + Pnote2 = 0x404, + SNote2 = 0x405, + ErrDisp2 = 0x406, + EshopMint2 = 0x407, + CirclePadProCalib2 = 0x408, + Notepad2 = 0x409, + }; + } + + enum class APTSignal : u32 { + None = 0x0, + Wakeup = 0x1, + Request = 0x2, + Response = 0x3, + Exit = 0x4, + Message = 0x5, + HomeButtonSingle = 0x6, + HomeButtonDouble = 0x7, + DspSleep = 0x8, + DspWakeup = 0x9, + WakeupByExit = 0xA, + WakeupByPause = 0xB, + WakeupByCancel = 0xC, + WakeupByCancelAll = 0xD, + WakeupByPowerButtonClick = 0xE, + WakeupToJumpHome = 0xF, + RequestForSysApplet = 0x10, + WakeupToLaunchApplication = 0x11, + }; + + struct Parameter { + u32 senderID; + u32 destID; + APTSignal signal; + std::vector data; + }; + + class AppletBase { + Memory& mem; + + public: + virtual const char* name() = 0; + + // Called by APT::StartLibraryApplet and similar + virtual Result::HorizonResult start() = 0; + // Transfer parameters from application -> applet + virtual Result::HorizonResult receiveParameter() = 0; + virtual void reset() = 0; + + AppletBase(Memory& mem) : mem(mem) {} + }; +} // namespace Applets \ No newline at end of file diff --git a/include/applets/applet_manager.hpp b/include/applets/applet_manager.hpp new file mode 100644 index 00000000..95b54009 --- /dev/null +++ b/include/applets/applet_manager.hpp @@ -0,0 +1,17 @@ +#include "applets/mii_selector.hpp" +#include "applets/software_keyboard.hpp" +#include "helpers.hpp" +#include "memory.hpp" +#include "result/result.hpp" + +namespace Applets { + class AppletManager { + MiiSelectorApplet miiSelector; + SoftwareKeyboardApplet swkbd; + + public: + AppletManager(Memory& mem); + void reset(); + AppletBase* getApplet(u32 id); + }; +} // namespace Applets \ No newline at end of file diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp new file mode 100644 index 00000000..e40547fb --- /dev/null +++ b/include/applets/mii_selector.hpp @@ -0,0 +1,13 @@ +#include "applets/applet.hpp" + +namespace Applets { + class MiiSelectorApplet final : public AppletBase { + public: + virtual const char* name() override { return "Mii Selector"; } + virtual Result::HorizonResult start() override; + virtual Result::HorizonResult receiveParameter() override; + virtual void reset() override; + + MiiSelectorApplet(Memory& memory) : AppletBase(memory) {} + }; +} // namespace Applets \ No newline at end of file diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp new file mode 100644 index 00000000..1fb721a1 --- /dev/null +++ b/include/applets/software_keyboard.hpp @@ -0,0 +1,13 @@ +#include "applets/applet.hpp" + +namespace Applets { + class SoftwareKeyboardApplet final : public AppletBase { + public: + virtual const char* name() override { return "Software Keyboard"; } + virtual Result::HorizonResult start() override; + virtual Result::HorizonResult receiveParameter() override; + virtual void reset() override; + + SoftwareKeyboardApplet(Memory& memory) : AppletBase(memory) {} + }; +} // namespace Applets \ No newline at end of file diff --git a/include/fs/archive_base.hpp b/include/fs/archive_base.hpp index 0b0f65a1..c782f6a6 100644 --- a/include/fs/archive_base.hpp +++ b/include/fs/archive_base.hpp @@ -25,17 +25,22 @@ namespace PathType { } namespace ArchiveID { - enum : u32 { - SelfNCCH = 3, - SaveData = 4, - ExtSaveData = 6, - SharedExtSaveData = 7, - SystemSaveData = 8, - SDMC = 9, - SDMCWriteOnly = 0xA, + enum : u32 { + SelfNCCH = 3, + SaveData = 4, + ExtSaveData = 6, + SharedExtSaveData = 7, + SystemSaveData = 8, + SDMC = 9, + SDMCWriteOnly = 0xA, - SavedataAndNcch = 0x2345678A - }; + SavedataAndNcch = 0x2345678A, + // 3DBrew: This is the same as the regular SaveData archive, except with this the savedata ID and mediatype is loaded from the input archive + // lowpath. + UserSaveData1 = 0x567890B2, + // 3DBrew: Similar to 0x567890B2 but can only access Accessible Save specified in exheader? + UserSaveData2 = 0x567890B4, + }; static std::string toString(u32 id) { switch (id) { @@ -246,4 +251,11 @@ public: virtual std::optional readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) = 0; ArchiveBase(Memory& mem) : mem(mem) {} +}; + +struct ArchiveResource { + u32 sectorSize; // Size of a sector in bytes + u32 clusterSize; // Size of a cluster in bytes + u32 partitionCapacityInClusters; + u32 freeSpaceInClusters; }; \ No newline at end of file diff --git a/include/fs/archive_user_save_data.hpp b/include/fs/archive_user_save_data.hpp new file mode 100644 index 00000000..56e2c0f6 --- /dev/null +++ b/include/fs/archive_user_save_data.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "archive_base.hpp" + +class UserSaveDataArchive : public ArchiveBase { + u32 archiveID; + public: + UserSaveDataArchive(Memory& mem, u32 archiveID) : ArchiveBase(mem), archiveID(archiveID) {} + + u64 getFreeBytes() override { return 32_MB; } + std::string name() override { return "UserSaveData"; } + + HorizonResult createDirectory(const FSPath& path) override; + HorizonResult createFile(const FSPath& path, u64 size) override; + HorizonResult deleteFile(const FSPath& path) override; + + Rust::Result openArchive(const FSPath& path) override; + Rust::Result openDirectory(const FSPath& path) override; + FileDescriptor openFile(const FSPath& path, const FilePerms& perms) override; + std::optional readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override; + + void format(const FSPath& path, const FormatInfo& info) override; + Rust::Result getFormatInfo(const FSPath& path) override; + + std::filesystem::path getFormatInfoPath() { return IOFile::getAppData() / "FormatInfo" / "SaveData.format"; } + + // Returns whether the cart has save data or not + bool cartHasSaveData() { + auto cxi = mem.getCXI(); + return (cxi != nullptr && cxi->hasSaveData()); // We need to have a CXI file with more than 0 bytes of save data + } +}; \ No newline at end of file diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 59d8cdae..92007674 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -1,5 +1,12 @@ +#include +#include + +#include "math_util.hpp" #include "renderer.hpp" -#include "vulkan_api.hpp" +#include "vk_api.hpp" +#include "vk_descriptor_heap.hpp" +#include "vk_descriptor_update_batch.hpp" +#include "vk_sampler_cache.hpp" class GPU; @@ -10,7 +17,7 @@ class RendererVK final : public Renderer { vk::UniqueInstance instance = {}; vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; - vk::UniqueSurfaceKHR surface = {}; + vk::SurfaceKHR swapchainSurface = {}; vk::PhysicalDevice physicalDevice = {}; @@ -32,17 +39,74 @@ class RendererVK final : public Renderer { std::vector swapchainImages = {}; std::vector swapchainImageViews = {}; - // Per-swapchain-image data - // Each vector is `swapchainImageCount` in size - std::vector presentCommandBuffers = {}; + // This value is the degree of parallelism to allow multiple frames to be in-flight + // aka: "double-buffer"/"triple-buffering" + // Todo: make this a configuration option + static constexpr usize frameBufferingCount = 3; + + // Frame-buffering data + // Each vector is `frameBufferingCount` in size std::vector swapImageFreeSemaphore = {}; std::vector renderFinishedSemaphore = {}; std::vector frameFinishedFences = {}; + std::vector> frameFramebuffers = {}; + std::vector frameCommandBuffers = {}; + + const vk::CommandBuffer& getCurrentCommandBuffer() const { return frameCommandBuffers[frameBufferingIndex].get(); } + + // Todo: + // Use `{colourBuffer,depthBuffer}Loc` to maintain an std::map-cache of framebuffers + struct Texture { + u32 loc = 0; + u32 sizePerPixel = 0; + std::array size = {}; + + vk::Format format; + vk::UniqueImage image; + vk::UniqueDeviceMemory imageMemory; + vk::UniqueImageView imageView; + + Math::Rect getSubRect(u32 inputAddress, u32 width, u32 height) { + // PICA textures have top-left origin, same as Vulkan + const u32 startOffset = (inputAddress - loc) / sizePerPixel; + const u32 x0 = (startOffset % (size[0] * 8)) / 8; + const u32 y0 = (startOffset / (size[0] * 8)) * 8; + return Math::Rect{x0, y0, x0 + width, y0 + height}; + } + }; + // Hash(loc, size, format) -> Texture + std::map textureCache; + + Texture* findRenderTexture(u32 addr); + Texture& getColorRenderTexture(u32 addr, PICA::ColorFmt format, u32 width, u32 height); + Texture& getDepthRenderTexture(u32 addr, PICA::DepthFmt format, u32 width, u32 height); + + // Framebuffer for the top/bottom image + std::vector screenTexture = {}; + std::vector screenTextureViews = {}; + std::vector screenTextureFramebuffers = {}; + vk::UniqueDeviceMemory framebufferMemory = {}; + + std::map renderPassCache; + + vk::RenderPass getRenderPass(vk::Format colorFormat, std::optional depthFormat); + vk::RenderPass getRenderPass(PICA::ColorFmt colorFormat, std::optional depthFormat); + + std::unique_ptr descriptorUpdateBatch; + std::unique_ptr samplerCache; + + // Display pipeline data + std::unique_ptr displayDescriptorHeap; + vk::UniquePipeline displayPipeline; + vk::UniquePipelineLayout displayPipelineLayout; + std::vector topDisplayPipelineDescriptorSet; + std::vector bottomDisplayPipelineDescriptorSet; // Recreate the swapchain, possibly re-using the old one in the case of a resize vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent); - u64 currentFrame = 0; + u64 frameBufferingIndex = 0; + public: RendererVK(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs); ~RendererVK() override; diff --git a/include/renderer_vk/vulkan_api.hpp b/include/renderer_vk/vk_api.hpp similarity index 100% rename from include/renderer_vk/vulkan_api.hpp rename to include/renderer_vk/vk_api.hpp diff --git a/include/renderer_vk/vk_debug.hpp b/include/renderer_vk/vk_debug.hpp index afc367dc..ed712269 100644 --- a/include/renderer_vk/vk_debug.hpp +++ b/include/renderer_vk/vk_debug.hpp @@ -4,7 +4,7 @@ #include #include -#include "vulkan_api.hpp" +#include "vk_api.hpp" namespace Vulkan { diff --git a/include/renderer_vk/vk_descriptor_heap.hpp b/include/renderer_vk/vk_descriptor_heap.hpp new file mode 100644 index 00000000..8a9630e3 --- /dev/null +++ b/include/renderer_vk/vk_descriptor_heap.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + // Implements a basic heap of descriptor sets given a layout of particular + // bindings. Create a descriptor set by providing a list of bindings and it will + // automatically create both the pool, layout, and maintail a heap of descriptor + // sets. Descriptor sets will be reused and recycled. Assume that newly + // allocated descriptor sets are in an undefined state. + class DescriptorHeap { + private: + const vk::Device device; + + vk::UniqueDescriptorPool descriptorPool; + vk::UniqueDescriptorSetLayout descriptorSetLayout; + std::vector descriptorSets; + + std::vector bindings; + + std::vector allocationMap; + + explicit DescriptorHeap(vk::Device device); + + public: + ~DescriptorHeap() = default; + + DescriptorHeap(DescriptorHeap&&) = default; + + const vk::DescriptorPool& getDescriptorPool() const { return descriptorPool.get(); }; + + const vk::DescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout.get(); }; + + const std::span getDescriptorSets() const { return descriptorSets; }; + + std::span getBindings() const { return bindings; }; + + std::optional allocateDescriptorSet(); + bool freeDescriptorSet(vk::DescriptorSet set); + + static std::optional create( + vk::Device device, std::span bindings, u16 descriptorHeapCount = 1024 + ); + }; +} // namespace Vulkan \ No newline at end of file diff --git a/include/renderer_vk/vk_descriptor_update_batch.hpp b/include/renderer_vk/vk_descriptor_update_batch.hpp new file mode 100644 index 00000000..1a10214d --- /dev/null +++ b/include/renderer_vk/vk_descriptor_update_batch.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + // Implements a re-usable structure for batching up descriptor writes with a + // finite amount of space for both convenience and to reduce the overall amount + // of API calls to `vkUpdateDescriptorSets` + class DescriptorUpdateBatch { + private: + const vk::Device device; + + const usize descriptorWriteMax; + const usize descriptorCopyMax; + + using DescriptorInfoUnion = std::variant; + + // Todo: Maybe some kind of hash so that these structures can be re-used + // among descriptor writes. + std::unique_ptr descriptorInfos; + std::unique_ptr descriptorWrites; + std::unique_ptr descriptorCopies; + + usize descriptorWriteEnd = 0; + usize descriptorCopyEnd = 0; + + DescriptorUpdateBatch(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax) + : device(device), descriptorWriteMax(descriptorWriteMax), descriptorCopyMax(descriptorCopyMax) {} + + public: + ~DescriptorUpdateBatch() = default; + + DescriptorUpdateBatch(DescriptorUpdateBatch&&) = default; + + void flush(); + + void addImage( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral + ); + void addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler); + + void addImageSampler( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, + vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal + ); + void addBuffer( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size = VK_WHOLE_SIZE + ); + + void copyBinding( + vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement = 0, + u8 targetArrayElement = 0, u8 descriptorCount = 1 + ); + + static std::optional create(vk::Device device, usize descriptorWriteMax = 256, usize descriptorCopyMax = 256); + }; +} // namespace Vulkan \ No newline at end of file diff --git a/include/renderer_vk/vk_memory.hpp b/include/renderer_vk/vk_memory.hpp new file mode 100644 index 00000000..a84a5720 --- /dev/null +++ b/include/renderer_vk/vk_memory.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + + // Will try to find a memory type that is suitable for the given requirements. + // Returns -1 if no suitable memory type was found. + s32 findMemoryTypeIndex( + vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties, + vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected + ); + + // Given an array of valid Vulkan image-handles or buffer-handles, these + // functions will allocate a single block of device-memory for all of them + // and bind them consecutively. + // There may be a case that all the buffers or images cannot be allocated + // to the same device memory due to their required memory-type. + std::tuple commitImageHeap( + vk::Device device, vk::PhysicalDevice physicalDevice, const std::span images, + vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal, + vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected + ); + + std::tuple commitBufferHeap( + vk::Device device, vk::PhysicalDevice physicalDevice, const std::span buffers, + vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal, + vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected + ); + +} // namespace Vulkan \ No newline at end of file diff --git a/include/renderer_vk/vk_pica.hpp b/include/renderer_vk/vk_pica.hpp new file mode 100644 index 00000000..affd3aa8 --- /dev/null +++ b/include/renderer_vk/vk_pica.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "PICA/gpu.hpp" +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + + vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat); + vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat); + +} // namespace Vulkan \ No newline at end of file diff --git a/include/renderer_vk/vk_sampler_cache.hpp b/include/renderer_vk/vk_sampler_cache.hpp new file mode 100644 index 00000000..8cb27689 --- /dev/null +++ b/include/renderer_vk/vk_sampler_cache.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "helpers.hpp" +#include "vk_api.hpp" + +namespace Vulkan { + // Implements a simple pool of reusable sampler objects + class SamplerCache { + private: + const vk::Device device; + + std::unordered_map samplerMap; + + explicit SamplerCache(vk::Device device); + + public: + ~SamplerCache() = default; + + SamplerCache(SamplerCache&&) = default; + + const vk::Sampler& getSampler(const vk::SamplerCreateInfo& samplerInfo); + + static std::optional create(vk::Device device); + }; +} // namespace Vulkan \ No newline at end of file diff --git a/include/services/ac.hpp b/include/services/ac.hpp index 3d3c3216..55f46d3e 100644 --- a/include/services/ac.hpp +++ b/include/services/ac.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include "helpers.hpp" #include "kernel_types.hpp" #include "logger.hpp" @@ -15,10 +17,14 @@ class ACService { void closeAsync(u32 messagePointer); void createDefaultConfig(u32 messagePointer); void getLastErrorCode(u32 messagePointer); + void isConnected(u32 messagePointer); void registerDisconnectEvent(u32 messagePointer); void setClientVersion(u32 messagePointer); -public: + bool connected = false; + std::optional disconnectEvent = std::nullopt; + + public: ACService(Memory& mem) : mem(mem) {} void reset(); void handleSyncRequest(u32 messagePointer); diff --git a/include/services/apt.hpp b/include/services/apt.hpp index 56bf3083..a7df056f 100644 --- a/include/services/apt.hpp +++ b/include/services/apt.hpp @@ -6,6 +6,8 @@ #include "memory.hpp" #include "result/result.hpp" +#include "applets/applet_manager.hpp" + // Yay, more circular dependencies class Kernel; @@ -23,6 +25,7 @@ class APTService { std::optional resumeEvent = std::nullopt; ConsoleModel model = ConsoleModel::Old3DS; + Applets::AppletManager appletManager; MAKE_LOG_FUNCTION(log, aptLogger) @@ -33,17 +36,22 @@ class APTService { void checkNew3DS(u32 messagePointer); void checkNew3DSApp(u32 messagePointer); void enable(u32 messagePointer); + void getAppletInfo(u32 messagePointer); void getSharedFont(u32 messagePointer); void getWirelessRebootInfo(u32 messagePointer); void glanceParameter(u32 messagePointer); void initialize(u32 messagePointer); void inquireNotification(u32 messagePointer); + void isRegistered(u32 messagePointer); void notifyToWait(u32 messagePointer); void preloadLibraryApplet(u32 messagePointer); + void prepareToStartLibraryApplet(u32 messagePointer); void receiveParameter(u32 messagePointer); void replySleepQuery(u32 messagePointer); void setApplicationCpuTimeLimit(u32 messagePointer); void setScreencapPostPermission(u32 messagePointer); + void sendParameter(u32 messagePointer); + void startLibraryApplet(u32 messagePointer); void theSmashBrosFunction(u32 messagePointer); // Percentage of the syscore available to the application, between 5% and 89% @@ -67,7 +75,7 @@ class APTService { u32 screencapPostPermission; public: - APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} + APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel), appletManager(mem) {} void reset(); void handleSyncRequest(u32 messagePointer); }; \ No newline at end of file diff --git a/include/services/frd.hpp b/include/services/frd.hpp index 17e50bc4..65eb7041 100644 --- a/include/services/frd.hpp +++ b/include/services/frd.hpp @@ -23,7 +23,10 @@ class FRDService { // Service commands void attachToEventNotification(u32 messagePointer); + void getFriendAttributeFlags(u32 messagePointer); void getFriendKeyList(u32 messagePointer); + void getFriendPresence(u32 messagePointer); + void getFriendProfile(u32 messagePointer); void getMyFriendKey(u32 messagePointer); void getMyMii(u32 messagePointer); void getMyPresence(u32 messagePointer); @@ -35,6 +38,15 @@ class FRDService { void setNotificationMask(u32 messagePointer); void updateGameModeDescription(u32 messagePointer); + struct Profile { + u8 region; + u8 country; + u8 area; + u8 language; + u32 unknown; + }; + static_assert(sizeof(Profile) == 8); + public: FRDService(Memory& mem) : mem(mem) {} void reset(); diff --git a/include/services/fs.hpp b/include/services/fs.hpp index fb098c7d..775c129a 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -4,6 +4,7 @@ #include "fs/archive_save_data.hpp" #include "fs/archive_sdmc.hpp" #include "fs/archive_self_ncch.hpp" +#include "fs/archive_user_save_data.hpp" #include "helpers.hpp" #include "kernel_types.hpp" #include "logger.hpp" @@ -26,6 +27,10 @@ class FSService { SDMCArchive sdmc; NCCHArchive ncch; + // UserSaveData archives + UserSaveDataArchive userSaveData1; + UserSaveDataArchive userSaveData2; + ExtSaveDataArchive extSaveData_sdmc; ExtSaveDataArchive sharedExtSaveData_nand; @@ -36,6 +41,7 @@ class FSService { FSPath readPath(u32 type, u32 pointer, u32 size); // Service commands + void abnegateAccessRight(u32 messagePointer); void createDirectory(u32 messagePointer); void createExtSaveData(u32 messagePointer); void createFile(u32 messagePointer); @@ -45,9 +51,12 @@ class FSService { void deleteFile(u32 messagePointer); void formatSaveData(u32 messagePointer); void formatThisUserSaveData(u32 messagePointer); + void getArchiveResource(u32 messagePointer); void getFreeBytes(u32 messagePointer); void getFormatInfo(u32 messagePointer); void getPriority(u32 messagePointer); + void getThisSaveDataSecureValue(u32 messagePointer); + void theGameboyVCFunction(u32 messagePointer); void initialize(u32 messagePointer); void initializeWithSdkVersion(u32 messagePointer); void isSdmcDetected(u32 messagePointer); @@ -56,16 +65,17 @@ class FSService { void openDirectory(u32 messagePointer); void openFile(u32 messagePointer); void openFileDirectly(u32 messagePointer); + void setArchivePriority(u32 messagePointer); void setPriority(u32 messagePointer); + void setThisSaveDataSecureValue(u32 messagePointer); // Used for set/get priority: Not sure what sort of priority this is referring to u32 priority; public: - FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), - sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), - sdmc(mem), selfNcch(mem), ncch(mem), kernel(kernel) - {} + FSService(Memory& mem, Kernel& kernel) + : mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), selfNcch(mem), + ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index c9facffb..92ca36e2 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -63,7 +63,9 @@ class GPUService { // Service commands void acquireRight(u32 messagePointer); void flushDataCache(u32 messagePointer); + void importDisplayCaptureInfo(u32 messagePointer); void registerInterruptRelayQueue(u32 messagePointer); + void saveVramSysArea(u32 messagePointer); void setAxiConfigQoSMode(u32 messagePointer); void setBufferSwap(u32 messagePointer); void setInternalPriorities(u32 messagePointer); diff --git a/include/services/mic.hpp b/include/services/mic.hpp index e193db1c..f709c27f 100644 --- a/include/services/mic.hpp +++ b/include/services/mic.hpp @@ -5,13 +5,20 @@ #include "memory.hpp" #include "result/result.hpp" +// Circular dependencies, yay +class Kernel; + class MICService { Handle handle = KernelHandles::MIC; Memory& mem; + Kernel& kernel; MAKE_LOG_FUNCTION(log, micLogger) // Service commands + void getEventHandle(u32 messagePointer); void getGain(u32 messagePointer); + void getPower(u32 messagePointer); + void isSampling(u32 messagePointer); void mapSharedMem(u32 messagePointer); void setClamp(u32 messagePointer); void setGain(u32 messagePointer); @@ -19,15 +26,18 @@ class MICService { void setPower(u32 messagePointer); void startSampling(u32 messagePointer); void stopSampling(u32 messagePointer); + void unmapSharedMem(u32 messagePointer); void theCaptainToadFunction(u32 messagePointer); u8 gain = 0; // How loud our microphone input signal is bool micEnabled = false; bool shouldClamp = false; - bool isSampling = false; + bool currentlySampling = false; + + std::optional eventHandle; public: - MICService(Memory& mem) : mem(mem) {} + MICService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); }; \ No newline at end of file diff --git a/include/services/nfc.hpp b/include/services/nfc.hpp index ed4a8565..e65c42c1 100644 --- a/include/services/nfc.hpp +++ b/include/services/nfc.hpp @@ -44,7 +44,9 @@ class NFCService { void getTagInRangeEvent(u32 messagePointer); void getTagOutOfRangeEvent(u32 messagePointer); void getTagState(u32 messagePointer); + void shutdown(u32 messagePointer); void startCommunication(u32 messagePointer); + void startTagScanning(u32 messagePointer); void stopCommunication(u32 messagePointer); public: diff --git a/readme.md b/readme.md index a820dc3a..c96fe28c 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ # Panda3DS -[![Windows Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml) [![MacOS Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml) [![Linux Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml) +[![Windows Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml) [![MacOS Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml) [![Linux Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml) [![AUR Package](https://img.shields.io/aur/version/panda3ds-git)](https://aur.archlinux.org/packages/panda3ds-git) Panda3DS is an HLE, red-panda-themed Nintendo 3DS emulator written in C++ which started out as a fun project out of curiosity, but evolved into something that can sort of play games! @@ -39,7 +39,7 @@ The 3DS emulation scene is already pretty mature, with offerings such as [Citra] Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility # How to build -Panda3DS compiles on Windows, Linux and MacOS, with only 1 system dependency, the Vulkan SDK. However, if you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command +Panda3DS compiles on Windows, Linux and MacOS, with only 1 (optional) system dependency, the Vulkan SDK. If you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project. diff --git a/src/core/applets/applet.cpp b/src/core/applets/applet.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp new file mode 100644 index 00000000..c94eee28 --- /dev/null +++ b/src/core/applets/applet_manager.cpp @@ -0,0 +1,21 @@ +#include "applets/applet_manager.hpp" +using namespace Applets; + +AppletManager::AppletManager(Memory& mem) : miiSelector(mem), swkbd(mem) {} + +void AppletManager::reset() { + miiSelector.reset(); + swkbd.reset(); +} + +AppletBase* AppletManager::getApplet(u32 id) { + switch (id) { + case AppletIDs::MiiSelector: + case AppletIDs::MiiSelector2: return &miiSelector; + + case AppletIDs::SoftwareKeyboard: + case AppletIDs::SoftwareKeyboard2: return &swkbd; + + default: return nullptr; + } +} \ No newline at end of file diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp new file mode 100644 index 00000000..211e6f07 --- /dev/null +++ b/src/core/applets/mii_selector.cpp @@ -0,0 +1,11 @@ +#include "applets/mii_selector.hpp" + +using namespace Applets; + +void MiiSelectorApplet::reset() {} +Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; } + +Result::HorizonResult MiiSelectorApplet::receiveParameter() { + Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); + return Result::Success; +} \ No newline at end of file diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp new file mode 100644 index 00000000..2ff22792 --- /dev/null +++ b/src/core/applets/software_keyboard.cpp @@ -0,0 +1,11 @@ +#include "applets/software_keyboard.hpp" + +using namespace Applets; + +void SoftwareKeyboardApplet::reset() {} +Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; } + +Result::HorizonResult SoftwareKeyboardApplet::receiveParameter() { + Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); + return Result::Success; +} \ No newline at end of file diff --git a/src/core/fs/archive_user_save_data.cpp b/src/core/fs/archive_user_save_data.cpp new file mode 100644 index 00000000..cba9bff8 --- /dev/null +++ b/src/core/fs/archive_user_save_data.cpp @@ -0,0 +1,198 @@ +#include +#include + +#include "fs/archive_user_save_data.hpp" + +namespace fs = std::filesystem; + +HorizonResult UserSaveDataArchive::createFile(const FSPath& path, u64 size) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) Helpers::panic("Unsafe path in UserSaveData::CreateFile"); + + fs::path p = IOFile::getAppData() / "SaveData"; + p += fs::path(path.utf16_string).make_preferred(); + + if (fs::exists(p)) return Result::FS::AlreadyExists; + + IOFile file(p.string().c_str(), "wb"); + + // If the size is 0, leave the file empty and return success + if (size == 0) { + file.close(); + return Result::Success; + } + + // If it is not empty, seek to size - 1 and write a 0 to create a file of size "size" + else if (file.seek(size - 1, SEEK_SET) && file.writeBytes("", 1).second == 1) { + file.close(); + return Result::Success; + } + + file.close(); + return Result::FS::FileTooLarge; + } + + Helpers::panic("UserSaveDataArchive::OpenFile: Failed"); + return Result::Success; +} + +HorizonResult UserSaveDataArchive::createDirectory(const FSPath& path) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile"); + + fs::path p = IOFile::getAppData() / "SaveData"; + p += fs::path(path.utf16_string).make_preferred(); + + if (fs::is_directory(p)) return Result::FS::AlreadyExists; + if (fs::is_regular_file(p)) { + Helpers::panic("File path passed to UserSaveData::CreateDirectory"); + } + + bool success = fs::create_directory(p); + return success ? Result::Success : Result::FS::UnexpectedFileOrDir; + } else { + Helpers::panic("Unimplemented UserSaveData::CreateDirectory"); + } +} + +HorizonResult UserSaveDataArchive::deleteFile(const FSPath& path) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) Helpers::panic("Unsafe path in UserSaveData::DeleteFile"); + + fs::path p = IOFile::getAppData() / "SaveData"; + p += fs::path(path.utf16_string).make_preferred(); + + if (fs::is_directory(p)) { + Helpers::panic("UserSaveData::DeleteFile: Tried to delete directory"); + } + + if (!fs::is_regular_file(p)) { + return Result::FS::FileNotFoundAlt; + } + + std::error_code ec; + bool success = fs::remove(p, ec); + + // It might still be possible for fs::remove to fail, if there's eg an open handle to a file being deleted + // In this case, print a warning, but still return success for now + if (!success) { + Helpers::warn("UserSaveData::DeleteFile: fs::remove failed\n"); + } + + return Result::Success; + } + + Helpers::panic("UserSaveDataArchive::DeleteFile: Unknown path type"); + return Result::Success; +} + +FileDescriptor UserSaveDataArchive::openFile(const FSPath& path, const FilePerms& perms) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) Helpers::panic("Unsafe path in UserSaveData::OpenFile"); + + if (perms.raw == 0 || (perms.create() && !perms.write())) Helpers::panic("[UserSaveData] Unsupported flags for OpenFile"); + + fs::path p = IOFile::getAppData() / "SaveData"; + p += fs::path(path.utf16_string).make_preferred(); + + const char* permString = perms.write() ? "r+b" : "rb"; + + if (fs::exists(p)) { // Return file descriptor if the file exists + IOFile file(p.string().c_str(), permString); + return file.isOpen() ? file.getHandle() : FileError; + } else { + // If the file is not found, create it if the create flag is on + if (perms.create()) { + IOFile file(p.string().c_str(), "wb"); // Create file + file.close(); // Close it + + file.open(p.string().c_str(), permString); // Reopen with proper perms + return file.isOpen() ? file.getHandle() : FileError; + } else { + return FileError; + } + } + } + + Helpers::panic("UserSaveDataArchive::OpenFile: Failed"); + return FileError; +} + +Rust::Result UserSaveDataArchive::openDirectory(const FSPath& path) { + if (path.type == PathType::UTF16) { + if (!isPathSafe(path)) Helpers::panic("Unsafe path in UserSaveData::OpenDirectory"); + + fs::path p = IOFile::getAppData() / "SaveData"; + p += fs::path(path.utf16_string).make_preferred(); + + if (fs::is_regular_file(p)) { + printf("SaveData: OpenDirectory used with a file path"); + return Err(Result::FS::UnexpectedFileOrDir); + } + + if (fs::is_directory(p)) { + return Ok(DirectorySession(this, p)); + } else { + return Err(Result::FS::FileNotFoundAlt); + } + } + + Helpers::panic("UserSaveDataArchive::OpenDirectory: Unimplemented path type"); + return Err(Result::Success); +} + +Rust::Result UserSaveDataArchive::getFormatInfo(const FSPath& path) { + const fs::path formatInfoPath = getFormatInfoPath(); + IOFile file(formatInfoPath, "rb"); + + // If the file failed to open somehow, we return that the archive is not formatted + if (!file.isOpen()) { + return Err(Result::FS::NotFormatted); + } + + FormatInfo ret; + auto [success, bytesRead] = file.readBytes(&ret, sizeof(FormatInfo)); + file.close(); + + if (!success || bytesRead != sizeof(FormatInfo)) { + Helpers::warn("UserSaveData::GetFormatInfo: Format file exists but was not properly read into the FormatInfo struct"); + return Err(Result::FS::NotFormatted); + } + + return Ok(ret); +} + +void UserSaveDataArchive::format(const FSPath& path, const ArchiveBase::FormatInfo& info) { + const fs::path saveDataPath = IOFile::getAppData() / "SaveData"; + const fs::path formatInfoPath = getFormatInfoPath(); + + // Delete all contents by deleting the directory then recreating it + fs::remove_all(saveDataPath); + fs::create_directories(saveDataPath); + + // Write format info on disk + IOFile file(formatInfoPath, "wb"); + file.writeBytes(&info, sizeof(info)); + file.flush(); + file.close(); +} + +Rust::Result UserSaveDataArchive::openArchive(const FSPath& path) { + if (path.type != PathType::Binary) { + Helpers::panic("Unimplemented path type for UserSaveData archive: %d\n", path.type); + return Err(Result::FS::NotFoundInvalid); + } + + const fs::path formatInfoPath = getFormatInfoPath(); + // Format info not found so the archive is not formatted + if (!fs::is_regular_file(formatInfoPath)) { + return Err(Result::FS::NotFormatted); + } + + return Ok((ArchiveBase*)this); +} + +std::optional UserSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) { + Helpers::panic("Unimplemented UserSaveData::ReadFile"); + return 0; +} diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 4ec70412..52c46668 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,5 +1,6 @@ #include "renderer_vk/renderer_vk.hpp" +#include #include #include #include @@ -7,6 +8,203 @@ #include "SDL_vulkan.h" #include "helpers.hpp" #include "renderer_vk/vk_debug.hpp" +#include "renderer_vk/vk_memory.hpp" +#include "renderer_vk/vk_pica.hpp" + +CMRC_DECLARE(RendererVK); + +static vk::SamplerCreateInfo sampler2D(bool filtered = true, bool clamp = false) { + vk::SamplerCreateInfo samplerInfo = {}; + samplerInfo.magFilter = filtered ? vk::Filter::eLinear : vk::Filter::eNearest; + samplerInfo.minFilter = filtered ? vk::Filter::eLinear : vk::Filter::eNearest; + + samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear; + + samplerInfo.addressModeU = clamp ? vk::SamplerAddressMode::eClampToEdge : vk::SamplerAddressMode::eRepeat; + samplerInfo.addressModeV = clamp ? vk::SamplerAddressMode::eClampToEdge : vk::SamplerAddressMode::eRepeat; + samplerInfo.addressModeW = clamp ? vk::SamplerAddressMode::eClampToEdge : vk::SamplerAddressMode::eRepeat; + + samplerInfo.mipLodBias = 0.0f; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 16.0f; + + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = vk::CompareOp::eAlways; + + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = VK_LOD_CLAMP_NONE; + samplerInfo.borderColor = vk::BorderColor::eFloatTransparentBlack; + samplerInfo.unnormalizedCoordinates = VK_FALSE; + return samplerInfo; +} + +static vk::UniqueShaderModule createShaderModule(vk::Device device, std::span shaderCode) { + vk::ShaderModuleCreateInfo shaderModuleInfo = {}; + shaderModuleInfo.pCode = reinterpret_cast(shaderCode.data()); + shaderModuleInfo.codeSize = shaderCode.size(); + + vk::UniqueShaderModule shaderModule = {}; + if (auto createResult = device.createShaderModuleUnique(shaderModuleInfo); createResult.result == vk::Result::eSuccess) { + shaderModule = std::move(createResult.value); + } else { + Helpers::panic("Error creating shader module: %s\n", vk::to_string(createResult.result).c_str()); + } + return shaderModule; +} + +static inline vk::UniqueShaderModule createShaderModule(vk::Device device, cmrc::file shaderFile) { + return createShaderModule(device, std::span(reinterpret_cast(shaderFile.begin()), shaderFile.size())); +} + +std::tuple createGraphicsPipeline( + vk::Device device, std::span pushConstants, std::span setLayouts, + vk::ShaderModule vertModule, vk::ShaderModule fragModule, std::span vertexBindingDescriptions, + std::span vertexAttributeDescriptions, vk::RenderPass renderPass +) { + // Create Pipeline Layout + vk::PipelineLayoutCreateInfo graphicsPipelineLayoutInfo = {}; + + graphicsPipelineLayoutInfo.pSetLayouts = setLayouts.data(); + graphicsPipelineLayoutInfo.setLayoutCount = setLayouts.size(); + graphicsPipelineLayoutInfo.pPushConstantRanges = pushConstants.data(); + graphicsPipelineLayoutInfo.pushConstantRangeCount = pushConstants.size(); + + vk::UniquePipelineLayout graphicsPipelineLayout = {}; + if (auto createResult = device.createPipelineLayoutUnique(graphicsPipelineLayoutInfo); createResult.result == vk::Result::eSuccess) { + graphicsPipelineLayout = std::move(createResult.value); + } else { + Helpers::panic("Error creating pipeline layout: %s\n", vk::to_string(createResult.result).c_str()); + return {}; + } + + // Describe the stage and entry point of each shader + const vk::PipelineShaderStageCreateInfo ShaderStagesInfo[2] = { + vk::PipelineShaderStageCreateInfo( + {}, // Flags + vk::ShaderStageFlagBits::eVertex, // Shader Stage + vertModule, // Shader Module + "main", // Shader entry point function name + {} // Shader specialization info + ), + vk::PipelineShaderStageCreateInfo( + {}, // Flags + vk::ShaderStageFlagBits::eFragment, // Shader Stage + fragModule, // Shader Module + "main", // Shader entry point function name + {} // Shader specialization info + ), + }; + + vk::PipelineVertexInputStateCreateInfo vertexInputState = {}; + + vertexInputState.vertexBindingDescriptionCount = vertexBindingDescriptions.size(); + vertexInputState.pVertexBindingDescriptions = vertexBindingDescriptions.data(); + + vertexInputState.vertexAttributeDescriptionCount = vertexAttributeDescriptions.size(); + vertexInputState.pVertexAttributeDescriptions = vertexAttributeDescriptions.data(); + + vk::PipelineInputAssemblyStateCreateInfo inputAssemblyState = {}; + inputAssemblyState.topology = vk::PrimitiveTopology::eTriangleList; + inputAssemblyState.primitiveRestartEnable = false; + + vk::PipelineViewportStateCreateInfo viewportState = {}; + + static const vk::Viewport defaultViewport = {0, 0, 16, 16, 0.0f, 1.0f}; + static const vk::Rect2D defaultScissor = {{0, 0}, {16, 16}}; + viewportState.viewportCount = 1; + viewportState.pViewports = &defaultViewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &defaultScissor; + + vk::PipelineRasterizationStateCreateInfo rasterizationState = {}; + + rasterizationState.depthClampEnable = false; + rasterizationState.rasterizerDiscardEnable = false; + rasterizationState.polygonMode = vk::PolygonMode::eFill; + rasterizationState.cullMode = vk::CullModeFlagBits::eBack; + rasterizationState.frontFace = vk::FrontFace::eClockwise; + rasterizationState.depthBiasEnable = false; + rasterizationState.depthBiasConstantFactor = 0.0f; + rasterizationState.depthBiasClamp = 0.0f; + rasterizationState.depthBiasSlopeFactor = 0.0; + rasterizationState.lineWidth = 1.0f; + + vk::PipelineMultisampleStateCreateInfo multisampleState = {}; + + multisampleState.rasterizationSamples = vk::SampleCountFlagBits::e1; + multisampleState.sampleShadingEnable = false; + multisampleState.minSampleShading = 1.0f; + multisampleState.pSampleMask = nullptr; + multisampleState.alphaToCoverageEnable = true; + multisampleState.alphaToOneEnable = false; + + vk::PipelineDepthStencilStateCreateInfo depthStencilState = {}; + + depthStencilState.depthTestEnable = false; + depthStencilState.depthWriteEnable = false; + depthStencilState.depthCompareOp = vk::CompareOp::eLessOrEqual; + depthStencilState.depthBoundsTestEnable = false; + depthStencilState.stencilTestEnable = false; + depthStencilState.front = vk::StencilOp::eKeep; + depthStencilState.back = vk::StencilOp::eKeep; + depthStencilState.minDepthBounds = 0.0f; + depthStencilState.maxDepthBounds = 1.0f; + + vk::PipelineColorBlendStateCreateInfo colorBlendState = {}; + + colorBlendState.logicOpEnable = false; + colorBlendState.logicOp = vk::LogicOp::eClear; + colorBlendState.attachmentCount = 1; + + vk::PipelineColorBlendAttachmentState blendAttachmentState = {}; + + blendAttachmentState.blendEnable = false; + blendAttachmentState.srcColorBlendFactor = vk::BlendFactor::eZero; + blendAttachmentState.dstColorBlendFactor = vk::BlendFactor::eZero; + blendAttachmentState.colorBlendOp = vk::BlendOp::eAdd; + blendAttachmentState.srcAlphaBlendFactor = vk::BlendFactor::eZero; + blendAttachmentState.dstAlphaBlendFactor = vk::BlendFactor::eZero; + blendAttachmentState.alphaBlendOp = vk::BlendOp::eAdd; + blendAttachmentState.colorWriteMask = + vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + + colorBlendState.pAttachments = &blendAttachmentState; + + vk::PipelineDynamicStateCreateInfo dynamicState = {}; + static vk::DynamicState dynamicStates[] = {// The viewport and scissor of the framebuffer will be dynamic at + // run-time + vk::DynamicState::eViewport, vk::DynamicState::eScissor}; + dynamicState.dynamicStateCount = std::size(dynamicStates); + dynamicState.pDynamicStates = dynamicStates; + + vk::GraphicsPipelineCreateInfo renderPipelineInfo = {}; + + renderPipelineInfo.stageCount = 2; // Vert + Frag stages + renderPipelineInfo.pStages = ShaderStagesInfo; + renderPipelineInfo.pVertexInputState = &vertexInputState; + renderPipelineInfo.pInputAssemblyState = &inputAssemblyState; + renderPipelineInfo.pViewportState = &viewportState; + renderPipelineInfo.pRasterizationState = &rasterizationState; + renderPipelineInfo.pMultisampleState = &multisampleState; + renderPipelineInfo.pDepthStencilState = &depthStencilState; + renderPipelineInfo.pColorBlendState = &colorBlendState; + renderPipelineInfo.pDynamicState = &dynamicState; + renderPipelineInfo.subpass = 0; + renderPipelineInfo.renderPass = renderPass; + renderPipelineInfo.layout = graphicsPipelineLayout.get(); + + // Create Pipeline + vk::UniquePipeline pipeline = {}; + + if (auto createResult = device.createGraphicsPipelineUnique({}, renderPipelineInfo); createResult.result == vk::Result::eSuccess) { + pipeline = std::move(createResult.value); + } else { + Helpers::panic("Error creating graphics pipeline: %s\n", vk::to_string(createResult.result).c_str()); + return {}; + } + + return std::make_tuple(std::move(pipeline), std::move(graphicsPipelineLayout)); +} // Finds the first queue family that satisfies `queueMask` and excludes `queueExcludeMask` bits // Returns -1 if not found @@ -23,6 +221,274 @@ static s32 findQueueFamily( return -1; } +static u32 rotl32(u32 x, u32 n) { return (x << n) | (x >> (32 - n)); } +static u32 ror32(u32 x, u32 n) { return (x >> n) | (x << (32 - n)); } + +// Lower 32 bits is the format + size, upper 32-bits is the address +static u64 colorBufferHash(u32 loc, u32 size, PICA::ColorFmt format) { + return (static_cast(loc) << 32) | (ror32(size, 23) ^ static_cast(format)); +} +static u64 depthBufferHash(u32 loc, u32 size, PICA::DepthFmt format) { + return (static_cast(loc) << 32) | (ror32(size, 29) ^ static_cast(format)); +} + +RendererVK::Texture* RendererVK::findRenderTexture(u32 addr) { + // Find first render-texture hash that is >= to addr + auto match = textureCache.lower_bound(static_cast(addr) << 32); + + if (match == textureCache.end()) { + // Not found + return nullptr; + } + + Texture* texture = &match->second; + + const usize sizeInBytes = texture->size[0] * texture->size[1] * texture->sizePerPixel; + + // Ensure this address is within the span of the texture + if ((addr - match->second.loc) <= sizeInBytes) { + return texture; + } + + return nullptr; +} + +RendererVK::Texture& RendererVK::getColorRenderTexture(u32 addr, PICA::ColorFmt format, u32 width, u32 height) { + const u64 renderTextureHash = colorBufferHash(addr, width * height * PICA::sizePerPixel(format), format); + + // Cache hit + if (textureCache.contains(renderTextureHash)) { + return textureCache.at(renderTextureHash); + } + + // Cache miss + Texture& newTexture = textureCache[renderTextureHash]; + newTexture.loc = addr; + newTexture.sizePerPixel = PICA::sizePerPixel(format); + newTexture.size = {width, height}; + + newTexture.format = Vulkan::colorFormatToVulkan(format); + + vk::ImageCreateInfo textureInfo = {}; + textureInfo.setImageType(vk::ImageType::e2D); + textureInfo.setFormat(newTexture.format); + textureInfo.setExtent(vk::Extent3D(width, height, 1)); + textureInfo.setMipLevels(1); + textureInfo.setArrayLayers(1); + textureInfo.setSamples(vk::SampleCountFlagBits::e1); + textureInfo.setTiling(vk::ImageTiling::eOptimal); + textureInfo.setUsage( + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled + ); + textureInfo.setSharingMode(vk::SharingMode::eExclusive); + textureInfo.setInitialLayout(vk::ImageLayout::eUndefined); + + if (auto createResult = device->createImageUnique(textureInfo); createResult.result == vk::Result::eSuccess) { + newTexture.image = std::move(createResult.value); + } else { + Helpers::panic("Error creating color render-texture image: %s\n", vk::to_string(createResult.result).c_str()); + } + + Vulkan::setObjectName( + device.get(), newTexture.image.get(), "TextureCache:%08x %ux%u %s", addr, width, height, vk::to_string(textureInfo.format).c_str() + ); + + vk::ImageViewCreateInfo viewInfo = {}; + viewInfo.image = newTexture.image.get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = newTexture.format; + viewInfo.components = vk::ComponentMapping(); + viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); + + if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, {&newTexture.image.get(), 1}); + result == vk::Result::eSuccess) { + newTexture.imageMemory = std::move(imageMemory); + } else { + Helpers::panic("Error allocating color render-texture memory: %s\n", vk::to_string(result).c_str()); + } + + if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { + newTexture.imageView = std::move(createResult.value); + } else { + Helpers::panic("Error creating color render-texture: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Initial layout transition + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags{}, {}, {}, + {vk::ImageMemoryBarrier( + vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, newTexture.image.get(), viewInfo.subresourceRange + )} + ); + + return newTexture; +} + +RendererVK::Texture& RendererVK::getDepthRenderTexture(u32 addr, PICA::DepthFmt format, u32 width, u32 height) { + const u64 renderTextureHash = depthBufferHash(addr, width * height * PICA::sizePerPixel(format), format); + + // Cache hit + if (textureCache.contains(renderTextureHash)) { + return textureCache.at(renderTextureHash); + } + + // Cache miss + Texture& newTexture = textureCache[renderTextureHash]; + newTexture.loc = addr; + newTexture.sizePerPixel = PICA::sizePerPixel(format); + newTexture.size = {width, height}; + + newTexture.format = Vulkan::depthFormatToVulkan(format); + + vk::ImageCreateInfo textureInfo = {}; + textureInfo.setImageType(vk::ImageType::e2D); + textureInfo.setFormat(newTexture.format); + textureInfo.setExtent(vk::Extent3D(width, height, 1)); + textureInfo.setMipLevels(1); + textureInfo.setArrayLayers(1); + textureInfo.setSamples(vk::SampleCountFlagBits::e1); + textureInfo.setTiling(vk::ImageTiling::eOptimal); + textureInfo.setUsage( + vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled + ); + textureInfo.setSharingMode(vk::SharingMode::eExclusive); + textureInfo.setInitialLayout(vk::ImageLayout::eUndefined); + + if (auto createResult = device->createImageUnique(textureInfo); createResult.result == vk::Result::eSuccess) { + newTexture.image = std::move(createResult.value); + } else { + Helpers::panic("Error creating depth render-texture image: %s\n", vk::to_string(createResult.result).c_str()); + } + + Vulkan::setObjectName( + device.get(), newTexture.image.get(), "TextureCache:%08x %ux%u %s", addr, width, height, vk::to_string(textureInfo.format).c_str() + ); + + vk::ImageViewCreateInfo viewInfo = {}; + viewInfo.image = newTexture.image.get(); + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = newTexture.format; + viewInfo.components = vk::ComponentMapping(); + // viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1); + viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1); + + if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, {&newTexture.image.get(), 1}); + result == vk::Result::eSuccess) { + newTexture.imageMemory = std::move(imageMemory); + } else { + Helpers::panic("Error allocating depth render-texture memory: %s\n", vk::to_string(result).c_str()); + } + + if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { + newTexture.imageView = std::move(createResult.value); + } else { + Helpers::panic("Error creating depth render-texture: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Initial layout transition (depth and/or stencil) + if (vk::componentCount(newTexture.format) == 2) { + viewInfo.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; + } + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags{}, {}, {}, + {vk::ImageMemoryBarrier( + vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal, + VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, newTexture.image.get(), viewInfo.subresourceRange + )} + ); + + return newTexture; +} + +vk::RenderPass RendererVK::getRenderPass(vk::Format colorFormat, std::optional depthFormat) { + u64 renderPassHash = static_cast(colorFormat); + + if (depthFormat.has_value()) { + renderPassHash |= (static_cast(depthFormat.value()) << 32); + } + + // Cache hit + if (renderPassCache.contains(renderPassHash)) { + return renderPassCache.at(renderPassHash).get(); + } + + // Cache miss + vk::RenderPassCreateInfo renderPassInfo = {}; + vk::SubpassDescription subPass = {}; + + std::vector renderPassAttachments = {}; + + vk::AttachmentDescription colorAttachment = {}; + colorAttachment.format = colorFormat; + colorAttachment.samples = vk::SampleCountFlagBits::e1; + colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad; + colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; + colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eLoad; + colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eStore; + colorAttachment.initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + colorAttachment.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + renderPassAttachments.emplace_back(colorAttachment); + + if (depthFormat.has_value()) { + vk::AttachmentDescription depthAttachment = {}; + depthAttachment.format = depthFormat.value(); + depthAttachment.samples = vk::SampleCountFlagBits::e1; + depthAttachment.loadOp = vk::AttachmentLoadOp::eLoad; + depthAttachment.storeOp = vk::AttachmentStoreOp::eStore; + depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eLoad; + depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eStore; + depthAttachment.initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + depthAttachment.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + renderPassAttachments.emplace_back(depthAttachment); + } + + renderPassInfo.setAttachments(renderPassAttachments); + + static const vk::AttachmentReference colorAttachmentReference = {0, vk::ImageLayout::eColorAttachmentOptimal}; + static const vk::AttachmentReference depthAttachmentReference = {1, vk::ImageLayout::eDepthStencilReadOnlyOptimal}; + + subPass.setColorAttachments(colorAttachmentReference); + if (depthFormat.has_value()) { + subPass.setPDepthStencilAttachment(&depthAttachmentReference); + } + + subPass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + + renderPassInfo.setSubpasses(subPass); + + // We only have one sub-pass and we want all render-passes to be sequential, + // so input/output depends on VK_SUBPASS_EXTERNAL + static const vk::SubpassDependency subpassDependencies[2] = { + vk::SubpassDependency( + VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion + ), + vk::SubpassDependency( + 0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eAllGraphics, vk::PipelineStageFlagBits::eAllGraphics, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::DependencyFlagBits::eByRegion + )}; + + renderPassInfo.setDependencies(subpassDependencies); + + if (auto createResult = device->createRenderPassUnique(renderPassInfo); createResult.result == vk::Result::eSuccess) { + return (renderPassCache[renderPassHash] = std::move(createResult.value)).get(); + } else { + Helpers::panic("Error creating render pass: %s\n", vk::to_string(createResult.result).c_str()); + } + return {}; +} + +vk::RenderPass RendererVK::getRenderPass(PICA::ColorFmt colorFormat, std::optional depthFormat) { + if (depthFormat.has_value()) { + return getRenderPass(Vulkan::colorFormatToVulkan(colorFormat), Vulkan::depthFormatToVulkan(depthFormat.value())); + } else { + return getRenderPass(Vulkan::colorFormatToVulkan(colorFormat), {}); + } +} + vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent) { static constexpr u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320 static constexpr u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall @@ -69,7 +535,7 @@ vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D sw // Fifo support is required by all vulkan implementations, waits for vsync vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; if (auto getResult = physicalDevice.getSurfacePresentModesKHR(surface); getResult.result == vk::Result::eSuccess) { - std::vector& presentModes = getResult.value; + const std::vector& presentModes = getResult.value; // Use mailbox if available, lowest-latency vsync-enabled mode if (std::find(presentModes.begin(), presentModes.end(), vk::PresentModeKHR::eMailbox) != presentModes.end()) { @@ -155,48 +621,6 @@ vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D sw Helpers::panic("Error creating acquiring swapchain images: %s\n", vk::to_string(getResult.result).c_str()); } - // Swapchain Command buffer(s) - vk::CommandBufferAllocateInfo commandBuffersInfo = {}; - commandBuffersInfo.commandPool = commandPool.get(); - commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; - commandBuffersInfo.commandBufferCount = swapchainImageCount; - - if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { - presentCommandBuffers = std::move(allocateResult.value); - } else { - Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str()); - } - - // Swapchain synchronization primitives - vk::FenceCreateInfo fenceInfo = {}; - fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; - - vk::SemaphoreCreateInfo semaphoreInfo = {}; - - swapImageFreeSemaphore.resize(swapchainImageCount); - renderFinishedSemaphore.resize(swapchainImageCount); - frameFinishedFences.resize(swapchainImageCount); - - for (usize i = 0; i < swapchainImageCount; i++) { - if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { - swapImageFreeSemaphore[i] = std::move(createResult.value); - } else { - Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); - } - - if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { - renderFinishedSemaphore[i] = std::move(createResult.value); - } else { - Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); - } - - if (auto createResult = device->createFenceUnique(fenceInfo); createResult.result == vk::Result::eSuccess) { - frameFinishedFences[i] = std::move(createResult.value); - } else { - Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); - } - } - return vk::Result::eSuccess; } @@ -205,136 +629,260 @@ RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs, co RendererVK::~RendererVK() {} -void RendererVK::reset() {} +void RendererVK::reset() { renderPassCache.clear(); } void RendererVK::display() { - // Block, on the CPU, to ensure that this swapchain-frame is ready for more work - if (auto waitResult = device->waitForFences({frameFinishedFences[currentFrame].get()}, true, std::numeric_limits::max()); - waitResult != vk::Result::eSuccess) { - Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str()); - } - - u32 swapchainImageIndex = std::numeric_limits::max(); - if (const auto acquireResult = - device->acquireNextImageKHR(swapchain.get(), std::numeric_limits::max(), swapImageFreeSemaphore[currentFrame].get(), {}); - acquireResult.result == vk::Result::eSuccess) { - swapchainImageIndex = acquireResult.value; - } else { - switch (acquireResult.result) { - case vk::Result::eSuboptimalKHR: - case vk::Result::eErrorOutOfDateKHR: { - // Surface resized - vk::Extent2D swapchainExtent; - { - int windowWidth, windowHeight; - // Block until we have a valid surface-area to present to - // Usually this is because the window has been minimized - // Todo: We should still be rendering even without a valid swapchain - do { - SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); - } while (!windowWidth || !windowHeight); - swapchainExtent.width = windowWidth; - swapchainExtent.height = windowHeight; + // Get the next available swapchain image, and signal the semaphore when it's ready + static constexpr u32 swapchainImageInvalid = std::numeric_limits::max(); + u32 swapchainImageIndex = swapchainImageInvalid; + if (swapchain) { + if (const auto acquireResult = + device->acquireNextImageKHR(swapchain.get(), std::numeric_limits::max(), swapImageFreeSemaphore[frameBufferingIndex].get(), {}); + acquireResult.result == vk::Result::eSuccess) { + swapchainImageIndex = acquireResult.value; + } else { + switch (acquireResult.result) { + case vk::Result::eSuboptimalKHR: + case vk::Result::eErrorOutOfDateKHR: { + // Surface resized + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + // Block until we have a valid surface-area to present to + // Usually this is because the window has been minimized + // Todo: We should still be rendering even without a valid swapchain + do { + SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); + } while (!windowWidth || !windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + recreateSwapchain(swapchainSurface, swapchainExtent); + break; + } + default: { + Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str()); } - recreateSwapchain(surface.get(), swapchainExtent); - break; - } - default: { - Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str()); } } } - vk::UniqueCommandBuffer& presentCommandBuffer = presentCommandBuffers.at(currentFrame); + const bool topActiveFb = externalRegs[PICA::ExternalRegs::Framebuffer0Select] & 1; + const u32 topScreenAddr = externalRegs[topActiveFb ? PICA::ExternalRegs::Framebuffer0AFirstAddr : PICA::ExternalRegs::Framebuffer0ASecondAddr]; - vk::CommandBufferBeginInfo beginInfo = {}; - beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; + const bool bottomActiveFb = externalRegs[PICA::ExternalRegs::Framebuffer1Select] & 1; + const u32 bottomScreenAddr = + externalRegs[bottomActiveFb ? PICA::ExternalRegs::Framebuffer1AFirstAddr : PICA::ExternalRegs::Framebuffer1ASecondAddr]; - if (const vk::Result beginResult = presentCommandBuffer->begin(beginInfo); beginResult != vk::Result::eSuccess) { - Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); + //// Render Display + { + static const std::array renderScreenScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}}; + + Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), renderScreenScopeColor, "Render Screen"); + + vk::RenderPassBeginInfo renderPassBeginInfo = {}; + renderPassBeginInfo.renderPass = getRenderPass(vk::Format::eR8G8B8A8Unorm, {}); + + renderPassBeginInfo.framebuffer = screenTextureFramebuffers[frameBufferingIndex].get(); + renderPassBeginInfo.renderArea.offset = vk::Offset2D(); + renderPassBeginInfo.renderArea.extent = vk::Extent2D(400, 240 * 2); + + getCurrentCommandBuffer().beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline); + + const Texture* topScreen = findRenderTexture(topScreenAddr); + const Texture* bottomScreen = findRenderTexture(bottomScreenAddr); + + if (topScreen || bottomScreen) { + getCurrentCommandBuffer().bindPipeline(vk::PipelineBindPoint::eGraphics, displayPipeline.get()); + + // Update descriptors before binding to the command buffer + if (topScreen) { + descriptorUpdateBatch->addImageSampler( + topDisplayPipelineDescriptorSet[frameBufferingIndex], 0, topScreen->imageView.get(), samplerCache->getSampler(sampler2D()) + ); + } + + if (bottomScreen) { + descriptorUpdateBatch->addImageSampler( + bottomDisplayPipelineDescriptorSet[frameBufferingIndex], 0, bottomScreen->imageView.get(), samplerCache->getSampler(sampler2D()) + ); + } + descriptorUpdateBatch->flush(); + + // Render top screen + if (topScreen) { + static const std::array scopeColor = {{1.0f, 0.0f, 0.0f, 1.0f}}; + Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), scopeColor, "Top Screen: %08x", topScreenAddr); + + getCurrentCommandBuffer().bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, displayPipelineLayout.get(), 0, {topDisplayPipelineDescriptorSet[frameBufferingIndex]}, {} + ); + getCurrentCommandBuffer().setViewport(0, vk::Viewport(0, 0, 400, 240)); + getCurrentCommandBuffer().setScissor(0, vk::Rect2D({0, 0}, {400, 240})); + getCurrentCommandBuffer().draw(3, 1, 0, 0); + } + + // Render bottom screen + if (bottomScreen) { + static const std::array scopeColor = {{0.0f, 1.0f, 0.0f, 1.0f}}; + Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), scopeColor, "Bottom Screen: %08x", bottomScreenAddr); + getCurrentCommandBuffer().bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, displayPipelineLayout.get(), 0, {bottomDisplayPipelineDescriptorSet[frameBufferingIndex]}, {} + ); + getCurrentCommandBuffer().bindPipeline(vk::PipelineBindPoint::eGraphics, displayPipeline.get()); + getCurrentCommandBuffer().setViewport(0, vk::Viewport(40, 240, 320, 240)); + getCurrentCommandBuffer().setScissor(0, vk::Rect2D({40, 240}, {320, 240})); + getCurrentCommandBuffer().draw(3, 1, 0, 0); + } + } + + getCurrentCommandBuffer().endRenderPass(); } - { - static const std::array presentScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}}; + //// Present + if (swapchainImageIndex != swapchainImageInvalid) { + static const std::array presentScopeColor = {{1.0f, 1.0f, 1.0f, 1.0f}}; + Vulkan::DebugLabelScope debugScope(getCurrentCommandBuffer(), presentScopeColor, "Present"); - Vulkan::DebugLabelScope debugScope(presentCommandBuffer.get(), presentScopeColor, "Present"); - - // Prepare for color-clear - presentCommandBuffer->pipelineBarrier( + // Prepare swapchain image for color-clear/blit-dst, prepare top/bottom screen for blit-src + getCurrentCommandBuffer().pipelineBarrier( vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, - {vk::ImageMemoryBarrier( - vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, - vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], - vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) - )} + { + // swapchainImage: Undefined -> TransferDst + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + // screenTexture: ShaderReadOnlyOptimal -> TransferSrc + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eTransferSrcOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, screenTexture[frameBufferingIndex].get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } ); - presentCommandBuffer->clearColorImage( - swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, presentScopeColor, + // Clear swapchain image with black + static const std::array clearColor = {{0.0f, 0.0f, 0.0f, 1.0f}}; + getCurrentCommandBuffer().clearColorImage( + swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, clearColor, vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ); - // Prepare for present - presentCommandBuffer->pipelineBarrier( - vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {}, - {vk::ImageMemoryBarrier( - vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, - vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], - vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) - )} + // Blit screentexture into swapchain image + static const vk::ImageBlit screenBlit( + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{}, vk::Offset3D{400, 240 * 2, 1}}, + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), {vk::Offset3D{}, vk::Offset3D{400, 240 * 2, 1}} + ); + getCurrentCommandBuffer().blitImage( + screenTexture[frameBufferingIndex].get(), vk::ImageLayout::eTransferSrcOptimal, swapchainImages[swapchainImageIndex], + vk::ImageLayout::eTransferDstOptimal, {screenBlit}, vk::Filter::eNearest + ); + + // Prepare swapchain image for present + // Transfer screenTexture back into ColorAttachmentOptimal + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlags(), {}, {}, + { + // swapchainImage: TransferDst -> Preset (wait for all writes) + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + // screenTexture: TransferSrc -> eShaderReadOnlyOptimal (wait for all reads) + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eTransferSrcOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, + screenTexture[frameBufferingIndex].get(), vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } ); } - if (const vk::Result endResult = presentCommandBuffer->end(); endResult != vk::Result::eSuccess) { + if (const vk::Result endResult = getCurrentCommandBuffer().end(); endResult != vk::Result::eSuccess) { Helpers::panic("Error ending command buffer recording: %s\n", vk::to_string(endResult).c_str()); } vk::SubmitInfo submitInfo = {}; // Wait for any previous uses of the image image to finish presenting - submitInfo.setWaitSemaphores(swapImageFreeSemaphore[currentFrame].get()); + std::vector waitSemaphores; + std::vector waitSemaphoreStages; + { + if (swapchainImageIndex != swapchainImageInvalid) { + waitSemaphores.emplace_back(swapImageFreeSemaphore[frameBufferingIndex].get()); + static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + waitSemaphoreStages.emplace_back(waitStageMask); + } + + submitInfo.setWaitSemaphores(waitSemaphores); + submitInfo.setWaitDstStageMask(waitSemaphoreStages); + } // Signal when finished - submitInfo.setSignalSemaphores(renderFinishedSemaphore[currentFrame].get()); + submitInfo.setSignalSemaphores(renderFinishedSemaphore[frameBufferingIndex].get()); - static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - submitInfo.setWaitDstStageMask(waitStageMask); + submitInfo.setCommandBuffers(getCurrentCommandBuffer()); - submitInfo.setCommandBuffers(presentCommandBuffer.get()); + device->resetFences({frameFinishedFences[frameBufferingIndex].get()}); - device->resetFences({frameFinishedFences[currentFrame].get()}); - - if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[currentFrame].get()); + if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[frameBufferingIndex].get()); submitResult != vk::Result::eSuccess) { Helpers::panic("Error submitting to graphics queue: %s\n", vk::to_string(submitResult).c_str()); } - vk::PresentInfoKHR presentInfo = {}; - presentInfo.setWaitSemaphores(renderFinishedSemaphore[currentFrame].get()); - presentInfo.setSwapchains(swapchain.get()); - presentInfo.setImageIndices(swapchainImageIndex); + if (swapchainImageIndex != swapchainImageInvalid) { + vk::PresentInfoKHR presentInfo = {}; + presentInfo.setWaitSemaphores(renderFinishedSemaphore[frameBufferingIndex].get()); + presentInfo.setSwapchains(swapchain.get()); + presentInfo.setImageIndices(swapchainImageIndex); - if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) { - } else { - switch (presentResult) { - case vk::Result::eSuboptimalKHR: - case vk::Result::eErrorOutOfDateKHR: { - // Surface resized - vk::Extent2D swapchainExtent; - { - int windowWidth, windowHeight; - SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); - swapchainExtent.width = windowWidth; - swapchainExtent.height = windowHeight; + if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) { + } else { + switch (presentResult) { + case vk::Result::eSuboptimalKHR: + case vk::Result::eErrorOutOfDateKHR: { + // Surface resized + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + recreateSwapchain(swapchainSurface, swapchainExtent); + break; + } + default: { + Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str()); } - recreateSwapchain(surface.get(), swapchainExtent); - break; - } - default: { - Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str()); } } } - currentFrame = ((currentFrame + 1) % swapchainImageCount); + // We are now working on the next frame + frameBufferingIndex = ((frameBufferingIndex + 1) % frameBufferingCount); + + // Wait for next frame to be ready + + // Block, on the CPU, to ensure that this buffered-frame is ready for more work + if (auto waitResult = device->waitForFences({frameFinishedFences[frameBufferingIndex].get()}, true, std::numeric_limits::max()); + waitResult != vk::Result::eSuccess) { + Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str()); + } + + { + frameFramebuffers[frameBufferingIndex].clear(); + + getCurrentCommandBuffer().reset(); + + vk::CommandBufferBeginInfo beginInfo = {}; + beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; + + if (const vk::Result beginResult = getCurrentCommandBuffer().begin(beginInfo); beginResult != vk::Result::eSuccess) { + Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); + } + } } void RendererVK::initGraphicsContext(SDL_Window* window) { @@ -357,19 +905,37 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { instanceInfo.pApplicationInfo = &applicationInfo; - std::vector instanceExtensions = { + std::unordered_set instanceExtensionsAvailable = {}; + if (const auto enumerateResult = vk::enumerateInstanceExtensionProperties(); enumerateResult.result == vk::Result::eSuccess) { + for (const auto& curExtension : enumerateResult.value) { + instanceExtensionsAvailable.emplace(curExtension.extensionName.data()); + } + } + + std::vector instanceExtensions = {}; + + if (instanceExtensionsAvailable.contains(VK_KHR_SURFACE_EXTENSION_NAME)) { + instanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); + } + + bool debugUtils = false; + if (instanceExtensionsAvailable.contains(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + instanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + debugUtils = true; + } + #if defined(__APPLE__) - VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + if (instanceExtensionsAvailable.contains(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { + instanceExtensions.emplace_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + } #endif - VK_EXT_DEBUG_UTILS_EXTENSION_NAME, - }; // Get any additional extensions that SDL wants as well - { + if (targetWindow) { unsigned int extensionCount = 0; - SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); + SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, nullptr); std::vector sdlInstanceExtensions(extensionCount); - SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, sdlInstanceExtensions.data()); + SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, sdlInstanceExtensions.data()); instanceExtensions.insert(instanceExtensions.end(), sdlInstanceExtensions.begin(), sdlInstanceExtensions.end()); } @@ -390,13 +956,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); // Enable debug messenger if the instance was able to be created with debug_utils - if (std::find( - instanceExtensions.begin(), instanceExtensions.end(), - // std::string_view has a way to compare itself to `const char*` - // so by casting it, we get the actual string comparisons - // and not pointer-comparisons - std::string_view(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) - ) != instanceExtensions.end()) { + if (debugUtils) { vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; debugCreateInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning; @@ -411,10 +971,12 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } // Create surface - if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { - surface.reset(newSurface); - } else { - Helpers::warn("Error creating Vulkan surface"); + if (window) { + if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { + swapchainSurface = newSurface; + } else { + Helpers::warn("Error creating Vulkan surface"); + } } // Pick physical device @@ -423,18 +985,20 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { std::vector::iterator partitionEnd = physicalDevices.end(); // Prefer GPUs that can access the surface - const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool { - const usize queueCount = physicalDevice.getQueueFamilyProperties().size(); - for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) { - if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, surface.get()); - supportResult.result == vk::Result::eSuccess) { - return supportResult.value; + if (swapchainSurface) { + const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool { + const usize queueCount = physicalDevice.getQueueFamilyProperties().size(); + for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) { + if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, swapchainSurface); + supportResult.result == vk::Result::eSuccess) { + return supportResult.value; + } } - } - return false; - }; + return false; + }; - partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport); + partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport); + } // Prefer Discrete GPUs const auto isDiscrete = [](const vk::PhysicalDevice& physicalDevice) -> bool { @@ -454,26 +1018,32 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { std::vector deviceQueueInfos; { const std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); - + std::unordered_set queueFamilyRequests; // Get present queue family - for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) { - if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface.get()); - supportResult.result == vk::Result::eSuccess) { - if (supportResult.value) { - presentQueueFamily = queueFamilyIndex; - break; + if (swapchainSurface) { + for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) { + if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, swapchainSurface); + supportResult.result == vk::Result::eSuccess) { + if (supportResult.value) { + presentQueueFamily = queueFamilyIndex; + break; + } } } + queueFamilyRequests.emplace(presentQueueFamily); } static const float queuePriority = 1.0f; graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics); + queueFamilyRequests.emplace(graphicsQueueFamily); computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute); + queueFamilyRequests.emplace(computeQueueFamily); transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer); + queueFamilyRequests.emplace(transferQueueFamily); // Requests a singular queue for each unique queue-family - const std::unordered_set queueFamilyRequests = {presentQueueFamily, graphicsQueueFamily, computeQueueFamily, transferQueueFamily}; + for (const u32 queueFamilyIndex : queueFamilyRequests) { deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, queueFamilyIndex, 1, &queuePriority)); } @@ -482,15 +1052,31 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { // Create Device vk::DeviceCreateInfo deviceInfo = {}; - static const char* deviceExtensions[] = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME, + // Device extensions + std::vector deviceExtensions = { #if defined(__APPLE__) "VK_KHR_portability_subset", #endif // VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME }; - deviceInfo.ppEnabledExtensionNames = deviceExtensions; - deviceInfo.enabledExtensionCount = std::size(deviceExtensions); + + std::unordered_set physicalDeviceExtensions; + if (const auto enumerateResult = physicalDevice.enumerateDeviceExtensionProperties(); enumerateResult.result == vk::Result::eSuccess) { + for (const auto& extension : enumerateResult.value) { + physicalDeviceExtensions.insert(extension.extensionName); + } + } else { + Helpers::panic("Error enumerating physical devices extensions: %s\n", vk::to_string(enumerateResult.result).c_str()); + } + + // Opertional extensions + + // Optionally enable the swapchain, to support "headless" rendering + if (physicalDeviceExtensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { + deviceExtensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + } + + deviceInfo.setPEnabledExtensionNames(deviceExtensions); vk::StructureChain deviceFeatureChain = {}; @@ -512,8 +1098,10 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); - presentQueue = device->getQueue(presentQueueFamily, 0); - graphicsQueue = device->getQueue(presentQueueFamily, 0); + if (presentQueueFamily != VK_QUEUE_FAMILY_IGNORED) { + presentQueue = device->getQueue(presentQueueFamily, 0); + } + graphicsQueue = device->getQueue(graphicsQueueFamily, 0); computeQueue = device->getQueue(computeQueueFamily, 0); transferQueue = device->getQueue(transferQueueFamily, 0); @@ -528,22 +1116,468 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } // Create swapchain - vk::Extent2D swapchainExtent; - { - int windowWidth, windowHeight; - SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); - swapchainExtent.width = windowWidth; - swapchainExtent.height = windowHeight; + if (targetWindow && swapchainSurface) { + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + recreateSwapchain(swapchainSurface, swapchainExtent); } - recreateSwapchain(surface.get(), swapchainExtent); + + // Create frame-buffering data + // Frame-buffering Command buffer(s) + vk::CommandBufferAllocateInfo commandBuffersInfo = {}; + commandBuffersInfo.commandPool = commandPool.get(); + commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; + commandBuffersInfo.commandBufferCount = frameBufferingCount; + + if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { + frameCommandBuffers = std::move(allocateResult.value); + } else { + Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str()); + } + + // Initialize the first command buffer to be in the RECORDING state + vk::CommandBufferBeginInfo beginInfo = {}; + beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; + + if (const vk::Result beginResult = frameCommandBuffers[frameBufferingIndex]->begin(beginInfo); beginResult != vk::Result::eSuccess) { + Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); + } + + // Frame-buffering synchronization primitives + vk::FenceCreateInfo fenceInfo = {}; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + + vk::SemaphoreCreateInfo semaphoreInfo = {}; + + swapImageFreeSemaphore.resize(frameBufferingCount); + renderFinishedSemaphore.resize(frameBufferingCount); + frameFinishedFences.resize(frameBufferingCount); + frameFramebuffers.resize(frameBufferingCount); + frameCommandBuffers.resize(frameBufferingCount); + + vk::ImageCreateInfo screenTextureInfo = {}; + screenTextureInfo.setImageType(vk::ImageType::e2D); + screenTextureInfo.setFormat(vk::Format::eR8G8B8A8Unorm); + screenTextureInfo.setExtent(vk::Extent3D(400, 240 * 2, 1)); + screenTextureInfo.setMipLevels(1); + screenTextureInfo.setArrayLayers(1); + screenTextureInfo.setSamples(vk::SampleCountFlagBits::e1); + screenTextureInfo.setTiling(vk::ImageTiling::eOptimal); + screenTextureInfo.setUsage( + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment | vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst + ); + screenTextureInfo.setSharingMode(vk::SharingMode::eExclusive); + screenTextureInfo.setInitialLayout(vk::ImageLayout::eUndefined); + + screenTexture.resize(frameBufferingCount); + screenTextureViews.resize(frameBufferingCount); + screenTextureFramebuffers.resize(frameBufferingCount); + + for (usize i = 0; i < frameBufferingCount; ++i) { + if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { + swapImageFreeSemaphore[i] = std::move(createResult.value); + + Vulkan::setObjectName(device.get(), swapImageFreeSemaphore[i].get(), "swapImageFreeSemaphore#%zu", i); + } else { + Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + + if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { + renderFinishedSemaphore[i] = std::move(createResult.value); + + Vulkan::setObjectName(device.get(), renderFinishedSemaphore[i].get(), "renderFinishedSemaphore#%zu", i); + } else { + Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + + if (auto createResult = device->createFenceUnique(fenceInfo); createResult.result == vk::Result::eSuccess) { + frameFinishedFences[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating 'frame-finished' fence: %s\n", vk::to_string(createResult.result).c_str()); + } + + if (auto createResult = device->createImageUnique(screenTextureInfo); createResult.result == vk::Result::eSuccess) { + screenTexture[i] = std::move(createResult.value); + + Vulkan::setObjectName(device.get(), screenTexture[i].get(), "screenTexture#%zu", i); + } else { + Helpers::panic("Error creating top-screen image: %s\n", vk::to_string(createResult.result).c_str()); + } + } + + // Commit memory to all of our images + { + const auto getImage = [](const vk::UniqueImage& image) -> vk::Image { return image.get(); }; + std::vector images; + std::transform(screenTexture.begin(), screenTexture.end(), std::back_inserter(images), getImage); + + if (auto [result, imageMemory] = Vulkan::commitImageHeap(device.get(), physicalDevice, images); result == vk::Result::eSuccess) { + framebufferMemory = std::move(imageMemory); + } else { + Helpers::panic("Error allocating framebuffer memory: %s\n", vk::to_string(result).c_str()); + } + } + + // Memory is bounded, create views, framebuffer, and layout transitions for screentexture + vk::ImageViewCreateInfo screenTextureViewCreateInfo = {}; + screenTextureViewCreateInfo.viewType = vk::ImageViewType::e2D; + screenTextureViewCreateInfo.format = vk::Format::eR8G8B8A8Unorm; + screenTextureViewCreateInfo.components.r = vk::ComponentSwizzle::eR; + screenTextureViewCreateInfo.components.g = vk::ComponentSwizzle::eG; + screenTextureViewCreateInfo.components.b = vk::ComponentSwizzle::eB; + screenTextureViewCreateInfo.components.a = vk::ComponentSwizzle::eA; + screenTextureViewCreateInfo.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; + + for (usize i = 0; i < frameBufferingCount; ++i) { + screenTextureViewCreateInfo.image = screenTexture[i].get(); + + if (auto createResult = device->createImageViewUnique(screenTextureViewCreateInfo); createResult.result == vk::Result::eSuccess) { + screenTextureViews[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating screen texture view: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Initial layout transition + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags{}, {}, {}, + {vk::ImageMemoryBarrier( + vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, screenTexture[i].get(), + screenTextureViewCreateInfo.subresourceRange + )} + ); + + vk::FramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.setRenderPass(getRenderPass(vk::Format::eR8G8B8A8Unorm, {})); + framebufferInfo.setAttachments(screenTextureViews[i].get()); + framebufferInfo.setWidth(400); + framebufferInfo.setHeight(240 * 2); + framebufferInfo.setLayers(1); + if (auto createResult = device->createFramebufferUnique(framebufferInfo); createResult.result == vk::Result::eSuccess) { + screenTextureFramebuffers[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating screen-texture framebuffer: %s\n", vk::to_string(createResult.result).c_str()); + } + } + + static vk::DescriptorSetLayoutBinding displayShaderLayout[] = { + {// Just a singular texture slot + 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, + }; + + if (auto createResult = Vulkan::DescriptorUpdateBatch::create(device.get()); createResult.has_value()) { + descriptorUpdateBatch = std::make_unique(std::move(createResult.value())); + } else { + Helpers::panic("Error creating descriptor update batch\n"); + } + + if (auto createResult = Vulkan::SamplerCache::create(device.get()); createResult.has_value()) { + samplerCache = std::make_unique(std::move(createResult.value())); + } else { + Helpers::panic("Error creating sampler cache\n"); + } + + if (auto createResult = Vulkan::DescriptorHeap::create(device.get(), displayShaderLayout); createResult.has_value()) { + displayDescriptorHeap = std::make_unique(std::move(createResult.value())); + } else { + Helpers::panic("Error creating descriptor heap\n"); + } + + for (usize i = 0; i < frameBufferingCount; ++i) { + if (auto allocateResult = displayDescriptorHeap->allocateDescriptorSet(); allocateResult.has_value()) { + topDisplayPipelineDescriptorSet.emplace_back(allocateResult.value()); + } else { + Helpers::panic("Error creating descriptor set\n"); + } + if (auto allocateResult = displayDescriptorHeap->allocateDescriptorSet(); allocateResult.has_value()) { + bottomDisplayPipelineDescriptorSet.emplace_back(allocateResult.value()); + } else { + Helpers::panic("Error creating descriptor set\n"); + } + } + + auto vk_resources = cmrc::RendererVK::get_filesystem(); + auto displayVertexShader = vk_resources.open("vulkan_display.vert.spv"); + auto displayFragmentShader = vk_resources.open("vulkan_display.frag.spv"); + + vk::UniqueShaderModule displayVertexShaderModule = createShaderModule(device.get(), displayVertexShader); + vk::UniqueShaderModule displayFragmentShaderModule = createShaderModule(device.get(), displayFragmentShader); + + vk::RenderPass screenTextureRenderPass = getRenderPass(screenTextureInfo.format, {}); + + std::tie(displayPipeline, displayPipelineLayout) = createGraphicsPipeline( + device.get(), {}, {{displayDescriptorHeap.get()->getDescriptorSetLayout()}}, displayVertexShaderModule.get(), + displayFragmentShaderModule.get(), {}, {}, screenTextureRenderPass + ); } -void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} +void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { + const Texture* renderTexture = findRenderTexture(startAddress); -void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {} + if (!renderTexture) { + // not found + return; + } + + if (*vk::componentName(renderTexture->format, 0) != 'D') { + // Color-Clear + vk::ClearColorValue clearColor = {}; + + clearColor.float32[0] = Helpers::getBits<24, 8>(value) / 255.0f; // r + clearColor.float32[1] = Helpers::getBits<16, 8>(value) / 255.0f; // g + clearColor.float32[2] = Helpers::getBits<8, 8>(value) / 255.0f; // b + clearColor.float32[3] = Helpers::getBits<0, 8>(value) / 255.0f; // a + + Vulkan::DebugLabelScope scope( + getCurrentCommandBuffer(), clearColor.float32, "ClearBuffer start:%08X end:%08X value:%08X control:%08X\n", startAddress, endAddress, + value, control + ); + + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, + { + // renderTexture: ShaderReadOnlyOptimal -> TransferDst + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, renderTexture->image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } + ); + + // Clear RenderTarget + getCurrentCommandBuffer().clearColorImage( + renderTexture->image.get(), vk::ImageLayout::eTransferDstOptimal, clearColor, + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ); + + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlags(), {}, {}, + { + // renderTexture: TransferDst -> eShaderReadOnlyOptimal + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, renderTexture->image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } + ); + } else { + // Depth-Clear + vk::ClearDepthStencilValue clearDepthStencil = {}; + + if (vk::componentBits(renderTexture->format, 0) == 16) { + clearDepthStencil.depth = (value & 0xffff) / 65535.0f; + } else { + clearDepthStencil.depth = (value & 0xffffff) / 16777215.0f; + } + + clearDepthStencil.stencil = (value >> 24); // Stencil + + const std::array scopeColor = {{clearDepthStencil.depth, clearDepthStencil.depth, clearDepthStencil.depth, 1.0f}}; + Vulkan::DebugLabelScope scope( + getCurrentCommandBuffer(), scopeColor, "ClearBuffer start:%08X end:%08X value:%08X control:%08X\n", startAddress, endAddress, value, + control + ); + + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, + { + // renderTexture: ShaderReadOnlyOptimal -> TransferDst + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, renderTexture->image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1) + ), + } + ); + + static vk::ImageSubresourceRange depthStencilRanges[2] = { + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1)}; + + // Clear RenderTarget + getCurrentCommandBuffer().clearDepthStencilImage( + renderTexture->image.get(), vk::ImageLayout::eTransferDstOptimal, &clearDepthStencil, vk::componentCount(renderTexture->format), + depthStencilRanges + ); + + getCurrentCommandBuffer().pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlags(), {}, {}, + { + // renderTexture: TransferDst -> eShaderReadOnlyOptimal + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, renderTexture->image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1) + ), + } + ); + } +} + +// NOTE: The GPU format has RGB5551 and RGB655 swapped compared to internal regs format +static PICA::ColorFmt ToColorFmt(u32 format) { + switch (format) { + case 2: return PICA::ColorFmt::RGB565; + case 3: return PICA::ColorFmt::RGBA5551; + default: return static_cast(format); + } +} + +void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) { + const u32 inputWidth = inputSize & 0xffff; + const u32 inputHeight = inputSize >> 16; + const PICA::ColorFmt inputFormat = ToColorFmt(Helpers::getBits<8, 3>(flags)); + const PICA::ColorFmt outputFormat = ToColorFmt(Helpers::getBits<12, 3>(flags)); + const bool verticalFlip = flags & 1; + const PICA::Scaling scaling = static_cast(Helpers::getBits<24, 2>(flags)); + + u32 outputWidth = outputSize & 0xffff; + u32 outputHeight = outputSize >> 16; + + Texture& srcFramebuffer = getColorRenderTexture(inputAddr, inputFormat, inputWidth, inputHeight); + Math::Rect srcRect = srcFramebuffer.getSubRect(inputAddr, outputWidth, outputHeight); + + if (verticalFlip) { + std::swap(srcRect.bottom, srcRect.top); + } + + // Apply scaling for the destination rectangle. + if (scaling == PICA::Scaling::X || scaling == PICA::Scaling::XY) { + outputWidth >>= 1; + } + + if (scaling == PICA::Scaling::XY) { + outputHeight >>= 1; + } + + Texture& destFramebuffer = getColorRenderTexture(outputAddr, outputFormat, outputWidth, outputHeight); + Math::Rect destRect = destFramebuffer.getSubRect(outputAddr, outputWidth, outputHeight); + + if (inputWidth != outputWidth) { + // Helpers::warn("Strided display transfer is not handled correctly!\n"); + } + + const vk::ImageBlit blitRegion( + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), + {vk::Offset3D{(int)srcRect.left, (int)srcRect.top, 0}, vk::Offset3D{(int)srcRect.right, (int)srcRect.bottom, 1}}, + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), + {vk::Offset3D{(int)destRect.left, (int)destRect.top, 0}, vk::Offset3D{(int)destRect.right, (int)destRect.bottom, 1}} + ); + + const vk::CommandBuffer& blitCommandBuffer = getCurrentCommandBuffer(); + + static const std::array displayTransferColor = {{1.0f, 1.0f, 0.0f, 1.0f}}; + Vulkan::DebugLabelScope scope( + blitCommandBuffer, displayTransferColor, + "DisplayTransfer inputAddr 0x%08X outputAddr 0x%08X inputWidth %d outputWidth %d inputHeight %d outputHeight %d", inputAddr, outputAddr, + inputWidth, outputWidth, inputHeight, outputHeight + ); + + blitCommandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, + { + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eTransferSrcOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, srcFramebuffer.image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, destFramebuffer.image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } + ); + + blitCommandBuffer.blitImage( + srcFramebuffer.image.get(), vk::ImageLayout::eTransferSrcOptimal, destFramebuffer.image.get(), vk::ImageLayout::eTransferDstOptimal, + {blitRegion}, vk::Filter::eLinear + ); + + blitCommandBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllGraphics, vk::DependencyFlags(), {}, {}, + { + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferSrcOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, srcFramebuffer.image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + vk::ImageMemoryBarrier( + vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::eShaderReadOnlyOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, destFramebuffer.image.get(), + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ), + } + ); +} void RendererVK::textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) {} -void RendererVK::drawVertices(PICA::PrimType primType, std::span vertices) {} +void RendererVK::drawVertices(PICA::PrimType primType, std::span vertices) { + using namespace Helpers; + + const u32 depthControl = regs[PICA::InternalRegs::DepthAndColorMask]; + const bool depthTestEnable = depthControl & 1; + const bool depthWriteEnable = getBit<12>(depthControl); + const int depthFunc = getBits<4, 3>(depthControl); + const vk::ColorComponentFlags colorMask = vk::ColorComponentFlags(getBits<8, 4>(depthControl)); + + const vk::RenderPass curRenderPass = getRenderPass(colourBufferFormat, depthTestEnable ? std::make_optional(depthBufferFormat) : std::nullopt); + + // Create framebuffer, find a way to cache this! + vk::Framebuffer curFramebuffer = {}; + { + std::vector renderTargets; + + const auto& colorTexture = getColorRenderTexture(colourBufferLoc, colourBufferFormat, fbSize[0], fbSize[1]); + renderTargets.emplace_back(colorTexture.imageView.get()); + + if (depthTestEnable) { + const auto& depthTexture = getDepthRenderTexture(depthBufferLoc, depthBufferFormat, fbSize[0], fbSize[1]); + renderTargets.emplace_back(depthTexture.imageView.get()); + } + + vk::FramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.setRenderPass(curRenderPass); + framebufferInfo.setAttachments(renderTargets); + framebufferInfo.setWidth(fbSize[0]); + framebufferInfo.setHeight(fbSize[1]); + framebufferInfo.setLayers(1); + if (auto createResult = device->createFramebufferUnique(framebufferInfo); createResult.result == vk::Result::eSuccess) { + curFramebuffer = (frameFramebuffers[frameBufferingIndex].emplace_back(std::move(createResult.value))).get(); + } else { + Helpers::panic("Error creating render-texture framebuffer: %s\n", vk::to_string(createResult.result).c_str()); + } + } + + vk::RenderPassBeginInfo renderBeginInfo = {}; + renderBeginInfo.renderPass = curRenderPass; + static const vk::ClearValue ClearColors[] = { + vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 0.0f}), + vk::ClearDepthStencilValue(1.0f, 0), + vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 0.0f}), + }; + renderBeginInfo.pClearValues = ClearColors; + renderBeginInfo.clearValueCount = std::size(ClearColors); + renderBeginInfo.renderArea.extent.width = fbSize[0]; + renderBeginInfo.renderArea.extent.height = fbSize[1]; + renderBeginInfo.framebuffer = curFramebuffer; + + const vk::CommandBuffer& commandBuffer = getCurrentCommandBuffer(); + + // Todo: Rather than starting a new renderpass for each draw, do some state-tracking to re-use render-passes + commandBuffer.beginRenderPass(renderBeginInfo, vk::SubpassContents::eInline); + static const std::array labelColor = {{1.0f, 0.0f, 0.0f, 1.0f}}; + Vulkan::insertDebugLabel(commandBuffer, labelColor, "DrawVertices: %u vertices", vertices.size()); + commandBuffer.endRenderPass(); +} void RendererVK::screenshot(const std::string& name) {} diff --git a/src/core/renderer_vk/vk_api.cpp b/src/core/renderer_vk/vk_api.cpp new file mode 100644 index 00000000..4f879dc2 --- /dev/null +++ b/src/core/renderer_vk/vk_api.cpp @@ -0,0 +1,3 @@ +#include "renderer_vk/vk_api.hpp" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE; \ No newline at end of file diff --git a/src/core/renderer_vk/vk_descriptor_heap.cpp b/src/core/renderer_vk/vk_descriptor_heap.cpp new file mode 100644 index 00000000..ecf71d92 --- /dev/null +++ b/src/core/renderer_vk/vk_descriptor_heap.cpp @@ -0,0 +1,119 @@ +#include "renderer_vk/vk_descriptor_heap.hpp" + +#include +#include +#include + +namespace Vulkan { + + DescriptorHeap::DescriptorHeap(vk::Device device) : device(device) {} + + std::optional DescriptorHeap::allocateDescriptorSet() { + // Find a free slot + const auto freeSlot = std::find(allocationMap.begin(), allocationMap.end(), false); + + // If there is no free slot, return + if (freeSlot == allocationMap.end()) { + return std::nullopt; + } + + // Mark the slot as allocated + *freeSlot = true; + + const u16 index = static_cast(std::distance(allocationMap.begin(), freeSlot)); + + vk::UniqueDescriptorSet& newDescriptorSet = descriptorSets[index]; + + if (!newDescriptorSet) { + // Descriptor set doesn't exist yet. Allocate a new one + vk::DescriptorSetAllocateInfo allocateInfo = {}; + + allocateInfo.descriptorPool = descriptorPool.get(); + allocateInfo.pSetLayouts = &descriptorSetLayout.get(); + allocateInfo.descriptorSetCount = 1; + + if (auto AllocateResult = device.allocateDescriptorSetsUnique(allocateInfo); AllocateResult.result == vk::Result::eSuccess) { + newDescriptorSet = std::move(AllocateResult.value[0]); + } else { + // Error allocating descriptor set + return std::nullopt; + } + } + + return newDescriptorSet.get(); + } + + bool DescriptorHeap::freeDescriptorSet(vk::DescriptorSet Set) { + // Find the descriptor set + const auto found = + std::find_if(descriptorSets.begin(), descriptorSets.end(), [&Set](const auto& CurSet) -> bool { return CurSet.get() == Set; }); + + // If the descriptor set is not found, return + if (found == descriptorSets.end()) { + return false; + } + + // Mark the slot as free + const u16 index = static_cast(std::distance(descriptorSets.begin(), found)); + + allocationMap[index] = false; + + return true; + } + + std::optional DescriptorHeap::create( + vk::Device device, std::span bindings, u16 descriptorHeapCount + ) { + DescriptorHeap newDescriptorHeap(device); + + // Create a histogram of each of the descriptor types and how many of each + // the pool should have + // Todo: maybe keep this around as a hash table to do more dynamic + // allocations of descriptor sets rather than allocating them all up-front + std::vector poolSizes; + { + std::unordered_map descriptorTypeCounts; + + for (const auto& binding : bindings) { + descriptorTypeCounts[binding.descriptorType] += binding.descriptorCount; + } + for (const auto& descriptorTypeCount : descriptorTypeCounts) { + poolSizes.push_back(vk::DescriptorPoolSize(descriptorTypeCount.first, descriptorTypeCount.second * descriptorHeapCount)); + } + } + + // Create descriptor pool + { + vk::DescriptorPoolCreateInfo poolInfo; + poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet; + poolInfo.maxSets = descriptorHeapCount; + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.poolSizeCount = poolSizes.size(); + if (auto createResult = device.createDescriptorPoolUnique(poolInfo); createResult.result == vk::Result::eSuccess) { + newDescriptorHeap.descriptorPool = std::move(createResult.value); + } else { + return std::nullopt; + } + } + + // Create descriptor set layout + { + vk::DescriptorSetLayoutCreateInfo layoutInfo; + layoutInfo.pBindings = bindings.data(); + layoutInfo.bindingCount = bindings.size(); + + if (auto createResult = device.createDescriptorSetLayoutUnique(layoutInfo); createResult.result == vk::Result::eSuccess) { + newDescriptorHeap.descriptorSetLayout = std::move(createResult.value); + } else { + return std::nullopt; + } + } + + newDescriptorHeap.descriptorSets.resize(descriptorHeapCount); + newDescriptorHeap.allocationMap.resize(descriptorHeapCount); + + newDescriptorHeap.bindings.assign(bindings.begin(), bindings.end()); + + return {std::move(newDescriptorHeap)}; + } +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vk_descriptor_update_batch.cpp b/src/core/renderer_vk/vk_descriptor_update_batch.cpp new file mode 100644 index 00000000..a414ca2d --- /dev/null +++ b/src/core/renderer_vk/vk_descriptor_update_batch.cpp @@ -0,0 +1,98 @@ +#include "renderer_vk/vk_descriptor_update_batch.hpp" + +#include +#include + +namespace Vulkan { + + void DescriptorUpdateBatch::flush() { + device.updateDescriptorSets({std::span(descriptorWrites.get(), descriptorWriteEnd)}, {std::span(descriptorCopies.get(), descriptorCopyEnd)}); + + descriptorWriteEnd = 0; + descriptorCopyEnd = 0; + } + + void DescriptorUpdateBatch::addImage(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(vk::Sampler(), imageView, imageLayout); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampledImage, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(sampler, vk::ImageView(), vk::ImageLayout()); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampler, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addImageSampler( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, vk::ImageLayout imageLayout + ) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace(sampler, imageView, imageLayout); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::addBuffer( + vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size + ) { + if (descriptorWriteEnd >= descriptorWriteMax) { + flush(); + } + + const auto& bufferInfo = descriptorInfos[descriptorWriteEnd].emplace(buffer, offset, size); + + descriptorWrites[descriptorWriteEnd] = + vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eStorageImage, nullptr, &bufferInfo, nullptr); + + ++descriptorWriteEnd; + } + + void DescriptorUpdateBatch::copyBinding( + vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement, + u8 targetArrayElement, u8 descriptorCount + ) { + if (descriptorCopyEnd >= descriptorCopyMax) { + flush(); + } + + descriptorCopies[descriptorCopyEnd] = vk::CopyDescriptorSet( + sourceDescriptor, sourceBinding, sourceArrayElement, targetDescriptor, targetBinding, targetArrayElement, descriptorCount + ); + + ++descriptorCopyEnd; + } + + std::optional DescriptorUpdateBatch::create(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax) + + { + DescriptorUpdateBatch newDescriptorUpdateBatch(device, descriptorWriteMax, descriptorCopyMax); + + newDescriptorUpdateBatch.descriptorInfos = std::make_unique(descriptorWriteMax); + newDescriptorUpdateBatch.descriptorWrites = std::make_unique(descriptorWriteMax); + newDescriptorUpdateBatch.descriptorCopies = std::make_unique(descriptorCopyMax); + + return {std::move(newDescriptorUpdateBatch)}; + } + +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vk_memory.cpp b/src/core/renderer_vk/vk_memory.cpp new file mode 100644 index 00000000..c9087719 --- /dev/null +++ b/src/core/renderer_vk/vk_memory.cpp @@ -0,0 +1,174 @@ +#include "renderer_vk/vk_memory.hpp" + +namespace Vulkan { + + static constexpr vk::DeviceSize alignUp(vk::DeviceSize value, std::size_t size) { + const vk::DeviceSize mod = static_cast(value % size); + value -= mod; + return static_cast(mod == vk::DeviceSize{0} ? value : value + size); + } + + // Given a speculative heap-allocation, defined by its current size and + // memory-type bits, appends a memory requirements structure to it, updating + // both the size and the required memory-type-bits. Returns the offset within + // the heap for the current MemoryRequirements Todo: Sun Apr 23 13:28:25 PDT + // 2023 Rather than using a running-size of the heap, look at all of the memory + // requests and optimally create a packing for all of the offset and alignment + // requirements. Such as by satisfying all of the largest alignments first, and + // then the smallest, to reduce padding + static vk::DeviceSize commitMemoryRequestToHeap( + const vk::MemoryRequirements& curMemoryRequirements, vk::DeviceSize& curHeapEnd, u32& curMemoryTypeBits, vk::DeviceSize sizeAlignment + ) { + // Accumulate a mask of all the memory types that satisfies each of the + // handles + curMemoryTypeBits &= curMemoryRequirements.memoryTypeBits; + + // Pad up the memory sizes so they are not considered aliasing + const vk::DeviceSize curMemoryOffset = alignUp(curHeapEnd, curMemoryRequirements.alignment); + // Pad the size by the required size-alignment. + // Intended for BufferImageGranularity + const vk::DeviceSize curMemorySize = alignUp(curMemoryRequirements.size, sizeAlignment); + + curHeapEnd = (curMemoryOffset + curMemorySize); + return curMemoryOffset; + } + + s32 findMemoryTypeIndex( + vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties, + vk::MemoryPropertyFlags memoryExcludeProperties + ) { + const vk::PhysicalDeviceMemoryProperties deviceMemoryProperties = physicalDevice.getMemoryProperties(); + // Iterate the physical device's memory types until we find a match + for (std::size_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) { + if( + // Is within memory type mask + (((memoryTypeMask >> i) & 0b1) == 0b1) && + // Has property flags + (deviceMemoryProperties.memoryTypes[i].propertyFlags + & memoryProperties) + == memoryProperties + && + // None of the excluded properties are enabled + !(deviceMemoryProperties.memoryTypes[i].propertyFlags + & memoryExcludeProperties) ) + { + return static_cast(i); + } + } + + return -1; + } + + std::tuple commitImageHeap( + vk::Device device, vk::PhysicalDevice physicalDevice, const std::span images, vk::MemoryPropertyFlags memoryProperties, + vk::MemoryPropertyFlags memoryExcludeProperties + ) { + vk::MemoryAllocateInfo imageHeapAllocInfo = {}; + u32 imageHeapMemoryTypeBits = 0xFFFFFFFF; + std::vector imageHeapBinds; + + const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity; + + for (const vk::Image& curImage : images) { + const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap( + device.getImageMemoryRequirements(curImage), imageHeapAllocInfo.allocationSize, imageHeapMemoryTypeBits, bufferImageGranularity + ); + + if (imageHeapMemoryTypeBits == 0) { + // No possible memory heap for all of the images to share + return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory()); + } + + // Put nullptr for the device memory for now + imageHeapBinds.emplace_back(vk::BindImageMemoryInfo{curImage, nullptr, curBindOffset}); + } + + const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, imageHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties); + + if (memoryTypeIndex < 0) { + // Unable to find a memory heap that satisfies all the images + return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory()); + } + + imageHeapAllocInfo.memoryTypeIndex = memoryTypeIndex; + + vk::UniqueDeviceMemory imageHeapMemory = {}; + + if (auto allocResult = device.allocateMemoryUnique(imageHeapAllocInfo); allocResult.result == vk::Result::eSuccess) { + imageHeapMemory = std::move(allocResult.value); + } else { + return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory()); + } + + // Assign the device memory to the bindings + for (vk::BindImageMemoryInfo& curBind : imageHeapBinds) { + curBind.memory = imageHeapMemory.get(); + } + + // Now bind them all in one call + if (const vk::Result bindResult = device.bindImageMemory2(imageHeapBinds); bindResult == vk::Result::eSuccess) { + // Binding memory succeeded + } else { + return std::make_tuple(bindResult, vk::UniqueDeviceMemory()); + } + + return std::make_tuple(vk::Result::eSuccess, std::move(imageHeapMemory)); + } + + std::tuple commitBufferHeap( + vk::Device device, vk::PhysicalDevice physicalDevice, const std::span buffers, vk::MemoryPropertyFlags memoryProperties, + vk::MemoryPropertyFlags memoryExcludeProperties + ) { + vk::MemoryAllocateInfo bufferHeapAllocInfo = {}; + u32 bufferHeapMemoryTypeBits = 0xFFFFFFFF; + std::vector bufferHeapBinds; + + const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity; + + for (const vk::Buffer& curBuffer : buffers) { + const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap( + device.getBufferMemoryRequirements(curBuffer), bufferHeapAllocInfo.allocationSize, bufferHeapMemoryTypeBits, bufferImageGranularity + ); + + if (bufferHeapMemoryTypeBits == 0) { + // No possible memory heap for all of the buffers to share + return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory()); + } + + // Put nullptr for the device memory for now + bufferHeapBinds.emplace_back(vk::BindBufferMemoryInfo{curBuffer, nullptr, curBindOffset}); + } + + const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, bufferHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties); + + if (memoryTypeIndex < 0) { + // Unable to find a memory heap that satisfies all the buffers + return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory()); + } + + bufferHeapAllocInfo.memoryTypeIndex = memoryTypeIndex; + + vk::UniqueDeviceMemory bufferHeapMemory = {}; + + if (auto allocResult = device.allocateMemoryUnique(bufferHeapAllocInfo); allocResult.result == vk::Result::eSuccess) { + bufferHeapMemory = std::move(allocResult.value); + } else { + return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory()); + } + + // Assign the device memory to the bindings + for (vk::BindBufferMemoryInfo& curBind : bufferHeapBinds) { + curBind.memory = bufferHeapMemory.get(); + } + + // Now bind them all in one call + if (const vk::Result bindResult = device.bindBufferMemory2(bufferHeapBinds); bindResult == vk::Result::eSuccess) { + // Binding memory succeeded + } else { + return std::make_tuple(bindResult, vk::UniqueDeviceMemory()); + } + + return std::make_tuple(vk::Result::eSuccess, std::move(bufferHeapMemory)); + } + +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vk_pica.cpp b/src/core/renderer_vk/vk_pica.cpp new file mode 100644 index 00000000..e7fc9033 --- /dev/null +++ b/src/core/renderer_vk/vk_pica.cpp @@ -0,0 +1,39 @@ +#include "renderer_vk/vk_pica.hpp" + +namespace Vulkan { + + vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat) { + switch (colorFormat) { + case PICA::ColorFmt::RGBA8: return vk::Format::eR8G8B8A8Unorm; + // VK_FORMAT_R8G8B8A8_UNORM is mandated by the vulkan specification + // VK_FORMAT_R8G8B8_UNORM may not be supported + // TODO: Detect this! + // case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8Unorm; + case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8A8Unorm; + case PICA::ColorFmt::RGBA5551: return vk::Format::eR5G5B5A1UnormPack16; + case PICA::ColorFmt::RGB565: return vk::Format::eR5G6B5UnormPack16; + case PICA::ColorFmt::RGBA4: return vk::Format::eR4G4B4A4UnormPack16; + } + return vk::Format::eUndefined; + } + vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat) { + switch (depthFormat) { + // VK_FORMAT_D16_UNORM is mandated by the vulkan specification + case PICA::DepthFmt::Depth16: return vk::Format::eD16Unorm; + case PICA::DepthFmt::Unknown1: return vk::Format::eUndefined; + // The GPU may _not_ support these formats natively + // Only one of: + // VK_FORMAT_X8_D24_UNORM_PACK32 and VK_FORMAT_D32_SFLOAT + // and one of: + // VK_FORMAT_D24_UNORM_S8_UINT and VK_FORMAT_D32_SFLOAT_S8_UINT + // will be supported + // TODO: Detect this! + // case PICA::DepthFmt::Depth24: return vk::Format::eX8D24UnormPack32; + // case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD24UnormS8Uint; + case PICA::DepthFmt::Depth24: return vk::Format::eD32Sfloat; + case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD32SfloatS8Uint; + } + return vk::Format::eUndefined; + } + +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vk_sampler_cache.cpp b/src/core/renderer_vk/vk_sampler_cache.cpp new file mode 100644 index 00000000..884264b1 --- /dev/null +++ b/src/core/renderer_vk/vk_sampler_cache.cpp @@ -0,0 +1,31 @@ +#include "renderer_vk/vk_sampler_cache.hpp" + +#include + +#include "helpers.hpp" + +namespace Vulkan { + + SamplerCache::SamplerCache(vk::Device device) : device(device) {} + + const vk::Sampler& SamplerCache::getSampler(const vk::SamplerCreateInfo& samplerInfo) { + const std::size_t samplerHash = std::hash()(samplerInfo); + + // Cache hit + if (samplerMap.contains(samplerHash)) { + return samplerMap.at(samplerHash).get(); + } + + if (auto createResult = device.createSamplerUnique(samplerInfo); createResult.result == vk::Result::eSuccess) { + return (samplerMap[samplerHash] = std::move(createResult.value)).get(); + } else { + Helpers::panic("Error creating sampler: %s\n", vk::to_string(createResult.result).c_str()); + } + } + + std::optional SamplerCache::create(vk::Device device) { + SamplerCache newSamplerCache(device); + + return {std::move(newSamplerCache)}; + } +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vulkan_api.cpp b/src/core/renderer_vk/vulkan_api.cpp deleted file mode 100644 index c207eea7..00000000 --- a/src/core/renderer_vk/vulkan_api.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "renderer_vk/vulkan_api.hpp" - -VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE; \ No newline at end of file diff --git a/src/core/services/ac.cpp b/src/core/services/ac.cpp index 56326609..25efdce6 100644 --- a/src/core/services/ac.cpp +++ b/src/core/services/ac.cpp @@ -8,11 +8,15 @@ namespace ACCommands { CloseAsync = 0x00080004, GetLastErrorCode = 0x000A0000, RegisterDisconnectEvent = 0x00300004, + IsConnected = 0x003E0042, SetClientVersion = 0x00400042, }; } -void ACService::reset() {} +void ACService::reset() { + connected = false; + disconnectEvent = std::nullopt; +} void ACService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); @@ -21,6 +25,7 @@ void ACService::handleSyncRequest(u32 messagePointer) { case ACCommands::CloseAsync: closeAsync(messagePointer); break; case ACCommands::CreateDefaultConfig: createDefaultConfig(messagePointer); break; case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break; + case ACCommands::IsConnected: isConnected(messagePointer); break; case ACCommands::RegisterDisconnectEvent: registerDisconnectEvent(messagePointer); break; case ACCommands::SetClientVersion: setClientVersion(messagePointer); break; default: Helpers::panic("AC service requested. Command: %08X\n", command); @@ -37,6 +42,11 @@ void ACService::cancelConnectAsync(u32 messagePointer) { void ACService::closeAsync(u32 messagePointer) { log("AC::CloseAsync (stubbed)\n"); + connected = false; + + if (disconnectEvent.has_value()) { + Helpers::warn("AC::DisconnectEvent should be signalled but isn't implemented yet"); + } // TODO: Verify if this response header is correct on hardware mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0)); @@ -59,6 +69,15 @@ void ACService::getLastErrorCode(u32 messagePointer) { mem.write32(messagePointer + 8, 0); // Hopefully this means no error? } +void ACService::isConnected(u32 messagePointer) { + log("AC::IsConnected\n"); + // This has parameters according to the command word but it's unknown what they are + + mem.write32(messagePointer, IPC::responseHeader(0x3E, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, connected ? 1 : 0); +} + void ACService::setClientVersion(u32 messagePointer) { u32 version = mem.read32(messagePointer + 4); log("AC::SetClientVersion (version = %d)\n", version); @@ -71,9 +90,11 @@ void ACService::registerDisconnectEvent(u32 messagePointer) { log("AC::RegisterDisconnectEvent (stubbed)\n"); const u32 pidHeader = mem.read32(messagePointer + 4); const u32 copyHandleHeader = mem.read32(messagePointer + 12); - // Event signaled when disconnecting from AC + // Event signaled when disconnecting from AC. TODO: Properly implement it. const Handle eventHandle = mem.read32(messagePointer + 16); + disconnectEvent = eventHandle; + mem.write32(messagePointer, IPC::responseHeader(0x30, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 4ad7f353..78ec80bc 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -9,10 +9,15 @@ namespace APTCommands { GetLockHandle = 0x00010040, Initialize = 0x00020080, Enable = 0x00030040, + GetAppletInfo = 0x00060040, + IsRegistered = 0x00090040, InquireNotification = 0x000B0040, + SendParameter = 0x000C0104, ReceiveParameter = 0x000D0080, GlanceParameter = 0x000E0080, PreloadLibraryApplet = 0x00160040, + PrepareToStartLibraryApplet = 0x00180040, + StartLibraryApplet = 0x001E0084, ReplySleepQuery = 0x003E0080, NotifyToWait = 0x00430040, GetSharedFont = 0x00440000, @@ -60,6 +65,8 @@ void APTService::reset() { lockHandle = std::nullopt; notificationEvent = std::nullopt; resumeEvent = std::nullopt; + + appletManager.reset(); } void APTService::handleSyncRequest(u32 messagePointer) { @@ -69,18 +76,22 @@ void APTService::handleSyncRequest(u32 messagePointer) { case APTCommands::CheckNew3DS: checkNew3DS(messagePointer); break; case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break; case APTCommands::Enable: enable(messagePointer); break; + case APTCommands::GetAppletInfo: getAppletInfo(messagePointer); break; case APTCommands::GetSharedFont: getSharedFont(messagePointer); break; case APTCommands::Initialize: initialize(messagePointer); break; case APTCommands::InquireNotification: [[likely]] inquireNotification(messagePointer); break; + case APTCommands::IsRegistered: isRegistered(messagePointer); break; case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break; case APTCommands::GetLockHandle: getLockHandle(messagePointer); break; case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break; case APTCommands::GlanceParameter: glanceParameter(messagePointer); break; case APTCommands::NotifyToWait: notifyToWait(messagePointer); break; case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break; + case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break; case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break; case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break; case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break; + case APTCommands::SendParameter: sendParameter(messagePointer); break; case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break; case APTCommands::TheSmashBrosFunction: theSmashBrosFunction(messagePointer); break; default: @@ -116,9 +127,38 @@ void APTService::appletUtility(u32 messagePointer) { } } +void APTService::getAppletInfo(u32 messagePointer) { + const u32 appID = mem.read32(messagePointer + 4); + Helpers::warn("APT::GetAppletInfo (appID = %X)\n", appID); + + mem.write32(messagePointer, IPC::responseHeader(0x06, 7, 0)); + mem.write32(messagePointer + 4, Result::Success); + + mem.write8(messagePointer + 20, 1); // 1 = registered + mem.write8(messagePointer + 24, 1); // 1 = loaded + // TODO: The rest of this +} + +void APTService::isRegistered(u32 messagePointer) { + const u32 appID = mem.read32(messagePointer + 4); + Helpers::warn("APT::IsRegistered (appID = %X)", appID); + + mem.write32(messagePointer, IPC::responseHeader(0x09, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, 1); // Return that the app is always registered. This might break with home menu? +} + void APTService::preloadLibraryApplet(u32 messagePointer) { const u32 appID = mem.read32(messagePointer + 4); - log("APT::PreloadLibraryApplet (app ID = %d) (stubbed)\n", appID); + log("APT::PreloadLibraryApplet (app ID = %X) (stubbed)\n", appID); + + mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void APTService::prepareToStartLibraryApplet(u32 messagePointer) { + const u32 appID = mem.read32(messagePointer + 4); + log("APT::PrepareToStartLibraryApplet (app ID = %X) (stubbed)\n", appID); mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0)); mem.write32(messagePointer + 4, Result::Success); @@ -193,6 +233,35 @@ void APTService::notifyToWait(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void APTService::sendParameter(u32 messagePointer) { + const u32 sourceAppID = mem.read32(messagePointer + 4); + const u32 destAppID = mem.read32(messagePointer + 8); + const u32 cmd = mem.read32(messagePointer + 12); + const u32 paramSize = mem.read32(messagePointer + 16); + + const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis? + const u32 parameterPointer = mem.read32(messagePointer + 32); + log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X) (Stubbed)", sourceAppID, destAppID, cmd, paramSize); + + mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + + if (sourceAppID != Applets::AppletIDs::Application) { + Helpers::warn("APT::SendParameter: Unimplemented source applet ID"); + } + + Applets::AppletBase* destApplet = appletManager.getApplet(destAppID); + if (destApplet == nullptr) { + Helpers::warn("APT::SendParameter: Unimplemented dest applet ID"); + } else { + auto result = destApplet->receiveParameter(); + } + + if (resumeEvent.has_value()) { + kernel.signalEvent(resumeEvent.value()); + } +} + void APTService::receiveParameter(u32 messagePointer) { const u32 app = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); diff --git a/src/core/services/frd.cpp b/src/core/services/frd.cpp index 67683dc1..d4a439df 100644 --- a/src/core/services/frd.cpp +++ b/src/core/services/frd.cpp @@ -18,6 +18,9 @@ namespace FRDCommands { GetMyScreenName = 0x00090000, GetMyMii = 0x000A0000, GetFriendKeyList = 0x00110080, + GetFriendPresence = 0x00120042, + GetFriendProfile = 0x00150042, + GetFriendAttributeFlags = 0x00170042, UpdateGameModeDescription = 0x001D0002, }; } @@ -28,7 +31,10 @@ void FRDService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { case FRDCommands::AttachToEventNotification: attachToEventNotification(messagePointer); break; + case FRDCommands::GetFriendAttributeFlags: getFriendAttributeFlags(messagePointer); break; case FRDCommands::GetFriendKeyList: getFriendKeyList(messagePointer); break; + case FRDCommands::GetFriendPresence: getFriendPresence(messagePointer); break; + case FRDCommands::GetFriendProfile: getFriendProfile(messagePointer); break; case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break; case FRDCommands::GetMyMii: getMyMii(messagePointer); break; case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break; @@ -83,6 +89,41 @@ void FRDService::getFriendKeyList(u32 messagePointer) { } } +void FRDService::getFriendProfile(u32 messagePointer) { + log("FRD::GetFriendProfile\n"); + + const u32 count = mem.read32(messagePointer + 4); + const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys + const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to + + mem.write32(messagePointer, IPC::responseHeader(0x15, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); + + // Clear all profiles + for (u32 i = 0; i < count; i++) { + const u32 pointer = profile + (i * sizeof(Profile)); + for (u32 j = 0; j < sizeof(Profile); j++) { + mem.write8(pointer + j, 0); + } + } +} + +void FRDService::getFriendAttributeFlags(u32 messagePointer) { + log("FRD::GetFriendAttributeFlags\n"); + + const u32 count = mem.read32(messagePointer + 4); + const u32 friendKeyList = mem.read32(messagePointer + 12); // Pointer to list of friend keys + const u32 profile = mem.read32(messagePointer + 0x104); // Pointer to friend profile where we'll write info to + + mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); + + // Clear flags + for (u32 i = 0; i < count; i++) { + mem.write8(profile + i, 0); + } +} + void FRDService::getMyPresence(u32 messagePointer) { static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains log("FRD::GetMyPresence\n"); @@ -96,6 +137,14 @@ void FRDService::getMyPresence(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void FRDService::getFriendPresence(u32 messagePointer) { + Helpers::warn("FRD::GetFriendPresence (stubbed)"); + + // TODO: Implement and document this, + mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void FRDService::getMyProfile(u32 messagePointer) { mem.write32(messagePointer, IPC::responseHeader(0x7, 3, 0)); // Not sure if the header here has the correct # of responses? mem.write32(messagePointer + 4, Result::Success); diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 38a526e3..00b66f0c 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -25,13 +25,19 @@ namespace FSCommands { GetFreeBytes = 0x08120080, IsSdmcDetected = 0x08170000, IsSdmcWritable = 0x08180000, + AbnegateAccessRight = 0x08400040, GetFormatInfo = 0x084500C2, + GetArchiveResource = 0x08490040, FormatSaveData = 0x084C0242, CreateExtSaveData = 0x08510242, DeleteExtSaveData = 0x08520100, + SetArchivePriority = 0x085A00C0, InitializeWithSdkVersion = 0x08610042, SetPriority = 0x08620040, - GetPriority = 0x08630000 + GetPriority = 0x08630000, + SetThisSaveDataSecureValue = 0x086E00C0, + GetThisSaveDataSecureValue = 0x086F0040, + TheGameboyVCFunction = 0x08750180, }; } @@ -70,6 +76,8 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) { switch (id) { case ArchiveID::SelfNCCH: return &selfNcch; case ArchiveID::SaveData: return &saveData; + case ArchiveID::UserSaveData2: return &userSaveData2; + case ArchiveID::ExtSaveData: return &extSaveData_sdmc; @@ -154,9 +162,11 @@ void FSService::handleSyncRequest(u32 messagePointer) { case FSCommands::DeleteFile: deleteFile(messagePointer); break; case FSCommands::FormatSaveData: formatSaveData(messagePointer); break; case FSCommands::FormatThisUserSaveData: formatThisUserSaveData(messagePointer); break; + case FSCommands::GetArchiveResource: getArchiveResource(messagePointer); break; case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break; case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break; case FSCommands::GetPriority: getPriority(messagePointer); break; + case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break; case FSCommands::Initialize: initialize(messagePointer); break; case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break; case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break; @@ -165,7 +175,11 @@ void FSService::handleSyncRequest(u32 messagePointer) { case FSCommands::OpenDirectory: openDirectory(messagePointer); break; case FSCommands::OpenFile: [[likely]] openFile(messagePointer); break; case FSCommands::OpenFileDirectly: [[likely]] openFileDirectly(messagePointer); break; + case FSCommands::SetArchivePriority: setArchivePriority(messagePointer); break; case FSCommands::SetPriority: setPriority(messagePointer); break; + case FSCommands::SetThisSaveDataSecureValue: setThisSaveDataSecureValue(messagePointer); break; + case FSCommands::AbnegateAccessRight: abnegateAccessRight(messagePointer); break; + case FSCommands::TheGameboyVCFunction: theGameboyVCFunction(messagePointer); break; default: Helpers::panic("FS service requested. Command: %08X\n", command); } } @@ -573,6 +587,36 @@ void FSService::getPriority(u32 messagePointer) { mem.write32(messagePointer + 8, priority); } +void FSService::getArchiveResource(u32 messagePointer) { + const u32 mediaType = mem.read32(messagePointer + 4); + log("FS::GetArchiveResource (media type = %d) (stubbed)\n"); + + // For the time being, return the same stubbed archive resource for every media type + static constexpr ArchiveResource resource = { + .sectorSize = 512, + .clusterSize = 16_KB, + .partitionCapacityInClusters = 0x80000, // 0x80000 * 16 KB = 8GB + .freeSpaceInClusters = 0x80000, // Same here + }; + + mem.write32(messagePointer, IPC::responseHeader(0x849, 5, 0)); + mem.write32(messagePointer + 4, Result::Success); + + mem.write32(messagePointer + 8, resource.sectorSize); + mem.write32(messagePointer + 12, resource.clusterSize); + mem.write32(messagePointer + 16, resource.partitionCapacityInClusters); + mem.write32(messagePointer + 20, resource.freeSpaceInClusters); +} + +void FSService::setArchivePriority(u32 messagePointer) { + Handle archive = mem.read64(messagePointer + 4); + const u32 value = mem.read32(messagePointer + 12); + log("FS::SetArchivePriority (priority = %d, archive handle = %X)\n", value, handle); + + mem.write32(messagePointer, IPC::responseHeader(0x85A, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void FSService::setPriority(u32 messagePointer) { const u32 value = mem.read32(messagePointer + 4); log("FS::SetPriority (priority = %d)\n", value); @@ -582,6 +626,44 @@ void FSService::setPriority(u32 messagePointer) { priority = value; } +void FSService::abnegateAccessRight(u32 messagePointer) { + const u32 right = mem.read32(messagePointer + 4); + log("FS::AbnegateAccessRight (right = %d)\n", right); + + if (right >= 0x38) { + Helpers::warn("FS::AbnegateAccessRight: Invalid access right"); + } + + mem.write32(messagePointer, IPC::responseHeader(0x840, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void FSService::getThisSaveDataSecureValue(u32 messagePointer) { + Helpers::warn("Unimplemented FS::GetThisSaveDataSecureValue"); + + mem.write32(messagePointer, IPC::responseHeader(0x86F, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void FSService::setThisSaveDataSecureValue(u32 messagePointer) { + const u64 value = mem.read32(messagePointer + 4); + const u32 slot = mem.read32(messagePointer + 12); + const u32 id = mem.read32(messagePointer + 16); + const u8 variation = mem.read8(messagePointer + 20); + + // TODO: Actually do something with this. + Helpers::warn("Unimplemented FS::SetThisSaveDataSecureValue"); + + mem.write32(messagePointer, IPC::responseHeader(0x86E, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void FSService::theGameboyVCFunction(u32 messagePointer) { + Helpers::warn("Unimplemented FS: function: 0x08750180"); + + mem.write32(messagePointer, IPC::responseHeader(0x875, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} // Shows whether an SD card is inserted. At the moment stubbed to no constexpr bool sdInserted = false; diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index c2f7faee..0e27c0ec 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -15,6 +15,8 @@ namespace ServiceCommands { FlushDataCache = 0x00080082, SetLCDForceBlack = 0x000B0040, TriggerCmdReqQueue = 0x000C0000, + ImportDisplayCaptureInfo = 0x00180000, + SaveVramSysArea = 0x00190000, SetInternalPriorities = 0x001E0080, StoreDataCache = 0x001F0082 }; @@ -42,15 +44,17 @@ void GPUService::reset() { void GPUService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { + case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break; case ServiceCommands::AcquireRight: acquireRight(messagePointer); break; case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break; + case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break; case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break; + case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break; case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break; case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break; case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break; case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break; case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break; - case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break; case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break; case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break; default: Helpers::panic("GPU service requested. Command: %08X\n", command); @@ -456,3 +460,20 @@ void GPUService::triggerTextureCopy(u32* cmd) { // NSMB2 relies on this requestInterrupt(GPUInterrupt::PPF); } + +// Used when transitioning from the app to an OS applet, such as software keyboard, mii maker, mii selector, etc +// Stubbed until we decide to support LLE applets +void GPUService::saveVramSysArea(u32 messagePointer) { + Helpers::warn("GSP::GPU::SaveVramSysArea (stubbed)"); + + mem.write32(messagePointer, IPC::responseHeader(0x19, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +// Used in similar fashion to the SaveVramSysArea function +void GPUService::importDisplayCaptureInfo(u32 messagePointer) { + Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)"); + + mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0)); + mem.write32(messagePointer + 4, Result::Success); +} \ No newline at end of file diff --git a/src/core/services/mic.cpp b/src/core/services/mic.cpp index 055f5c1d..c20159f1 100644 --- a/src/core/services/mic.cpp +++ b/src/core/services/mic.cpp @@ -1,14 +1,19 @@ #include "services/mic.hpp" #include "ipc.hpp" +#include "kernel/kernel.hpp" namespace MICCommands { enum : u32 { MapSharedMem = 0x00010042, + UnmapSharedMem = 0x00020000, StartSampling = 0x00030140, StopSampling = 0x00050000, + IsSampling = 0x00060000, + GetEventHandle = 0x00070000, SetGain = 0x00080040, GetGain = 0x00090000, SetPower = 0x000A0040, + GetPower = 0x000B0000, SetIirFilter = 0x000C0042, SetClamp = 0x000D0040, CaptainToadFunction = 0x00100040, @@ -18,14 +23,19 @@ namespace MICCommands { void MICService::reset() { micEnabled = false; shouldClamp = false; - isSampling = false; + currentlySampling = false; gain = 0; + + eventHandle = std::nullopt; } void MICService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { + case MICCommands::GetEventHandle: getEventHandle(messagePointer); break; case MICCommands::GetGain: getGain(messagePointer); break; + case MICCommands::GetPower: getPower(messagePointer); break; + case MICCommands::IsSampling: isSampling(messagePointer); break; case MICCommands::MapSharedMem: mapSharedMem(messagePointer); break; case MICCommands::SetClamp: setClamp(messagePointer); break; case MICCommands::SetGain: setGain(messagePointer); break; @@ -33,6 +43,7 @@ void MICService::handleSyncRequest(u32 messagePointer) { case MICCommands::SetPower: setPower(messagePointer); break; case MICCommands::StartSampling: startSampling(messagePointer); break; case MICCommands::StopSampling: stopSampling(messagePointer); break; + case MICCommands::UnmapSharedMem: unmapSharedMem(messagePointer); break; case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break; default: Helpers::panic("MIC service requested. Command: %08X\n", command); } @@ -47,6 +58,27 @@ void MICService::mapSharedMem(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void MICService::unmapSharedMem(u32 messagePointer) { + log("MIC::UnmapSharedMem (stubbed)\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void MICService::getEventHandle(u32 messagePointer) { + log("MIC::GetEventHandle\n"); + Helpers::warn("Acquire MIC event handle"); + + if (!eventHandle.has_value()) { + eventHandle = kernel.makeEvent(ResetType::OneShot); + } + + mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); + // TODO: Translation descriptor + mem.write32(messagePointer + 12, eventHandle.value()); +} + void MICService::getGain(u32 messagePointer) { log("MIC::GetGain\n"); mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); @@ -71,6 +103,14 @@ void MICService::setPower(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void MICService::getPower(u32 messagePointer) { + log("MIC::GetPower\n"); + + mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, micEnabled ? 1 : 0); +} + void MICService::setClamp(u32 messagePointer) { u8 val = mem.read8(messagePointer + 4); log("MIC::SetClamp (value = %d)\n", val); @@ -91,19 +131,27 @@ void MICService::startSampling(u32 messagePointer) { encoding, sampleRate, offset, dataSize, loop ? "yes" : "no" ); - isSampling = true; + currentlySampling = true; mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void MICService::stopSampling(u32 messagePointer) { log("MIC::StopSampling\n"); - isSampling = false; + currentlySampling = false; mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } +void MICService::isSampling(u32 messagePointer) { + log("MIC::IsSampling"); + + mem.write32(messagePointer, IPC::responseHeader(0x6, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, currentlySampling ? 1 : 0); +} + void MICService::setIirFilter(u32 messagePointer) { const u32 size = mem.read32(messagePointer + 4); const u32 pointer = mem.read32(messagePointer + 12); diff --git a/src/core/services/nfc.cpp b/src/core/services/nfc.cpp index eadb1b92..b1e04ab2 100644 --- a/src/core/services/nfc.cpp +++ b/src/core/services/nfc.cpp @@ -5,8 +5,10 @@ namespace NFCCommands { enum : u32 { Initialize = 0x00010040, + Shutdown = 0x00020040, StartCommunication = 0x00030000, StopCommunication = 0x00040000, + StartTagScanning = 0x00050040, GetTagInRangeEvent = 0x000B0000, GetTagOutOfRangeEvent = 0x000C0000, GetTagState = 0x000D0000, @@ -32,7 +34,9 @@ void NFCService::handleSyncRequest(u32 messagePointer) { case NFCCommands::GetTagInRangeEvent: getTagInRangeEvent(messagePointer); break; case NFCCommands::GetTagOutOfRangeEvent: getTagOutOfRangeEvent(messagePointer); break; case NFCCommands::GetTagState: getTagState(messagePointer); break; + case NFCCommands::Shutdown: shutdown(messagePointer); break; case NFCCommands::StartCommunication: startCommunication(messagePointer); break; + case NFCCommands::StartTagScanning: startTagScanning(messagePointer); break; case NFCCommands::StopCommunication: stopCommunication(messagePointer); break; default: Helpers::panic("NFC service requested. Command: %08X\n", command); } @@ -50,6 +54,16 @@ void NFCService::initialize(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void NFCService::shutdown(u32 messagePointer) { + log("MFC::Shutdown"); + const u8 mode = mem.read8(messagePointer + 4); + + Helpers::warn("NFC::Shutdown: Unimplemented mode: %d", mode); + + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + /* The NFC service provides userland with 2 events. One that is signaled when an NFC tag gets in range, And one that is signaled when it gets out of range. Userland can have a thread sleep on this so it will be alerted @@ -114,6 +128,14 @@ void NFCService::startCommunication(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void NFCService::startTagScanning(u32 messagePointer) { + log("NFC::StartTagScanning\n"); + tagStatus = TagStatus::Scanning; + + mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void NFCService::stopCommunication(u32 messagePointer) { log("NFC::StopCommunication\n"); adapterStatus = Old3DSAdapterStatus::InitializationComplete; diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index 2f196362..cb93b627 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -8,7 +8,7 @@ ServiceManager::ServiceManager(std::span regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel, const EmulatorConfig& config) : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), act(mem), apt(mem, kernel), cam(mem, kernel), cecd(mem, kernel), cfg(mem), dlp_srvr(mem), dsp(mem, kernel), hid(mem, kernel), http(mem), ir_user(mem, kernel), frd(mem), fs(mem, kernel), - gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem), nfc(mem, kernel), nim(mem), ndm(mem), + gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), mcu_hwc(mem, config), mic(mem, kernel), nfc(mem, kernel), nim(mem), ndm(mem), news_u(mem), ptm(mem, config), soc(mem), ssl(mem), y2r(mem, kernel) {} static constexpr int MAX_NOTIFICATION_COUNT = 16; diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag index 3f1a256b..2fd4146a 100644 --- a/src/host_shaders/opengl_fragment_shader.frag +++ b/src/host_shaders/opengl_fragment_shader.frag @@ -345,7 +345,7 @@ void main() { if ((textureConfig & 2u) != 0u) tevSources[4] = texture(u_tex1, v_texcoord1); if ((textureConfig & 4u) != 0u) tevSources[5] = texture(u_tex2, tex2UV); tevSources[13] = vec4(0.0); // Previous buffer - tevSources[15] = vec4(0.0); // Previous combiner + tevSources[15] = v_colour; // Previous combiner tevNextPreviousBuffer = v_textureEnvBufferColor; uint textureEnvUpdateBuffer = readPicaReg(0xE0); diff --git a/src/host_shaders/opengl_vertex_shader.vert b/src/host_shaders/opengl_vertex_shader.vert index cbf992c4..63ef1ae4 100644 --- a/src/host_shaders/opengl_vertex_shader.vert +++ b/src/host_shaders/opengl_vertex_shader.vert @@ -64,7 +64,8 @@ float decodeFP(uint hex, uint E, uint M) { void main() { gl_Position = a_coords; - v_colour = a_vertexColour; + vec4 colourAbs = abs(a_vertexColour); + v_colour = min(colourAbs, vec4(1.f)); // Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA v_texcoord0 = vec3(a_texcoord0.x, 1.0 - a_texcoord0.y, a_texcoord0_w); @@ -94,4 +95,4 @@ void main() { // There's also another, always-on clipping plane based on vertex z gl_ClipDistance[0] = -a_coords.z; gl_ClipDistance[1] = dot(clipData, a_coords); -} \ No newline at end of file +} diff --git a/src/host_shaders/vulkan_display.frag b/src/host_shaders/vulkan_display.frag new file mode 100644 index 00000000..1b6bd937 --- /dev/null +++ b/src/host_shaders/vulkan_display.frag @@ -0,0 +1,7 @@ +#version 460 core +layout(location = 0) in vec2 UV; +layout(location = 0) out vec4 FragColor; + +layout(binding = 0) uniform sampler2D u_texture; + +void main() { FragColor = texture(u_texture, UV); } \ No newline at end of file diff --git a/src/host_shaders/vulkan_display.vert b/src/host_shaders/vulkan_display.vert new file mode 100644 index 00000000..284997ca --- /dev/null +++ b/src/host_shaders/vulkan_display.vert @@ -0,0 +1,7 @@ +#version 460 core +layout(location = 0) out vec2 UV; + +void main() { + UV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(UV * 2.0f + -1.0f, 0.0f, 1.0f); +} \ No newline at end of file