diff --git a/.github/workflows/windows-native-backend-test.yml b/.github/workflows/windows-native-backend-test.yml deleted file mode 100644 index b3af065..0000000 --- a/.github/workflows/windows-native-backend-test.yml +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (C) 2026 SharpEmu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -name: Test Windows Native Vulkan Backend - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - test-windows-native-vulkan: - name: Build and test win-x64 native backend - runs-on: windows-latest - env: - DOTNET_NOLOGO: true - NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages - NATIVE_BUILD_DIR: ${{ github.workspace }}\artifacts\native\gpu-vulkan - PUBLISH_DIR: ${{ github.workspace }}\artifacts\publish\win-x64-native-test - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 10.0.103 - cache: true - cache-dependency-path: | - Directory.Packages.props - src/**/packages.lock.json - - - name: Install native dependencies - shell: pwsh - run: | - & "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install sdl3[vulkan]:x64-windows vulkan:x64-windows - - - name: Configure native backend - shell: pwsh - run: | - cmake ` - -S src/SharpEmu.Gpu.Vulkan.Native ` - -B "$env:NATIVE_BUILD_DIR" ` - -A x64 ` - -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" ` - -DVCPKG_TARGET_TRIPLET=x64-windows ` - -DBUILD_TESTING=ON - - - name: Build native backend and Windows-safe ABI test - run: cmake --build "$env:NATIVE_BUILD_DIR" --config Release --target sharpemu_gpu_vulkan_exports_test - - - name: Run native DLL load and ABI test - run: ctest --test-dir "$env:NATIVE_BUILD_DIR" -C Release -R sharpemu_gpu_vulkan_exports_test --output-on-failure - - - name: Restore solution - run: dotnet restore SharpEmu.slnx --locked-mode - - - name: Build solution - run: dotnet build SharpEmu.slnx -c Release --no-restore - - - name: Publish win-x64 emulator - run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r win-x64 --self-contained true --no-restore -p:PublishDir="${env:PUBLISH_DIR}" - - - name: Verify published native runtime - shell: pwsh - run: | - $required = @( - (Join-Path $env:PUBLISH_DIR 'SharpEmu.exe'), - (Join-Path $env:PUBLISH_DIR 'sharpemu_gpu_vulkan.dll'), - (Join-Path $env:PUBLISH_DIR 'SDL3.dll') - ) - foreach ($path in $required) { - if (-not (Test-Path $path)) { - throw "Missing published runtime file: $path" - } - } - - $exports = & dumpbin.exe /nologo /exports (Join-Path $env:PUBLISH_DIR 'sharpemu_gpu_vulkan.dll') - foreach ($name in @('se_gpu_abi_version', 'se_gpu_create', 'se_gpu_submit_draw', 'se_gpu_present_bgra')) { - if ($exports -notmatch "\b$name\b") { - throw "Missing native GPU export: $name" - } - } - - - name: Upload tested Windows package - uses: actions/upload-artifact@v7 - with: - name: sharpemu-win-x64-native-backend-test-${{ github.sha }} - path: ${{ env.PUBLISH_DIR }} - if-no-files-found: error - retention-days: 7 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index c202716..90ded6c 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -96,19 +96,6 @@ jobs: Directory.Packages.props Directory.Build.props - - name: Install Windows native GPU dependencies - shell: pwsh - run: | - & "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install sdl3[vulkan]:x64-windows vulkan:x64-windows - - cmake ` - -S src/SharpEmu.Gpu.Vulkan.Native ` - -B artifacts/native/gpu-vulkan ` - -A x64 ` - -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" ` - -DVCPKG_TARGET_TRIPLET=x64-windows ` - -DBUILD_TESTING=OFF - - name: Restore solution run: dotnet restore SharpEmu.slnx @@ -127,19 +114,6 @@ jobs: - name: Publish win-x64 CLI run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r win-x64 --self-contained true --no-restore -p:PublishDir="${env:PUBLISH_DIR}" - - name: Verify Windows native GPU runtime - shell: pwsh - run: | - $required = @( - (Join-Path $env:PUBLISH_DIR 'sharpemu_gpu_vulkan.dll'), - (Join-Path $env:PUBLISH_DIR 'SDL3.dll') - ) - foreach ($path in $required) { - if (-not (Test-Path $path)) { - throw "Missing native GPU runtime: $path" - } - } - - name: Create release archive run: | New-Item -ItemType Directory -Path $env:RELEASE_DIR -Force | Out-Null @@ -191,42 +165,6 @@ jobs: Directory.Packages.props Directory.Build.props - - name: Install Linux native GPU dependencies - if: matrix.rid == 'linux-x64' - run: | - sudo apt-get update - sudo apt-get install --yes \ - autoconf \ - autoconf-archive \ - automake \ - libegl1-mesa-dev \ - libibus-1.0-dev \ - libtool \ - libltdl-dev \ - libvulkan-dev \ - libwayland-dev \ - libxkbcommon-dev \ - pkg-config - "$VCPKG_INSTALLATION_ROOT/vcpkg" install sdl3[vulkan]:x64-linux - cmake \ - -S src/SharpEmu.Gpu.Vulkan.Native \ - -B artifacts/native/gpu-vulkan \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" \ - -DVCPKG_TARGET_TRIPLET=x64-linux \ - -DBUILD_TESTING=OFF - - - name: Install macOS native GPU dependencies - if: matrix.rid == 'osx-x64' - run: | - brew install sdl3 vulkan-loader vulkan-headers - cmake \ - -S src/SharpEmu.Gpu.Vulkan.Native \ - -B artifacts/native/gpu-vulkan \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH="$(brew --prefix)" \ - -DBUILD_TESTING=OFF - - name: Restore solution run: dotnet restore SharpEmu.slnx diff --git a/src/SharpEmu.CLI/SharpEmu.CLI.csproj b/src/SharpEmu.CLI/SharpEmu.CLI.csproj index a15ad97..5b9ddc8 100644 --- a/src/SharpEmu.CLI/SharpEmu.CLI.csproj +++ b/src/SharpEmu.CLI/SharpEmu.CLI.csproj @@ -58,21 +58,6 @@ SPDX-License-Identifier: GPL-2.0-or-later $(NoWarn);1591 - $(RepoRoot)artifacts\native\gpu-vulkan\bin - - - - $(NativeGpuBuildDir)\sharpemu_gpu_vulkan.dll - sharpemu_gpu_vulkan.dll - $(NativeGpuBuildDir)\SDL3.dll - - - $(NativeGpuBuildDir)\libsharpemu_gpu_vulkan.so - libsharpemu_gpu_vulkan.so - - - $(NativeGpuBuildDir)\libsharpemu_gpu_vulkan.dylib - libsharpemu_gpu_vulkan.dylib @@ -101,18 +86,4 @@ SPDX-License-Identifier: GPL-2.0-or-later - - - - - - diff --git a/src/SharpEmu.GUI/Languages/en.json b/src/SharpEmu.GUI/Languages/en.json index 750c8a6..db8f282 100644 --- a/src/SharpEmu.GUI/Languages/en.json +++ b/src/SharpEmu.GUI/Languages/en.json @@ -44,9 +44,10 @@ "Options.CpuEngine.Native": "Native", "Options.RenderingBackend.Label": "Rendering backend", - "Options.RenderingBackend.Desc": "Graphics implementation used for newly launched games.", - "Options.RenderingBackend.Native": "Native Vulkan", - "Options.RenderingBackend.Legacy": "Silk.NET Vulkan (Legacy)", + "Options.RenderingBackend.Desc": "Graphics implementation used for newly launched games. Native Vulkan requires a separate library.", + "Options.RenderingBackend.Native": "Native Vulkan (Experimental)", + "Options.RenderingBackend.Legacy": "Silk.NET Vulkan (Default)", + "Launch.NativeBackendMissing": "WARNING: Native Vulkan is selected, but {0} was not found next to SharpEmu. Download it from https://github.com/sharpemu/sharpemu.vulkan or select Silk.NET Vulkan in Options.", "Options.Strict.Label": "Strict dynlib resolution", "Options.Strict.Desc": "Fail the launch when an imported symbol cannot be resolved.", diff --git a/src/SharpEmu.GUI/MainWindow.axaml b/src/SharpEmu.GUI/MainWindow.axaml index 9fa70d1..01bbc88 100644 --- a/src/SharpEmu.GUI/MainWindow.axaml +++ b/src/SharpEmu.GUI/MainWindow.axaml @@ -211,8 +211,8 @@ SPDX-License-Identifier: GPL-2.0-or-later - - + + diff --git a/src/SharpEmu.GUI/MainWindow.axaml.cs b/src/SharpEmu.GUI/MainWindow.axaml.cs index ee73c47..1d5a57e 100644 --- a/src/SharpEmu.GUI/MainWindow.axaml.cs +++ b/src/SharpEmu.GUI/MainWindow.axaml.cs @@ -1496,6 +1496,22 @@ public partial class MainWindow : Window ? "legacy" : "native"); + if (string.Equals(_settings.RenderingBackend, "Native", StringComparison.OrdinalIgnoreCase)) + { + var nativeLibraryName = OperatingSystem.IsWindows() + ? "sharpemu_gpu_vulkan.dll" + : OperatingSystem.IsMacOS() + ? "libsharpemu_gpu_vulkan.dylib" + : "libsharpemu_gpu_vulkan.so"; + var emulatorDirectory = Path.GetDirectoryName(_emulatorExePath) ?? AppContext.BaseDirectory; + if (!File.Exists(Path.Combine(emulatorDirectory, nativeLibraryName))) + { + AppendConsoleLine( + Localization.Instance.Format("Launch.NativeBackendMissing", nativeLibraryName), + WarningLineBrush); + } + } + var emulator = new EmulatorProcess(); emulator.OutputReceived += (line, isError) => _pendingLines.Enqueue((line, isError)); emulator.Exited += code => Dispatcher.UIThread.Post(() => OnEmulatorExited(code)); diff --git a/src/SharpEmu.Gpu.Vulkan.Native/CMakeLists.txt b/src/SharpEmu.Gpu.Vulkan.Native/CMakeLists.txt deleted file mode 100644 index 11e7428..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/CMakeLists.txt +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2026 SharpEmu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -cmake_minimum_required(VERSION 3.25) -project(SharpEmuGpuVulkan VERSION 0.1.0 LANGUAGES CXX) - -find_package(Vulkan REQUIRED) -find_package(SDL3 CONFIG QUIET) -if(TARGET SDL3::SDL3) - set(SE_GPU_SDL_TARGET SDL3::SDL3) -elseif(TARGET SDL3::SDL3-shared) - set(SE_GPU_SDL_TARGET SDL3::SDL3-shared) -else() - find_package(PkgConfig REQUIRED) - pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3>=3.2) - set(SE_GPU_SDL_TARGET PkgConfig::SDL3) -endif() - -add_library(sharpemu_gpu_vulkan SHARED src/backend.cpp) -target_compile_features(sharpemu_gpu_vulkan PRIVATE cxx_std_20) -target_compile_definitions(sharpemu_gpu_vulkan PRIVATE SE_GPU_BUILD) -target_include_directories(sharpemu_gpu_vulkan PUBLIC include) -target_link_libraries(sharpemu_gpu_vulkan PRIVATE Vulkan::Vulkan ${SE_GPU_SDL_TARGET}) -if(MSVC) - target_compile_options(sharpemu_gpu_vulkan PRIVATE /W4 /permissive- /EHsc) -else() - target_compile_options(sharpemu_gpu_vulkan PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Werror) -endif() -set_target_properties(sharpemu_gpu_vulkan PROPERTIES - CXX_VISIBILITY_PRESET hidden - VISIBILITY_INLINES_HIDDEN YES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - -# Visual Studio is a multi-config generator and otherwise inserts an extra -# Debug/Release directory. Keep the ABI library at the path consumed by the -# managed project on every generator. -foreach(SE_GPU_CONFIG Debug Release RelWithDebInfo MinSizeRel) - string(TOUPPER "${SE_GPU_CONFIG}" SE_GPU_CONFIG_UPPER) - set_target_properties(sharpemu_gpu_vulkan PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${SE_GPU_CONFIG_UPPER} "${CMAKE_BINARY_DIR}/bin" - LIBRARY_OUTPUT_DIRECTORY_${SE_GPU_CONFIG_UPPER} "${CMAKE_BINARY_DIR}/bin") -endforeach() - -if(WIN32) - add_custom_command(TARGET sharpemu_gpu_vulkan POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $ - $ - COMMAND_EXPAND_LISTS) -endif() - -include(CTest) -if(BUILD_TESTING) - add_executable(sharpemu_gpu_vulkan_exports_test tests/exports_test.cpp) - target_link_libraries(sharpemu_gpu_vulkan_exports_test PRIVATE sharpemu_gpu_vulkan) - set_target_properties(sharpemu_gpu_vulkan_exports_test PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") - foreach(SE_GPU_CONFIG Debug Release RelWithDebInfo MinSizeRel) - string(TOUPPER "${SE_GPU_CONFIG}" SE_GPU_CONFIG_UPPER) - set_target_properties(sharpemu_gpu_vulkan_exports_test PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${SE_GPU_CONFIG_UPPER} "${CMAKE_BINARY_DIR}/bin") - endforeach() - add_test(NAME sharpemu_gpu_vulkan_exports_test COMMAND sharpemu_gpu_vulkan_exports_test) - - if(TARGET Vulkan::glslc) - set(SMOKE_COMPUTE_SPV "${CMAKE_CURRENT_BINARY_DIR}/smoke.comp.spv") - set(SMOKE_VERTEX_SPV "${CMAKE_CURRENT_BINARY_DIR}/smoke.vert.spv") - set(SMOKE_FRAGMENT_SPV "${CMAKE_CURRENT_BINARY_DIR}/smoke.frag.spv") - add_custom_command(OUTPUT "${SMOKE_COMPUTE_SPV}" - COMMAND Vulkan::glslc "${CMAKE_CURRENT_SOURCE_DIR}/tests/smoke.comp" -o "${SMOKE_COMPUTE_SPV}" - DEPENDS tests/smoke.comp VERBATIM) - add_custom_command(OUTPUT "${SMOKE_VERTEX_SPV}" - COMMAND Vulkan::glslc "${CMAKE_CURRENT_SOURCE_DIR}/tests/smoke.vert" -o "${SMOKE_VERTEX_SPV}" - DEPENDS tests/smoke.vert VERBATIM) - add_custom_command(OUTPUT "${SMOKE_FRAGMENT_SPV}" - COMMAND Vulkan::glslc "${CMAKE_CURRENT_SOURCE_DIR}/tests/smoke.frag" -o "${SMOKE_FRAGMENT_SPV}" - DEPENDS tests/smoke.frag VERBATIM) - add_custom_target(sharpemu_gpu_vulkan_test_shaders - DEPENDS "${SMOKE_COMPUTE_SPV}" "${SMOKE_VERTEX_SPV}" "${SMOKE_FRAGMENT_SPV}") - add_executable(sharpemu_gpu_vulkan_abi_test tests/abi_test.cpp) - target_link_libraries(sharpemu_gpu_vulkan_abi_test PRIVATE sharpemu_gpu_vulkan) - add_dependencies(sharpemu_gpu_vulkan_abi_test sharpemu_gpu_vulkan_test_shaders) - add_test(NAME sharpemu_gpu_vulkan_abi_test - COMMAND sharpemu_gpu_vulkan_abi_test - "${SMOKE_COMPUTE_SPV}" "${SMOKE_VERTEX_SPV}" "${SMOKE_FRAGMENT_SPV}") - endif() -endif() diff --git a/src/SharpEmu.Gpu.Vulkan.Native/include/sharpemu_gpu_vulkan.h b/src/SharpEmu.Gpu.Vulkan.Native/include/sharpemu_gpu_vulkan.h deleted file mode 100644 index df47c56..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/include/sharpemu_gpu_vulkan.h +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright (C) 2026 SharpEmu Emulator Project - * SPDX-License-Identifier: GPL-2.0-or-later */ -#pragma once -#include -#include - -#if defined(_WIN32) -# define SE_GPU_CALL __cdecl -# if defined(SE_GPU_BUILD) -# define SE_GPU_API __declspec(dllexport) -# else -# define SE_GPU_API __declspec(dllimport) -# endif -#else -# define SE_GPU_CALL -# define SE_GPU_API __attribute__((visibility("default"))) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define SE_GPU_ABI_VERSION 1u -typedef struct se_gpu_backend se_gpu_backend; -typedef enum se_gpu_result { - SE_GPU_OK = 0, SE_GPU_NOT_FOUND = 1, SE_GPU_NOT_READY = 2, - SE_GPU_INVALID_ARGUMENT = -1, SE_GPU_INCOMPATIBLE_ABI = -2, - SE_GPU_PLATFORM_ERROR = -3, SE_GPU_VULKAN_ERROR = -4, - SE_GPU_OUT_OF_MEMORY = -5, SE_GPU_INTERNAL_ERROR = -6 -} se_gpu_result; -typedef struct se_gpu_bytes { const void* data; size_t size; } se_gpu_bytes; -typedef void (SE_GPU_CALL *se_gpu_log_fn)(int32_t level, const char* message, void* user); -typedef struct se_gpu_create_info { - uint32_t struct_size, abi_version, width, height, enable_validation; - const char* title_utf8; - se_gpu_log_fn log; void* log_user; -} se_gpu_create_info; -typedef struct se_gpu_sampler { uint32_t words[4]; } se_gpu_sampler; -typedef struct se_gpu_texture { - uint32_t struct_size; uint64_t address; uint32_t width, height, format, number_type; - se_gpu_bytes rgba_pixels; uint32_t is_fallback, is_storage, mip_levels, mip_level; - uint32_t pitch, tile_mode, dst_select; se_gpu_sampler sampler; -} se_gpu_texture; -typedef struct se_gpu_memory_buffer { uint64_t address; se_gpu_bytes data; } se_gpu_memory_buffer; -typedef struct se_gpu_vertex_buffer { - uint32_t struct_size, location, component_count, data_format, number_format; - uint64_t address; uint32_t stride, offset_bytes; se_gpu_bytes data; -} se_gpu_vertex_buffer; -typedef struct se_gpu_index_buffer { se_gpu_bytes data; uint32_t is_32_bit; } se_gpu_index_buffer; -typedef struct se_gpu_rect { int32_t x, y; uint32_t width, height; } se_gpu_rect; -typedef struct se_gpu_viewport { float x, y, width, height, min_depth, max_depth; } se_gpu_viewport; -typedef struct se_gpu_blend { - uint32_t enable, color_src, color_dst, color_func, alpha_src, alpha_dst, alpha_func; - uint32_t separate_alpha, write_mask; -} se_gpu_blend; -typedef struct se_gpu_render_target { - uint32_t struct_size; uint64_t address; uint32_t width, height, format, number_type, mip_levels; -} se_gpu_render_target; -typedef struct se_gpu_draw { - uint32_t struct_size, width, height; se_gpu_bytes vertex_spirv, pixel_spirv; - const se_gpu_texture* textures; uint32_t texture_count; - const se_gpu_memory_buffer* memory_buffers; uint32_t memory_buffer_count; - const se_gpu_vertex_buffer* vertex_buffers; uint32_t vertex_buffer_count; - const se_gpu_render_target* targets; uint32_t target_count; - const se_gpu_blend* blends; uint32_t blend_count; - const se_gpu_index_buffer* index_buffer; const se_gpu_rect* scissor; - const se_gpu_viewport* viewport; uint32_t attribute_count, vertex_count, instance_count; - uint32_t primitive_type, publish_targets; -} se_gpu_draw; -typedef struct se_gpu_compute { - uint32_t struct_size; uint64_t shader_address; se_gpu_bytes spirv; - const se_gpu_texture* textures; uint32_t texture_count; - const se_gpu_memory_buffer* memory_buffers; uint32_t memory_buffer_count; - uint32_t groups_x, groups_y, groups_z; -} se_gpu_compute; -typedef struct se_gpu_input { - uint32_t struct_size, keyboard_focused, virtual_keys[8], gamepad_connected, gamepad_buttons; - uint8_t left_x, left_y, right_x, right_y, left_trigger, right_trigger, reserved[2]; - char gamepad_name_utf8[128]; -} se_gpu_input; - -SE_GPU_API uint32_t SE_GPU_CALL se_gpu_abi_version(void); -SE_GPU_API const char* SE_GPU_CALL se_gpu_last_error(const se_gpu_backend* backend); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_create(const se_gpu_create_info*, se_gpu_backend**); -SE_GPU_API void SE_GPU_CALL se_gpu_destroy(se_gpu_backend*); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_poll(se_gpu_backend*, uint32_t* should_close); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_input_snapshot(se_gpu_backend*, se_gpu_input*); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_present_bgra( - se_gpu_backend*, const void* pixels, size_t size, uint32_t width, uint32_t height, uint32_t pitch); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_submit_draw(se_gpu_backend*, const se_gpu_draw*); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_submit_compute(se_gpu_backend*, const se_gpu_compute*); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_register_display_buffer(se_gpu_backend*, uint64_t, uint32_t); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_present_guest_image( - se_gpu_backend*, uint64_t, uint32_t, uint32_t, uint32_t); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_has_guest_image( - se_gpu_backend*, uint64_t, uint32_t, uint32_t); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_blit_guest_image( - se_gpu_backend*, uint64_t, uint32_t, uint32_t, uint32_t, - uint64_t, uint32_t, uint32_t, uint32_t); -SE_GPU_API se_gpu_result SE_GPU_CALL se_gpu_render_target_output_kind( - uint32_t format, uint32_t number_type, uint32_t* output_kind); - -#ifdef __cplusplus -} -#endif diff --git a/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp b/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp deleted file mode 100644 index adf08ae..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/src/backend.cpp +++ /dev/null @@ -1,1403 +0,0 @@ -/* Copyright (C) 2026 SharpEmu Emulator Project - * SPDX-License-Identifier: GPL-2.0-or-later */ -#define SDL_MAIN_HANDLED 1 -#include "sharpemu_gpu_vulkan.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct guest_image { - VkImage image{}; - VkDeviceMemory memory{}; - VkImageView view{}; - VkFormat format{}; - VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED}; - uint32_t width{}, height{}, guest_format{}, number_type{}; - uint64_t cpu_fingerprint{}; - size_t cpu_size{}; -}; - -struct host_buffer { - VkBuffer buffer{}; - VkDeviceMemory memory{}; - VkDeviceSize size{}; - void* mapping{}; - VkBufferUsageFlags usage{}; -}; - -struct se_gpu_backend { - SDL_Window* window{}; SDL_Gamepad* gamepad{}; VkInstance instance{}; VkSurfaceKHR surface{}; - VkPhysicalDevice physical_device{}; VkDevice device{}; VkQueue queue{}; - uint32_t queue_family{}; std::string error; se_gpu_log_fn log{}; void* log_user{}; - VkSwapchainKHR swapchain{}; VkFormat swapchain_format{}; VkExtent2D extent{}; - std::vector swapchain_images; - VkCommandPool command_pool{}; VkCommandBuffer command_buffer{}; - VkPipelineCache pipeline_cache{}; - VkSemaphore image_available{}, render_finished{}; VkFence frame_fence{}; - VkBuffer staging{}; VkDeviceMemory staging_memory{}; void* staging_map{}; - VkDeviceSize staging_capacity{}; bool resized{}; - std::string base_title{"SharpEmu"}; - std::chrono::steady_clock::time_point fps_sample_started{std::chrono::steady_clock::now()}; - uint64_t fps_sample_frames{}; bool fps_counter_enabled{true}; - std::unordered_map guest_images; - std::unordered_map display_formats; - std::vector buffer_pool; -}; -namespace { -thread_local std::string global_error; -se_gpu_result ensure_staging(se_gpu_backend* b, VkDeviceSize size); -se_gpu_result create_swapchain(se_gpu_backend* b); -se_gpu_result fail(se_gpu_backend* b, se_gpu_result r, std::string message) { - (b ? b->error : global_error) = std::move(message); - if (b && b->log) b->log(3, b->error.c_str(), b->log_user); - return r; -} -void cleanup(se_gpu_backend* b) { - if (!b) return; - if (b->device) { - vkDeviceWaitIdle(b->device); - for (auto& [address, image] : b->guest_images) { - (void)address; - if (image.view) vkDestroyImageView(b->device, image.view, nullptr); - if (image.image) vkDestroyImage(b->device, image.image, nullptr); - if (image.memory) vkFreeMemory(b->device, image.memory, nullptr); - } - for (auto& buffer : b->buffer_pool) { - if (buffer.mapping) vkUnmapMemory(b->device, buffer.memory); - if (buffer.buffer) vkDestroyBuffer(b->device, buffer.buffer, nullptr); - if (buffer.memory) vkFreeMemory(b->device, buffer.memory, nullptr); - } - if (b->staging_map) vkUnmapMemory(b->device, b->staging_memory); - if (b->staging) vkDestroyBuffer(b->device, b->staging, nullptr); - if (b->staging_memory) vkFreeMemory(b->device, b->staging_memory, nullptr); - if (b->frame_fence) vkDestroyFence(b->device, b->frame_fence, nullptr); - if (b->render_finished) vkDestroySemaphore(b->device, b->render_finished, nullptr); - if (b->image_available) vkDestroySemaphore(b->device, b->image_available, nullptr); - if (b->command_pool) vkDestroyCommandPool(b->device, b->command_pool, nullptr); - if (b->pipeline_cache) vkDestroyPipelineCache(b->device, b->pipeline_cache, nullptr); - if (b->swapchain) vkDestroySwapchainKHR(b->device, b->swapchain, nullptr); - vkDestroyDevice(b->device, nullptr); - } - if (b->surface) vkDestroySurfaceKHR(b->instance, b->surface, nullptr); - if (b->instance) vkDestroyInstance(b->instance, nullptr); - if (b->gamepad) SDL_CloseGamepad(b->gamepad); - if (b->window) SDL_DestroyWindow(b->window); - SDL_Quit(); delete b; -} - -se_gpu_result abandon(se_gpu_backend* b, se_gpu_result result, std::string message) { - cleanup(b); - return fail(nullptr, result, std::move(message)); -} - -void record_presented_frame(se_gpu_backend* b) { - if (!b->fps_counter_enabled) return; - ++b->fps_sample_frames; - const auto now = std::chrono::steady_clock::now(); - const double seconds = std::chrono::duration(now - b->fps_sample_started).count(); - if (seconds < 0.5) return; - const double fps = static_cast(b->fps_sample_frames) / seconds; - char title[256]{}; - std::snprintf(title, sizeof(title), "FPS %.1f | %s", fps, b->base_title.c_str()); - SDL_SetWindowTitle(b->window, title); - b->fps_sample_frames = 0; - b->fps_sample_started = now; -} - -uint32_t memory_type(se_gpu_backend* b, uint32_t bits, VkMemoryPropertyFlags required) { - VkPhysicalDeviceMemoryProperties properties{}; - vkGetPhysicalDeviceMemoryProperties(b->physical_device, &properties); - for (uint32_t index = 0; index < properties.memoryTypeCount; ++index) { - if ((bits & (1u << index)) != 0 && - (properties.memoryTypes[index].propertyFlags & required) == required) return index; - } - return UINT32_MAX; -} - -VkFormat guest_format(uint32_t format, uint32_t number_type) { - if (format == 4) return number_type == 4 ? VK_FORMAT_R32_UINT : - number_type == 5 ? VK_FORMAT_R32_SINT : VK_FORMAT_R32_SFLOAT; - if (format == 5) return number_type == 4 ? VK_FORMAT_R16G16_UINT : - number_type == 5 ? VK_FORMAT_R16G16_SINT : number_type == 7 ? VK_FORMAT_R16G16_SFLOAT : - VK_FORMAT_R16G16_UNORM; - if (format == 10) return number_type == 4 ? VK_FORMAT_R8G8B8A8_UINT : - number_type == 5 ? VK_FORMAT_R8G8B8A8_SINT : VK_FORMAT_R8G8B8A8_UNORM; - if (format == 12) return number_type == 4 ? VK_FORMAT_R16G16B16A16_UINT : - number_type == 5 ? VK_FORMAT_R16G16B16A16_SINT : number_type == 7 ? VK_FORMAT_R16G16B16A16_SFLOAT : - VK_FORMAT_R16G16B16A16_UNORM; - switch (format) { - case 1: case 36: return VK_FORMAT_R8_UNORM; - case 3: return VK_FORMAT_R8G8_UNORM; - case 7: return VK_FORMAT_B10G11R11_UFLOAT_PACK32; - case 9: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; - case 13: case 14: return VK_FORMAT_R32G32B32A32_SFLOAT; - case 22: case 71: return VK_FORMAT_R16G16B16A16_SFLOAT; - case 29: return VK_FORMAT_R32_SFLOAT; - case 49: return VK_FORMAT_R8_UINT; - case 56: case 62: case 64: return VK_FORMAT_R8G8B8A8_UNORM; - case 75: return VK_FORMAT_R32G32_SFLOAT; - case 0x10004: return VK_FORMAT_R32_UINT; - case 0x20004: return VK_FORMAT_R32_SINT; - case 0x30004: return VK_FORMAT_R32_SFLOAT; - case 0x10005: return VK_FORMAT_R16G16_UINT; - case 0x20005: return VK_FORMAT_R16G16_SINT; - case 0x30005: return VK_FORMAT_R16G16_SFLOAT; - case 0x1000a: return VK_FORMAT_R8G8B8A8_UINT; - case 0x2000a: return VK_FORMAT_R8G8B8A8_SINT; - case 0x1000c: return VK_FORMAT_R16G16B16A16_UINT; - case 0x2000c: return VK_FORMAT_R16G16B16A16_SINT; - default: return VK_FORMAT_UNDEFINED; - } -} - -uint32_t format_bytes(VkFormat format) { - switch (format) { - case VK_FORMAT_R8_UNORM: case VK_FORMAT_R8_UINT: return 1; - case VK_FORMAT_R8G8_UNORM: return 2; - case VK_FORMAT_R32G32B32A32_SFLOAT: return 16; - case VK_FORMAT_R16G16B16A16_UNORM: case VK_FORMAT_R16G16B16A16_UINT: - case VK_FORMAT_R16G16B16A16_SINT: case VK_FORMAT_R16G16B16A16_SFLOAT: - case VK_FORMAT_R32G32_SFLOAT: return 8; - default: return 4; - } -} - -[[maybe_unused]] se_gpu_result create_buffer(se_gpu_backend* b, const void* data, size_t data_size, - VkBufferUsageFlags usage, host_buffer* output) { - if (!output) return SE_GPU_INVALID_ARGUMENT; - VkDeviceSize size = std::max(static_cast(data_size), 4); - auto pooled = std::find_if(b->buffer_pool.begin(), b->buffer_pool.end(), - [size, usage](const host_buffer& candidate) { return candidate.size >= size && candidate.usage == usage; }); - if (pooled != b->buffer_pool.end()) { - *output = *pooled; - b->buffer_pool.erase(pooled); - if (data && data_size) std::memcpy(output->mapping, data, data_size); - return SE_GPU_OK; - } - VkBufferCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - info.size = size; info.usage = usage; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(b->device, &info, nullptr, &output->buffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "host buffer creation failed"); - VkMemoryRequirements requirements{}; vkGetBufferMemoryRequirements(b->device, output->buffer, &requirements); - uint32_t type = memory_type(b, requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - if (type == UINT32_MAX) return fail(b, SE_GPU_VULKAN_ERROR, "no host buffer memory type"); - VkMemoryAllocateInfo allocation{}; allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = type; - if (vkAllocateMemory(b->device, &allocation, nullptr, &output->memory) != VK_SUCCESS || - vkBindBufferMemory(b->device, output->buffer, output->memory, 0) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "host buffer memory allocation failed"); - if (vkMapMemory(b->device, output->memory, 0, size, 0, &output->mapping) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "host buffer map failed"); - if (data && data_size) std::memcpy(output->mapping, data, data_size); - output->size = size; - output->usage = usage; - return SE_GPU_OK; -} - -[[maybe_unused]] void destroy_buffer(se_gpu_backend* b, host_buffer* buffer) { - if (buffer->buffer) b->buffer_pool.push_back(*buffer); - *buffer = {}; -} - -void destroy_image(se_gpu_backend* b, guest_image* image) { - if (image->view) vkDestroyImageView(b->device, image->view, nullptr); - if (image->image) vkDestroyImage(b->device, image->image, nullptr); - if (image->memory) vkFreeMemory(b->device, image->memory, nullptr); - *image = {}; -} - -se_gpu_result begin_immediate(se_gpu_backend* b) { - if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "GPU fence wait failed"); - vkResetFences(b->device, 1, &b->frame_fence); - vkResetCommandBuffer(b->command_buffer, 0); - VkCommandBufferBeginInfo begin{}; begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - return vkBeginCommandBuffer(b->command_buffer, &begin) == VK_SUCCESS ? SE_GPU_OK : - fail(b, SE_GPU_VULKAN_ERROR, "command buffer begin failed"); -} - -se_gpu_result end_immediate(se_gpu_backend* b) { - if (vkEndCommandBuffer(b->command_buffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "command buffer end failed"); - VkSubmitInfo submit{}; submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submit.commandBufferCount = 1; submit.pCommandBuffers = &b->command_buffer; - if (vkQueueSubmit(b->queue, 1, &submit, b->frame_fence) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "GPU queue submit failed"); - return vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) == VK_SUCCESS ? SE_GPU_OK : - fail(b, SE_GPU_VULKAN_ERROR, "GPU command completion failed"); -} - -void image_barrier(se_gpu_backend* b, guest_image* image, VkImageLayout next, - VkAccessFlags source_access, VkAccessFlags destination_access, - VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) { - VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.srcAccessMask = source_access; barrier.dstAccessMask = destination_access; - barrier.oldLayout = image->layout; barrier.newLayout = next; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image->image; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(b->command_buffer, source_stage, destination_stage, 0, - 0, nullptr, 0, nullptr, 1, &barrier); - image->layout = next; -} - -se_gpu_result create_image(se_gpu_backend* b, uint32_t width, uint32_t height, - uint32_t format, uint32_t number_type, guest_image* output) { - VkFormat native_format = guest_format(format, number_type); - if (!width || !height || native_format == VK_FORMAT_UNDEFINED) - return fail(b, SE_GPU_INVALID_ARGUMENT, "unsupported guest image format"); - VkImageCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - info.imageType = VK_IMAGE_TYPE_2D; info.format = native_format; info.extent = {width, height, 1}; - info.mipLevels = 1; info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; - info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateImage(b->device, &info, nullptr, &output->image) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image creation failed"); - VkMemoryRequirements requirements{}; vkGetImageMemoryRequirements(b->device, output->image, &requirements); - uint32_t type = memory_type(b, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - if (type == UINT32_MAX) return fail(b, SE_GPU_VULKAN_ERROR, "no device-local image memory type"); - VkMemoryAllocateInfo allocation{}; allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = type; - if (vkAllocateMemory(b->device, &allocation, nullptr, &output->memory) != VK_SUCCESS || - vkBindImageMemory(b->device, output->image, output->memory, 0) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image memory allocation failed"); - VkImageViewCreateInfo view{}; view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - view.image = output->image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = native_format; - view.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - if (vkCreateImageView(b->device, &view, nullptr, &output->view) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image view creation failed"); - output->format = native_format; output->width = width; output->height = height; - output->guest_format = format; output->number_type = number_type; return SE_GPU_OK; -} - -se_gpu_result upload_image(se_gpu_backend* b, guest_image* image, const se_gpu_bytes& pixels, - uint32_t row_length) { - if (!pixels.data || !pixels.size) return SE_GPU_OK; - row_length = std::max(row_length, image->width); - uint32_t source_texel_bytes = image->guest_format == 13 ? 12u : format_bytes(image->format); - uint32_t upload_texel_bytes = format_bytes(image->format); - size_t required = static_cast(row_length) * image->height * source_texel_bytes; - if (pixels.size < required) return fail(b, SE_GPU_INVALID_ARGUMENT, "guest image upload is undersized"); - size_t upload_size = static_cast(row_length) * image->height * upload_texel_bytes; - se_gpu_result result = ensure_staging(b, static_cast(upload_size)); - if (result != SE_GPU_OK) return result; - if (image->guest_format == 13) { - const auto* source = static_cast(pixels.data); - auto* destination = static_cast(b->staging_map); - size_t texels = static_cast(row_length) * image->height; - for (size_t texel = 0; texel < texels; ++texel) { - std::memcpy(destination + texel * 16, source + texel * 12, 12); - destination[texel * 16 + 12] = 0; - destination[texel * 16 + 13] = 0; - destination[texel * 16 + 14] = 0x80; - destination[texel * 16 + 15] = 0x3f; - } - } else { - std::memcpy(b->staging_map, pixels.data, upload_size); - } - result = begin_immediate(b); if (result != SE_GPU_OK) return result; - image_barrier(b, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VkBufferImageCopy copy{}; - copy.bufferRowLength = row_length > image->width ? row_length : 0; - copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - copy.imageExtent = {image->width, image->height, 1}; - vkCmdCopyBufferToImage(b->command_buffer, b->staging, image->image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); - image_barrier(b, image, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - return end_immediate(b); -} - -uint64_t fingerprint(const se_gpu_bytes& bytes) { - const auto* data = static_cast(bytes.data); - uint64_t hash = 1469598103934665603ull; - for (size_t index = 0; index < bytes.size; ++index) { - hash ^= data[index]; hash *= 1099511628211ull; - } - return hash; -} - -se_gpu_result cached_image(se_gpu_backend* b, uint64_t address, uint32_t width, uint32_t height, - uint32_t format, uint32_t number_type, uint32_t row_length, - const se_gpu_bytes* pixels, guest_image** output) { - if (!address || !output) return SE_GPU_INVALID_ARGUMENT; - auto [iterator, inserted] = b->guest_images.try_emplace(address); - guest_image& image = iterator->second; - VkFormat native_format = guest_format(format, number_type); - if (!inserted && (image.width != width || image.height != height || image.format != native_format)) { - vkDeviceWaitIdle(b->device); destroy_image(b, &image); inserted = true; - } - se_gpu_result result = SE_GPU_OK; - if (inserted) result = create_image(b, width, height, format, number_type, &image); - if (result == SE_GPU_OK && pixels && pixels->data && pixels->size) { - uint64_t content = fingerprint(*pixels); - if (image.cpu_size != pixels->size || image.cpu_fingerprint != content) { - result = upload_image(b, &image, *pixels, row_length); - if (result == SE_GPU_OK) { image.cpu_size = pixels->size; image.cpu_fingerprint = content; } - } - } - if (result != SE_GPU_OK) { destroy_image(b, &image); b->guest_images.erase(iterator); return result; } - *output = ℑ return SE_GPU_OK; -} - -se_gpu_result blit_image(se_gpu_backend* b, guest_image* source, guest_image* destination) { - se_gpu_result result = begin_immediate(b); if (result != SE_GPU_OK) return result; - VkImageLayout source_layout = source->layout; - image_barrier(b, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - image_barrier(b, destination, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VkImageBlit region{}; region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - region.srcOffsets[1] = {static_cast(source->width), static_cast(source->height), 1}; - region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - region.dstOffsets[1] = {static_cast(destination->width), static_cast(destination->height), 1}; - vkCmdBlitImage(b->command_buffer, source->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - destination->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_NEAREST); - image_barrier(b, source, source_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_GENERAL : source_layout, - VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - image_barrier(b, destination, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - return end_immediate(b); -} - -se_gpu_result present_image(se_gpu_backend* b, guest_image* source) { - if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "presentation fence wait failed"); - if (b->resized) { vkDeviceWaitIdle(b->device); se_gpu_result recreated = create_swapchain(b); - if (recreated != SE_GPU_OK) return recreated; } - uint32_t image_index{}; VkResult acquired = vkAcquireNextImageKHR(b->device, b->swapchain, - UINT64_MAX, b->image_available, VK_NULL_HANDLE, &image_index); - if (acquired == VK_ERROR_OUT_OF_DATE_KHR) { b->resized = true; return SE_GPU_NOT_READY; } - if (acquired != VK_SUCCESS && acquired != VK_SUBOPTIMAL_KHR) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image swapchain acquisition failed"); - vkResetFences(b->device, 1, &b->frame_fence); vkResetCommandBuffer(b->command_buffer, 0); - VkCommandBufferBeginInfo begin{}; begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - if (vkBeginCommandBuffer(b->command_buffer, &begin) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image command begin failed"); - VkImageLayout original = source->layout; - image_barrier(b, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VkImageMemoryBarrier swapchain{}; swapchain.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - swapchain.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; swapchain.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - swapchain.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - swapchain.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; swapchain.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - swapchain.image = b->swapchain_images[image_index]; - swapchain.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(b->command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &swapchain); - VkImageBlit region{}; region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - region.srcOffsets[1] = {static_cast(source->width), static_cast(source->height), 1}; - region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - region.dstOffsets[1] = {static_cast(b->extent.width), static_cast(b->extent.height), 1}; - vkCmdBlitImage(b->command_buffer, source->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - b->swapchain_images[image_index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_NEAREST); - image_barrier(b, source, original == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_GENERAL : original, - VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - swapchain.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; swapchain.dstAccessMask = 0; - swapchain.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; swapchain.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - vkCmdPipelineBarrier(b->command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &swapchain); - if (vkEndCommandBuffer(b->command_buffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image command end failed"); - VkPipelineStageFlags wait = VK_PIPELINE_STAGE_TRANSFER_BIT; VkSubmitInfo submit{}; - submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit.waitSemaphoreCount = 1; - submit.pWaitSemaphores = &b->image_available; submit.pWaitDstStageMask = &wait; - submit.commandBufferCount = 1; submit.pCommandBuffers = &b->command_buffer; - submit.signalSemaphoreCount = 1; submit.pSignalSemaphores = &b->render_finished; - if (vkQueueSubmit(b->queue, 1, &submit, b->frame_fence) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image queue submit failed"); - VkPresentInfoKHR present{}; present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - present.waitSemaphoreCount = 1; present.pWaitSemaphores = &b->render_finished; - present.swapchainCount = 1; present.pSwapchains = &b->swapchain; present.pImageIndices = &image_index; - VkResult result = vkQueuePresentKHR(b->queue, &present); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - b->resized = true; - if (result == VK_SUBOPTIMAL_KHR) record_presented_frame(b); - return SE_GPU_OK; - } - if (result != VK_SUCCESS) return fail(b, SE_GPU_VULKAN_ERROR, "guest image present failed"); - if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "guest image presentation completion failed"); - record_presented_frame(b); - return SE_GPU_OK; -} - -se_gpu_result create_commands(se_gpu_backend* b) { - VkPipelineCacheCreateInfo cache{}; cache.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - if (vkCreatePipelineCache(b->device, &cache, nullptr, &b->pipeline_cache) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vkCreatePipelineCache failed"); - VkCommandPoolCreateInfo pool{}; - pool.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - pool.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - pool.queueFamilyIndex = b->queue_family; - if (vkCreateCommandPool(b->device, &pool, nullptr, &b->command_pool) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vkCreateCommandPool failed"); - VkCommandBufferAllocateInfo allocate{}; - allocate.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocate.commandPool = b->command_pool; allocate.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocate.commandBufferCount = 1; - if (vkAllocateCommandBuffers(b->device, &allocate, &b->command_buffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vkAllocateCommandBuffers failed"); - VkSemaphoreCreateInfo semaphore{}; semaphore.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - if (vkCreateSemaphore(b->device, &semaphore, nullptr, &b->image_available) != VK_SUCCESS || - vkCreateSemaphore(b->device, &semaphore, nullptr, &b->render_finished) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vkCreateSemaphore failed"); - VkFenceCreateInfo fence{}; fence.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fence.flags = VK_FENCE_CREATE_SIGNALED_BIT; - if (vkCreateFence(b->device, &fence, nullptr, &b->frame_fence) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vkCreateFence failed"); - return SE_GPU_OK; -} - -se_gpu_result create_swapchain(se_gpu_backend* b) { - VkSurfaceCapabilitiesKHR capabilities{}; - if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(b->physical_device, b->surface, &capabilities) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "surface capability query failed"); - int width{}, height{}; SDL_GetWindowSizeInPixels(b->window, &width, &height); - if (width <= 0 || height <= 0) return SE_GPU_NOT_READY; - uint32_t count{}; vkGetPhysicalDeviceSurfaceFormatsKHR(b->physical_device, b->surface, &count, nullptr); - if (!count) return fail(b, SE_GPU_VULKAN_ERROR, "surface has no formats"); - std::vector formats(count); - vkGetPhysicalDeviceSurfaceFormatsKHR(b->physical_device, b->surface, &count, formats.data()); - VkSurfaceFormatKHR selected = formats.front(); - - // Guest render targets are already encoded for display. Prefer a UNORM - // swapchain everywhere so presentation does not apply an additional sRGB - // transfer function. BGRA avoids a channel shuffle on the common path; - // RGBA UNORM is the next-best native surface format. - for (const auto& format : formats) { - if (format.format == VK_FORMAT_B8G8R8A8_UNORM) { - selected = format; break; - } - } - if (selected.format != VK_FORMAT_B8G8R8A8_UNORM) { - for (const auto& format : formats) { - if (format.format == VK_FORMAT_R8G8B8A8_UNORM) { - selected = format; break; - } - } - } - VkExtent2D extent = capabilities.currentExtent; - if (extent.width == UINT32_MAX) { - extent.width = std::clamp(static_cast(width), capabilities.minImageExtent.width, - capabilities.maxImageExtent.width); - extent.height = std::clamp(static_cast(height), capabilities.minImageExtent.height, - capabilities.maxImageExtent.height); - } - uint32_t image_count = capabilities.minImageCount + 1; - if (capabilities.maxImageCount) image_count = std::min(image_count, capabilities.maxImageCount); - VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - if ((capabilities.supportedCompositeAlpha & alpha) == 0) { - const VkCompositeAlphaFlagBitsKHR choices[]{VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, - VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR}; - for (auto choice : choices) if ((capabilities.supportedCompositeAlpha & choice) != 0) { alpha = choice; break; } - } - VkSwapchainCreateInfoKHR info{}; info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - info.surface = b->surface; info.minImageCount = image_count; info.imageFormat = selected.format; - info.imageColorSpace = selected.colorSpace; info.imageExtent = extent; info.imageArrayLayers = 1; - info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; info.preTransform = capabilities.currentTransform; - info.compositeAlpha = alpha; info.presentMode = VK_PRESENT_MODE_FIFO_KHR; info.clipped = VK_TRUE; - info.oldSwapchain = b->swapchain; - VkSwapchainKHR replacement{}; VkResult result = vkCreateSwapchainKHR(b->device, &info, nullptr, &replacement); - if (result != VK_SUCCESS) return fail(b, SE_GPU_VULKAN_ERROR, "vkCreateSwapchainKHR failed"); - if (b->swapchain) vkDestroySwapchainKHR(b->device, b->swapchain, nullptr); - b->swapchain = replacement; b->swapchain_format = selected.format; b->extent = extent; - vkGetSwapchainImagesKHR(b->device, b->swapchain, &image_count, nullptr); - b->swapchain_images.resize(image_count); - vkGetSwapchainImagesKHR(b->device, b->swapchain, &image_count, b->swapchain_images.data()); - b->resized = false; return SE_GPU_OK; -} - -se_gpu_result ensure_staging(se_gpu_backend* b, VkDeviceSize size) { - if (b->staging_capacity >= size) return SE_GPU_OK; - vkDeviceWaitIdle(b->device); - if (b->staging_map) vkUnmapMemory(b->device, b->staging_memory); - if (b->staging) vkDestroyBuffer(b->device, b->staging, nullptr); - if (b->staging_memory) vkFreeMemory(b->device, b->staging_memory, nullptr); - b->staging_map = nullptr; b->staging = {}; b->staging_memory = {}; b->staging_capacity = 0; - VkBufferCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.size = size; - info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(b->device, &info, nullptr, &b->staging) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "staging buffer creation failed"); - VkMemoryRequirements requirements{}; vkGetBufferMemoryRequirements(b->device, b->staging, &requirements); - uint32_t type = memory_type(b, requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - if (type == UINT32_MAX) return fail(b, SE_GPU_VULKAN_ERROR, "no host-visible memory type"); - VkMemoryAllocateInfo allocation{}; allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = type; - if (vkAllocateMemory(b->device, &allocation, nullptr, &b->staging_memory) != VK_SUCCESS || - vkBindBufferMemory(b->device, b->staging, b->staging_memory, 0) != VK_SUCCESS || - vkMapMemory(b->device, b->staging_memory, 0, requirements.size, 0, &b->staging_map) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "staging memory allocation failed"); - b->staging_capacity = requirements.size; return SE_GPU_OK; -} - -void refresh_gamepad(se_gpu_backend* b) { - if (b->gamepad && SDL_GamepadConnected(b->gamepad)) return; - if (b->gamepad) { SDL_CloseGamepad(b->gamepad); b->gamepad = nullptr; } - int count{}; SDL_JoystickID* ids = SDL_GetGamepads(&count); - if (ids && count > 0) b->gamepad = SDL_OpenGamepad(ids[0]); - SDL_free(ids); -} -void set_key(se_gpu_input* state, uint32_t virtual_key, bool down) { - if (down && virtual_key < 256) state->virtual_keys[virtual_key / 32] |= 1u << (virtual_key % 32); -} -uint8_t stick_value(int16_t value) { - return static_cast((static_cast(value) + 32768) * 255 / 65535); -} -uint8_t trigger_value(int16_t value) { - return static_cast(std::clamp(static_cast(value), 0, 32767) * 255 / 32767); -} - -VkPrimitiveTopology primitive_topology(uint32_t type) { - switch (type) { - case 1: return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; - case 2: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; - case 3: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; - case 5: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; - case 6: case 0x11: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; - default: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - } -} -VkBlendFactor blend_factor(uint32_t value) { - switch (value) { - case 0: return VK_BLEND_FACTOR_ZERO; case 1: return VK_BLEND_FACTOR_ONE; - case 2: return VK_BLEND_FACTOR_SRC_COLOR; case 3: return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; - case 4: return VK_BLEND_FACTOR_SRC_ALPHA; case 5: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - case 6: return VK_BLEND_FACTOR_DST_ALPHA; case 7: return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; - case 8: return VK_BLEND_FACTOR_DST_COLOR; case 9: return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; - case 10: return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; case 13: return VK_BLEND_FACTOR_CONSTANT_COLOR; - case 14: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; case 15: return VK_BLEND_FACTOR_SRC1_COLOR; - case 16: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; case 17: return VK_BLEND_FACTOR_SRC1_ALPHA; - case 18: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; case 19: return VK_BLEND_FACTOR_CONSTANT_ALPHA; - case 20: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; default: return VK_BLEND_FACTOR_ONE; - } -} -VkBlendOp blend_op(uint32_t value) { - switch (value) { case 1: return VK_BLEND_OP_SUBTRACT; case 2: return VK_BLEND_OP_MIN; - case 3: return VK_BLEND_OP_MAX; case 4: return VK_BLEND_OP_REVERSE_SUBTRACT; default: return VK_BLEND_OP_ADD; } -} -VkFormat vertex_format(uint32_t data, uint32_t number, uint32_t components) { - if (data == 1) return number == 1 ? VK_FORMAT_R8_SNORM : number == 4 ? VK_FORMAT_R8_UINT : - number == 5 ? VK_FORMAT_R8_SINT : number == 9 ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; - if (data == 2) return number == 1 ? VK_FORMAT_R16_SNORM : number == 4 ? VK_FORMAT_R16_UINT : - number == 5 ? VK_FORMAT_R16_SINT : number == 7 ? VK_FORMAT_R16_SFLOAT : VK_FORMAT_R16_UNORM; - if (data == 3) return number == 1 ? VK_FORMAT_R8G8_SNORM : number == 4 ? VK_FORMAT_R8G8_UINT : - number == 5 ? VK_FORMAT_R8G8_SINT : number == 9 ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; - if (data == 4) return number == 4 ? VK_FORMAT_R32_UINT : number == 5 ? VK_FORMAT_R32_SINT : VK_FORMAT_R32_SFLOAT; - if (data == 5) return number == 1 ? VK_FORMAT_R16G16_SNORM : number == 4 ? VK_FORMAT_R16G16_UINT : - number == 5 ? VK_FORMAT_R16G16_SINT : number == 7 ? VK_FORMAT_R16G16_SFLOAT : VK_FORMAT_R16G16_UNORM; - if (data == 10) return number == 1 ? VK_FORMAT_R8G8B8A8_SNORM : number == 4 ? VK_FORMAT_R8G8B8A8_UINT : - number == 5 ? VK_FORMAT_R8G8B8A8_SINT : number == 9 ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; - if (data == 11) return number == 4 ? VK_FORMAT_R32G32_UINT : number == 5 ? VK_FORMAT_R32G32_SINT : VK_FORMAT_R32G32_SFLOAT; - if (data == 12) return number == 1 || number == 6 ? VK_FORMAT_R16G16B16A16_SNORM : - number == 4 ? VK_FORMAT_R16G16B16A16_UINT : number == 5 ? VK_FORMAT_R16G16B16A16_SINT : - number == 7 ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R16G16B16A16_UNORM; - if (data == 13) return number == 4 ? VK_FORMAT_R32G32B32_UINT : number == 5 ? VK_FORMAT_R32G32B32_SINT : VK_FORMAT_R32G32B32_SFLOAT; - if (data == 14) return number == 4 ? VK_FORMAT_R32G32B32A32_UINT : number == 5 ? VK_FORMAT_R32G32B32A32_SINT : VK_FORMAT_R32G32B32A32_SFLOAT; - switch (components) { case 2: return VK_FORMAT_R32G32_SFLOAT; case 3: return VK_FORMAT_R32G32B32_SFLOAT; - case 4: return VK_FORMAT_R32G32B32A32_SFLOAT; default: return VK_FORMAT_R32_SFLOAT; } -} - -VkSamplerAddressMode sampler_address(uint32_t value) { - switch (value) { case 0: return VK_SAMPLER_ADDRESS_MODE_REPEAT; - case 1: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; - case 2: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - case 3: case 5: case 7: return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; - case 4: case 6: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - default: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; } -} -VkCompareOp compare_op(uint32_t value) { - switch (value) { case 1: return VK_COMPARE_OP_LESS; case 2: return VK_COMPARE_OP_EQUAL; - case 3: return VK_COMPARE_OP_LESS_OR_EQUAL; case 4: return VK_COMPARE_OP_GREATER; - case 5: return VK_COMPARE_OP_NOT_EQUAL; case 6: return VK_COMPARE_OP_GREATER_OR_EQUAL; - case 7: return VK_COMPARE_OP_ALWAYS; default: return VK_COMPARE_OP_NEVER; } -} -se_gpu_result create_sampler(se_gpu_backend* b, const se_gpu_sampler& source, VkSampler* output) { - uint32_t compare = (source.words[0] >> 12) & 7u; - uint32_t border = source.words[3] >> 30; - uint32_t bias_bits = source.words[2] & 0x3fffu; - int32_t signed_bias = static_cast((bias_bits ^ 0x2000u) - 0x2000u); - VkSamplerCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - info.addressModeU = sampler_address(source.words[0] & 7u); - info.addressModeV = sampler_address((source.words[0] >> 3) & 7u); - info.addressModeW = sampler_address((source.words[0] >> 6) & 7u); - info.magFilter = ((source.words[2] >> 20) & 3u) == 1 || ((source.words[2] >> 20) & 3u) == 3 ? - VK_FILTER_LINEAR : VK_FILTER_NEAREST; - info.minFilter = ((source.words[2] >> 22) & 3u) == 1 || ((source.words[2] >> 22) & 3u) == 3 ? - VK_FILTER_LINEAR : VK_FILTER_NEAREST; - info.mipmapMode = ((source.words[2] >> 26) & 3u) == 2 ? - VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST; - info.mipLodBias = static_cast(signed_bias) / 256.0f; - info.minLod = static_cast(source.words[1] & 0xfffu) / 256.0f; - info.maxLod = std::max(info.minLod, - static_cast((source.words[1] >> 12) & 0xfffu) / 256.0f); - info.compareEnable = compare ? VK_TRUE : VK_FALSE; info.compareOp = compare_op(compare); - info.borderColor = border == 1 ? VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK : - border == 2 ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE : VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; - return vkCreateSampler(b->device, &info, nullptr, output) == VK_SUCCESS ? SE_GPU_OK : - fail(b, SE_GPU_VULKAN_ERROR, "guest sampler creation failed"); -} - -VkComponentSwizzle component_swizzle(uint32_t selector) { - switch (selector) { case 0: return VK_COMPONENT_SWIZZLE_ZERO; case 1: return VK_COMPONENT_SWIZZLE_ONE; - case 4: return VK_COMPONENT_SWIZZLE_R; case 5: return VK_COMPONENT_SWIZZLE_G; - case 6: return VK_COMPONENT_SWIZZLE_B; case 7: return VK_COMPONENT_SWIZZLE_A; - default: return VK_COMPONENT_SWIZZLE_IDENTITY; } -} -se_gpu_result create_texture_view(se_gpu_backend* b, guest_image* image, - const se_gpu_texture& texture, VkImageView* output) { - uint32_t select = texture.dst_select ? texture.dst_select : 0xfacu; - if (texture.is_storage || (select == 0xfacu && texture.mip_level == 0)) { - *output = image->view; return SE_GPU_OK; - } - if (texture.mip_level != 0) - return fail(b, SE_GPU_INVALID_ARGUMENT, "guest texture requests an unavailable mip level"); - VkImageViewCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - info.image = image->image; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = image->format; - info.components = {component_swizzle(select & 7u), component_swizzle((select >> 3) & 7u), - component_swizzle((select >> 6) & 7u), component_swizzle((select >> 9) & 7u)}; - info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - return vkCreateImageView(b->device, &info, nullptr, output) == VK_SUCCESS ? SE_GPU_OK : - fail(b, SE_GPU_VULKAN_ERROR, "swizzled guest texture view creation failed"); -} -} -extern "C" { -uint32_t SE_GPU_CALL se_gpu_abi_version(void) { return SE_GPU_ABI_VERSION; } -const char* SE_GPU_CALL se_gpu_last_error(const se_gpu_backend* b) { - return b ? b->error.c_str() : global_error.c_str(); -} -se_gpu_result SE_GPU_CALL se_gpu_create(const se_gpu_create_info* info, se_gpu_backend** out) { - if (!info || !out || info->struct_size < sizeof(*info) || !info->width || !info->height) { - return fail(nullptr, SE_GPU_INVALID_ARGUMENT, "invalid native GPU create info"); - } - *out = nullptr; - if (info->abi_version != SE_GPU_ABI_VERSION) - return fail(nullptr, SE_GPU_INCOMPATIBLE_ABI, "native GPU ABI version mismatch"); - auto* b = new (std::nothrow) se_gpu_backend{}; - if (!b) return fail(nullptr, SE_GPU_OUT_OF_MEMORY, "native GPU backend allocation failed"); - b->log = info->log; b->log_user = info->log_user; - b->base_title = info->title_utf8 ? info->title_utf8 : "SharpEmu"; - const char* perf_hud = std::getenv("SHARPEMU_PERF_HUD"); - b->fps_counter_enabled = !perf_hud || std::strcmp(perf_hud, "0") != 0; - // SharpEmu owns the process entry point (and is a WinExe on Windows), so - // SDL never gets its usual SDL_main bootstrap. Mark the host entry point - // ready before initializing video from the dedicated renderer thread. - SDL_SetMainReady(); - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) - return abandon(b, SE_GPU_PLATFORM_ERROR, SDL_GetError()); - int window_width = static_cast(std::min(info->width, static_cast(INT32_MAX))); - int window_height = static_cast(std::min(info->height, static_cast(INT32_MAX))); - SDL_Rect usable{}; - SDL_DisplayID display = SDL_GetPrimaryDisplay(); - if (display && SDL_GetDisplayUsableBounds(display, &usable) && usable.w > 0 && usable.h > 0) { - int maximum_width = std::max(usable.w * 9 / 10, 1); - int maximum_height = std::max(usable.h * 9 / 10, 1); - if (window_width > maximum_width || window_height > maximum_height) { - double scale = std::min(static_cast(maximum_width) / window_width, - static_cast(maximum_height) / window_height); - window_width = std::max(static_cast(window_width * scale), 1); - window_height = std::max(static_cast(window_height * scale), 1); - } - } - b->window = SDL_CreateWindow(info->title_utf8 ? info->title_utf8 : "SharpEmu", - window_width, window_height, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); - if (!b->window) return abandon(b, SE_GPU_PLATFORM_ERROR, SDL_GetError()); - if (!SDL_ShowWindow(b->window)) return abandon(b, SE_GPU_PLATFORM_ERROR, SDL_GetError()); - SDL_RaiseWindow(b->window); - SDL_PumpEvents(); - uint32_t count{}; const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&count); - VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "SharpEmu", 1, - "SharpEmu native guest GPU", 1, VK_API_VERSION_1_2}; - VkInstanceCreateInfo create{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, nullptr, 0, &app, - 0, nullptr, count, extensions}; - VkResult vr = vkCreateInstance(&create, nullptr, &b->instance); - if (vr != VK_SUCCESS) return abandon(b, SE_GPU_VULKAN_ERROR, - "vkCreateInstance failed (VkResult " + std::to_string(vr) + ")"); - if (!SDL_Vulkan_CreateSurface(b->window, b->instance, nullptr, &b->surface)) { - return abandon(b, SE_GPU_VULKAN_ERROR, SDL_GetError()); - } - uint32_t device_count{}; vkEnumeratePhysicalDevices(b->instance, &device_count, nullptr); - if (!device_count) return abandon(b, SE_GPU_VULKAN_ERROR, "no Vulkan device"); - VkPhysicalDevice devices[16]{}; device_count = device_count > 16 ? 16 : device_count; - vkEnumeratePhysicalDevices(b->instance, &device_count, devices); - for (uint32_t d = 0; d < device_count && !b->physical_device; ++d) { - uint32_t families{}; vkGetPhysicalDeviceQueueFamilyProperties(devices[d], &families, nullptr); - std::vector properties(families); - vkGetPhysicalDeviceQueueFamilyProperties(devices[d], &families, properties.data()); - for (uint32_t q = 0; q < families; ++q) { VkBool32 present{}; - vkGetPhysicalDeviceSurfaceSupportKHR(devices[d], q, b->surface, &present); - if (present && (properties[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { - b->physical_device = devices[d]; b->queue_family = q; break; - } - } - } - if (!b->physical_device) return abandon(b, SE_GPU_VULKAN_ERROR, "no presentation queue"); - float priority = 1; VkDeviceQueueCreateInfo queue{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - nullptr, 0, b->queue_family, 1, &priority}; - const char* device_extensions[]{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; - VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, nullptr, 0, 1, &queue, - 0, nullptr, 1, device_extensions, nullptr}; - vr = vkCreateDevice(b->physical_device, &device, nullptr, &b->device); - if (vr != VK_SUCCESS) return abandon(b, SE_GPU_VULKAN_ERROR, - "vkCreateDevice failed (VkResult " + std::to_string(vr) + ")"); - vkGetDeviceQueue(b->device, b->queue_family, 0, &b->queue); - se_gpu_result result = create_commands(b); - if (result == SE_GPU_OK) result = create_swapchain(b); - if (result != SE_GPU_OK) { global_error = b->error; cleanup(b); return result; } - *out = b; return SE_GPU_OK; -} -void SE_GPU_CALL se_gpu_destroy(se_gpu_backend* b) { cleanup(b); } -se_gpu_result SE_GPU_CALL se_gpu_poll(se_gpu_backend* b, uint32_t* close) { - if (!b || !close) return SE_GPU_INVALID_ARGUMENT; - *close = 0; - SDL_Event e{}; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_EVENT_QUIT || e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) *close = 1; - if (e.type == SDL_EVENT_WINDOW_RESIZED || e.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) b->resized = true; - } - return SE_GPU_OK; -} -se_gpu_result SE_GPU_CALL se_gpu_input_snapshot(se_gpu_backend* b, se_gpu_input* state) { - if (!b || !state || state->struct_size < sizeof(*state)) return SE_GPU_INVALID_ARGUMENT; - uint32_t size = state->struct_size; std::memset(state, 0, sizeof(*state)); state->struct_size = size; - state->keyboard_focused = (SDL_GetWindowFlags(b->window) & SDL_WINDOW_INPUT_FOCUS) != 0 ? 1u : 0u; - int count{}; const bool* keys = SDL_GetKeyboardState(&count); - auto down = [keys, count](SDL_Scancode key) { return static_cast(key) < count && keys[key]; }; - set_key(state, 0x08, down(SDL_SCANCODE_BACKSPACE)); set_key(state, 0x09, down(SDL_SCANCODE_TAB)); - set_key(state, 0x0d, down(SDL_SCANCODE_RETURN)); set_key(state, 0x1b, down(SDL_SCANCODE_ESCAPE)); - set_key(state, 0x25, down(SDL_SCANCODE_LEFT)); set_key(state, 0x26, down(SDL_SCANCODE_UP)); - set_key(state, 0x27, down(SDL_SCANCODE_RIGHT)); set_key(state, 0x28, down(SDL_SCANCODE_DOWN)); - for (uint32_t index = 0; index < 26; ++index) - set_key(state, 0x41u + index, down(static_cast(SDL_SCANCODE_A + index))); - refresh_gamepad(b); if (!b->gamepad) return SE_GPU_OK; - state->gamepad_connected = 1; - auto button = [b](SDL_GamepadButton value) { return SDL_GetGamepadButton(b->gamepad, value); }; - if (button(SDL_GAMEPAD_BUTTON_DPAD_UP)) state->gamepad_buttons |= 1u << 0; - if (button(SDL_GAMEPAD_BUTTON_DPAD_DOWN)) state->gamepad_buttons |= 1u << 1; - if (button(SDL_GAMEPAD_BUTTON_DPAD_LEFT)) state->gamepad_buttons |= 1u << 2; - if (button(SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) state->gamepad_buttons |= 1u << 3; - if (button(SDL_GAMEPAD_BUTTON_SOUTH)) state->gamepad_buttons |= 1u << 4; - if (button(SDL_GAMEPAD_BUTTON_EAST)) state->gamepad_buttons |= 1u << 5; - if (button(SDL_GAMEPAD_BUTTON_WEST)) state->gamepad_buttons |= 1u << 6; - if (button(SDL_GAMEPAD_BUTTON_NORTH)) state->gamepad_buttons |= 1u << 7; - if (button(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) state->gamepad_buttons |= 1u << 8; - if (button(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) state->gamepad_buttons |= 1u << 9; - if (button(SDL_GAMEPAD_BUTTON_LEFT_STICK)) state->gamepad_buttons |= 1u << 12; - if (button(SDL_GAMEPAD_BUTTON_RIGHT_STICK)) state->gamepad_buttons |= 1u << 13; - if (button(SDL_GAMEPAD_BUTTON_START)) state->gamepad_buttons |= 1u << 14; - if (button(SDL_GAMEPAD_BUTTON_BACK)) state->gamepad_buttons |= 1u << 15; - state->left_x = stick_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_LEFTX)); - state->left_y = stick_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_LEFTY)); - state->right_x = stick_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_RIGHTX)); - state->right_y = stick_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_RIGHTY)); - state->left_trigger = trigger_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER)); - state->right_trigger = trigger_value(SDL_GetGamepadAxis(b->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)); - if (state->left_trigger > 64) state->gamepad_buttons |= 1u << 10; - if (state->right_trigger > 64) state->gamepad_buttons |= 1u << 11; - const char* name = SDL_GetGamepadName(b->gamepad); - if (name) { -#if defined(_WIN32) - strncpy_s(state->gamepad_name_utf8, sizeof(state->gamepad_name_utf8), name, _TRUNCATE); -#else - SDL_strlcpy(state->gamepad_name_utf8, name, sizeof(state->gamepad_name_utf8)); -#endif - } - return SE_GPU_OK; -} -se_gpu_result SE_GPU_CALL se_gpu_present_bgra( - se_gpu_backend* b, const void* pixels, size_t size, uint32_t width, uint32_t height, uint32_t pitch) { - if (!b || !pixels || !width || !height || pitch < width * 4u || size < static_cast(pitch) * height) - return SE_GPU_INVALID_ARGUMENT; - if (vkWaitForFences(b->device, 1, &b->frame_fence, VK_TRUE, UINT64_MAX) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "frame fence wait failed"); - if (b->resized) { vkDeviceWaitIdle(b->device); se_gpu_result recreated = create_swapchain(b); - if (recreated != SE_GPU_OK) return recreated; } - uint32_t image_index{}; - VkResult result = vkAcquireNextImageKHR(b->device, b->swapchain, UINT64_MAX, - b->image_available, VK_NULL_HANDLE, &image_index); - if (result == VK_ERROR_OUT_OF_DATE_KHR) { b->resized = true; return SE_GPU_NOT_READY; } - if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) - return fail(b, SE_GPU_VULKAN_ERROR, "swapchain image acquisition failed"); - uint32_t copy_width = std::min(width, b->extent.width), copy_height = std::min(height, b->extent.height); - VkDeviceSize packed_size = static_cast(copy_width) * copy_height * 4u; - se_gpu_result staged = ensure_staging(b, packed_size); if (staged != SE_GPU_OK) return staged; - const auto* source = static_cast(pixels); auto* destination = static_cast(b->staging_map); - bool bgra = b->swapchain_format == VK_FORMAT_B8G8R8A8_UNORM || b->swapchain_format == VK_FORMAT_B8G8R8A8_SRGB; - for (uint32_t y = 0; y < copy_height; ++y) { - const uint8_t* row = source + static_cast(y) * pitch; - uint8_t* output = destination + static_cast(y) * copy_width * 4u; - if (bgra) std::memcpy(output, row, static_cast(copy_width) * 4u); - else for (uint32_t x = 0; x < copy_width; ++x) { - output[x * 4] = row[x * 4 + 2]; output[x * 4 + 1] = row[x * 4 + 1]; - output[x * 4 + 2] = row[x * 4]; output[x * 4 + 3] = row[x * 4 + 3]; - } - } - vkResetFences(b->device, 1, &b->frame_fence); vkResetCommandBuffer(b->command_buffer, 0); - VkCommandBufferBeginInfo begin{}; begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - if (vkBeginCommandBuffer(b->command_buffer, &begin) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "command buffer begin failed"); - VkImageMemoryBarrier transfer{}; transfer.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - transfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; transfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - transfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; transfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - transfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; transfer.image = b->swapchain_images[image_index]; - transfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(b->command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &transfer); - VkBufferImageCopy copy{}; copy.bufferRowLength = copy_width; copy.bufferImageHeight = copy_height; - copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.imageExtent = {copy_width, copy_height, 1}; - vkCmdCopyBufferToImage(b->command_buffer, b->staging, b->swapchain_images[image_index], - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); - VkImageMemoryBarrier present = transfer; present.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - present.dstAccessMask = 0; present.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - present.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - vkCmdPipelineBarrier(b->command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &present); - if (vkEndCommandBuffer(b->command_buffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "command buffer end failed"); - VkPipelineStageFlags wait = VK_PIPELINE_STAGE_TRANSFER_BIT; VkSubmitInfo submit{}; - submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit.waitSemaphoreCount = 1; - submit.pWaitSemaphores = &b->image_available; submit.pWaitDstStageMask = &wait; - submit.commandBufferCount = 1; submit.pCommandBuffers = &b->command_buffer; - submit.signalSemaphoreCount = 1; submit.pSignalSemaphores = &b->render_finished; - if (vkQueueSubmit(b->queue, 1, &submit, b->frame_fence) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "queue submit failed"); - VkPresentInfoKHR info{}; info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - info.waitSemaphoreCount = 1; info.pWaitSemaphores = &b->render_finished; - info.swapchainCount = 1; info.pSwapchains = &b->swapchain; info.pImageIndices = &image_index; - result = vkQueuePresentKHR(b->queue, &info); - if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - b->resized = true; - if (result == VK_SUBOPTIMAL_KHR) record_presented_frame(b); - return SE_GPU_OK; - } - if (result != VK_SUCCESS) return fail(b, SE_GPU_VULKAN_ERROR, "queue present failed"); - record_presented_frame(b); - return SE_GPU_OK; -} -se_gpu_result SE_GPU_CALL se_gpu_submit_draw(se_gpu_backend* b, const se_gpu_draw* work) { - if (!b || !work || work->struct_size < sizeof(*work) || !work->pixel_spirv.data || - !work->vertex_spirv.data || !work->pixel_spirv.size || !work->vertex_spirv.size || - (work->pixel_spirv.size % 4) != 0 || (work->vertex_spirv.size % 4) != 0 || - !work->width || !work->height || !work->vertex_count || !work->instance_count || - (work->texture_count && !work->textures) || (work->memory_buffer_count && !work->memory_buffers) || - (work->vertex_buffer_count && !work->vertex_buffers) || (work->target_count && !work->targets)) - return SE_GPU_INVALID_ARGUMENT; - std::vector global_buffers(work->memory_buffer_count); - std::vector vertex_buffers(work->vertex_buffer_count); - host_buffer index_buffer{}; - std::vector transient_images; transient_images.reserve(work->texture_count + work->target_count + 1); - std::vector textures(work->texture_count); - std::vector texture_views(work->texture_count); - uint32_t target_count = work->target_count ? work->target_count : 1; - std::vector targets(target_count); - std::vector samplers(work->texture_count); - VkDescriptorSetLayout descriptor_layout{}; VkPipelineLayout pipeline_layout{}; - VkDescriptorPool descriptor_pool{}; VkDescriptorSet descriptor_set{}; VkRenderPass render_pass{}; - VkFramebuffer framebuffer{}; VkShaderModule vertex_shader{}, pixel_shader{}; VkPipeline pipeline{}; - auto release = [&] { - if (pipeline) vkDestroyPipeline(b->device, pipeline, nullptr); - if (pixel_shader) vkDestroyShaderModule(b->device, pixel_shader, nullptr); - if (vertex_shader) vkDestroyShaderModule(b->device, vertex_shader, nullptr); - if (framebuffer) vkDestroyFramebuffer(b->device, framebuffer, nullptr); - if (render_pass) vkDestroyRenderPass(b->device, render_pass, nullptr); - if (descriptor_pool) vkDestroyDescriptorPool(b->device, descriptor_pool, nullptr); - if (pipeline_layout) vkDestroyPipelineLayout(b->device, pipeline_layout, nullptr); - if (descriptor_layout) vkDestroyDescriptorSetLayout(b->device, descriptor_layout, nullptr); - for (VkSampler sampler : samplers) if (sampler) vkDestroySampler(b->device, sampler, nullptr); - for (uint32_t index = 0; index < work->texture_count; ++index) - if (texture_views[index] && textures[index] && texture_views[index] != textures[index]->view) - vkDestroyImageView(b->device, texture_views[index], nullptr); - destroy_buffer(b, &index_buffer); - for (auto& buffer : vertex_buffers) destroy_buffer(b, &buffer); - for (auto& buffer : global_buffers) destroy_buffer(b, &buffer); - for (auto& image : transient_images) destroy_image(b, &image); - }; - se_gpu_result operation = [&]() -> se_gpu_result { - for (uint32_t index = 0; index < work->memory_buffer_count; ++index) { - const auto& source = work->memory_buffers[index]; - se_gpu_result result = create_buffer(b, source.data.data, source.data.size, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &global_buffers[index]); - if (result != SE_GPU_OK) return result; - } - for (uint32_t index = 0; index < work->vertex_buffer_count; ++index) { - const auto& source = work->vertex_buffers[index]; - se_gpu_result result = create_buffer(b, source.data.data, source.data.size, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, &vertex_buffers[index]); - if (result != SE_GPU_OK) return result; - } - if (work->index_buffer) { - se_gpu_result result = create_buffer(b, work->index_buffer->data.data, - work->index_buffer->data.size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, &index_buffer); - if (result != SE_GPU_OK) return result; - } - for (uint32_t index = 0; index < work->texture_count; ++index) { - const auto& source = work->textures[index]; guest_image* image{}; - if (source.address) { - uint32_t row_length = source.tile_mode == 0 ? std::max(source.pitch, source.width) : source.width; - se_gpu_result result = cached_image(b, source.address, source.width, source.height, - source.format, source.number_type, row_length, &source.rgba_pixels, &image); - if (result != SE_GPU_OK) return result; - } else { - transient_images.emplace_back(); image = &transient_images.back(); - se_gpu_result result = create_image(b, source.width, source.height, - source.format, source.number_type, image); - uint32_t row_length = source.tile_mode == 0 ? std::max(source.pitch, source.width) : source.width; - if (result == SE_GPU_OK) result = upload_image(b, image, source.rgba_pixels, row_length); - if (result != SE_GPU_OK) return result; - } - textures[index] = image; - se_gpu_result view_result = create_texture_view(b, image, source, &texture_views[index]); - if (view_result != SE_GPU_OK) return view_result; - } - for (uint32_t index = 0; index < target_count; ++index) { - guest_image* image{}; - if (work->target_count && work->targets[index].address) { - const auto& source = work->targets[index]; - se_gpu_result result = cached_image(b, source.address, source.width, source.height, - source.format, source.number_type, source.width, nullptr, &image); - if (result != SE_GPU_OK) return result; - } else { - transient_images.emplace_back(); image = &transient_images.back(); - uint32_t format = work->target_count ? work->targets[index].format : 10; - uint32_t number = work->target_count ? work->targets[index].number_type : 0; - se_gpu_result result = create_image(b, work->width, work->height, format, number, image); - if (result != SE_GPU_OK) return result; - } - targets[index] = image; - } - for (uint32_t index = 0; index < work->texture_count; ++index) - if (!work->textures[index].is_storage) { - se_gpu_result result = create_sampler(b, work->textures[index].sampler, &samplers[index]); - if (result != SE_GPU_OK) return result; - } - std::vector bindings; - VkShaderStageFlags resource_stages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; - if (work->memory_buffer_count) { - VkDescriptorSetLayoutBinding binding{}; binding.binding = 0; - binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - binding.descriptorCount = work->memory_buffer_count; binding.stageFlags = resource_stages; - bindings.push_back(binding); - } - for (uint32_t index = 0; index < work->texture_count; ++index) { - VkDescriptorSetLayoutBinding binding{}; binding.binding = index + 1; - binding.descriptorType = work->textures[index].is_storage ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - binding.descriptorCount = 1; binding.stageFlags = resource_stages; bindings.push_back(binding); - } - if (!bindings.empty()) { - VkDescriptorSetLayoutCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = static_cast(bindings.size()); info.pBindings = bindings.data(); - if (vkCreateDescriptorSetLayout(b->device, &info, nullptr, &descriptor_layout) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "graphics descriptor layout creation failed"); - } - VkPipelineLayoutCreateInfo pipeline_layout_info{}; - pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - if (descriptor_layout) { pipeline_layout_info.setLayoutCount = 1; pipeline_layout_info.pSetLayouts = &descriptor_layout; } - if (vkCreatePipelineLayout(b->device, &pipeline_layout_info, nullptr, &pipeline_layout) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "graphics pipeline layout creation failed"); - if (!bindings.empty()) { - std::vector sizes; - if (work->memory_buffer_count) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, work->memory_buffer_count}); - uint32_t sampled{}, storage{}; - for (uint32_t index = 0; index < work->texture_count; ++index) - work->textures[index].is_storage ? ++storage : ++sampled; - if (sampled) sizes.push_back({VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, sampled}); - if (storage) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, storage}); - VkDescriptorPoolCreateInfo pool{}; pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool.maxSets = 1; pool.poolSizeCount = static_cast(sizes.size()); pool.pPoolSizes = sizes.data(); - if (vkCreateDescriptorPool(b->device, &pool, nullptr, &descriptor_pool) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "graphics descriptor pool creation failed"); - VkDescriptorSetAllocateInfo allocate{}; allocate.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocate.descriptorPool = descriptor_pool; allocate.descriptorSetCount = 1; allocate.pSetLayouts = &descriptor_layout; - if (vkAllocateDescriptorSets(b->device, &allocate, &descriptor_set) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "graphics descriptor allocation failed"); - std::vector buffer_info(work->memory_buffer_count); - for (uint32_t index = 0; index < work->memory_buffer_count; ++index) - buffer_info[index] = {global_buffers[index].buffer, 0, global_buffers[index].size}; - std::vector image_info(work->texture_count); - std::vector writes; - if (work->memory_buffer_count) { - VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = descriptor_set; write.dstBinding = 0; write.descriptorCount = work->memory_buffer_count; - write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write.pBufferInfo = buffer_info.data(); - writes.push_back(write); - } - for (uint32_t index = 0; index < work->texture_count; ++index) { - bool storage_image = work->textures[index].is_storage != 0; - image_info[index] = {storage_image ? VK_NULL_HANDLE : samplers[index], texture_views[index], VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = descriptor_set; write.dstBinding = index + 1; write.descriptorCount = 1; - write.descriptorType = storage_image ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &image_info[index]; writes.push_back(write); - } - vkUpdateDescriptorSets(b->device, static_cast(writes.size()), writes.data(), 0, nullptr); - } - std::vector attachments(target_count); - std::vector references(target_count); - for (uint32_t index = 0; index < target_count; ++index) { - attachments[index].format = targets[index]->format; attachments[index].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[index].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[index].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[index].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[index].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - attachments[index].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - attachments[index].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - references[index] = {index, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; - } - VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = target_count; subpass.pColorAttachments = references.data(); - VkRenderPassCreateInfo pass{}; pass.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - pass.attachmentCount = target_count; pass.pAttachments = attachments.data(); pass.subpassCount = 1; pass.pSubpasses = &subpass; - if (vkCreateRenderPass(b->device, &pass, nullptr, &render_pass) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "render pass creation failed"); - std::vector views(target_count); - for (uint32_t index = 0; index < target_count; ++index) views[index] = targets[index]->view; - VkFramebufferCreateInfo frame{}; frame.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - frame.renderPass = render_pass; frame.attachmentCount = target_count; frame.pAttachments = views.data(); - frame.width = work->width; frame.height = work->height; frame.layers = 1; - if (vkCreateFramebuffer(b->device, &frame, nullptr, &framebuffer) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "framebuffer creation failed"); - VkShaderModuleCreateInfo module{}; module.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - module.codeSize = work->vertex_spirv.size; module.pCode = static_cast(work->vertex_spirv.data); - if (vkCreateShaderModule(b->device, &module, nullptr, &vertex_shader) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "vertex shader module creation failed"); - module.codeSize = work->pixel_spirv.size; module.pCode = static_cast(work->pixel_spirv.data); - if (vkCreateShaderModule(b->device, &module, nullptr, &pixel_shader) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "pixel shader module creation failed"); - VkPipelineShaderStageCreateInfo stages[2]{}; - stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; stages[0].module = vertex_shader; stages[0].pName = "main"; - stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stages[1].module = pixel_shader; stages[1].pName = "main"; - std::vector vertex_bindings(work->vertex_buffer_count); - std::vector attributes(work->vertex_buffer_count); - for (uint32_t index = 0; index < work->vertex_buffer_count; ++index) { - const auto& source = work->vertex_buffers[index]; - vertex_bindings[index] = {index, source.stride ? source.stride : std::max(source.component_count, 1u) * 4u, - VK_VERTEX_INPUT_RATE_VERTEX}; - attributes[index] = {source.location, index, - vertex_format(source.data_format, source.number_format, source.component_count), 0}; - } - VkPipelineVertexInputStateCreateInfo vertex_input{}; - vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertex_input.vertexBindingDescriptionCount = work->vertex_buffer_count; - vertex_input.pVertexBindingDescriptions = vertex_bindings.data(); - vertex_input.vertexAttributeDescriptionCount = work->vertex_buffer_count; - vertex_input.pVertexAttributeDescriptions = attributes.data(); - VkPipelineInputAssemblyStateCreateInfo assembly{}; - assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - assembly.topology = primitive_topology(work->primitive_type); - VkPipelineViewportStateCreateInfo viewport_state{}; - viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewport_state.viewportCount = 1; viewport_state.scissorCount = 1; - VkPipelineRasterizationStateCreateInfo raster{}; - raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - raster.polygonMode = VK_POLYGON_MODE_FILL; raster.cullMode = VK_CULL_MODE_NONE; - raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; raster.lineWidth = 1.0f; - VkPipelineMultisampleStateCreateInfo multisample{}; - multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - std::vector color_blends(target_count); - for (uint32_t index = 0; index < target_count; ++index) { - const se_gpu_blend* source = work->blend_count ? &work->blends[std::min(index, work->blend_count - 1)] : nullptr; - auto& blend = color_blends[index]; blend.colorWriteMask = source ? source->write_mask : 0xf; - if (source) { blend.blendEnable = source->enable; blend.srcColorBlendFactor = blend_factor(source->color_src); - blend.dstColorBlendFactor = blend_factor(source->color_dst); blend.colorBlendOp = blend_op(source->color_func); - blend.srcAlphaBlendFactor = blend_factor(source->separate_alpha ? source->alpha_src : source->color_src); - blend.dstAlphaBlendFactor = blend_factor(source->separate_alpha ? source->alpha_dst : source->color_dst); - blend.alphaBlendOp = blend_op(source->separate_alpha ? source->alpha_func : source->color_func); } - } - VkPipelineColorBlendStateCreateInfo blend_state{}; - blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - blend_state.attachmentCount = target_count; blend_state.pAttachments = color_blends.data(); - VkDynamicState dynamic_values[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo dynamic{}; dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamic.dynamicStateCount = 2; dynamic.pDynamicStates = dynamic_values; - VkGraphicsPipelineCreateInfo pipeline_info{}; pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - pipeline_info.stageCount = 2; pipeline_info.pStages = stages; pipeline_info.pVertexInputState = &vertex_input; - pipeline_info.pInputAssemblyState = &assembly; pipeline_info.pViewportState = &viewport_state; - pipeline_info.pRasterizationState = &raster; pipeline_info.pMultisampleState = &multisample; - pipeline_info.pColorBlendState = &blend_state; pipeline_info.pDynamicState = &dynamic; - pipeline_info.layout = pipeline_layout; pipeline_info.renderPass = render_pass; - if (vkCreateGraphicsPipelines(b->device, b->pipeline_cache, 1, &pipeline_info, nullptr, &pipeline) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "graphics pipeline creation failed"); - se_gpu_result begun = begin_immediate(b); if (begun != SE_GPU_OK) return begun; - for (guest_image* target : targets) image_barrier(b, target, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, - VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); - VkRenderPassBeginInfo render{}; render.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - render.renderPass = render_pass; render.framebuffer = framebuffer; - render.renderArea.extent = {work->width, work->height}; - vkCmdBeginRenderPass(b->command_buffer, &render, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(b->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - if (descriptor_set) vkCmdBindDescriptorSets(b->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipeline_layout, 0, 1, &descriptor_set, 0, nullptr); - if (!vertex_buffers.empty()) { - std::vector handles(vertex_buffers.size()); std::vector offsets(vertex_buffers.size()); - for (size_t index = 0; index < vertex_buffers.size(); ++index) { - handles[index] = vertex_buffers[index].buffer; - offsets[index] = work->vertex_buffers[index].offset_bytes < vertex_buffers[index].size ? - work->vertex_buffers[index].offset_bytes : 0; - } - vkCmdBindVertexBuffers(b->command_buffer, 0, static_cast(handles.size()), handles.data(), offsets.data()); - } - VkViewport viewport{0, 0, static_cast(work->width), static_cast(work->height), 0, 1}; - if (work->viewport) viewport = {work->viewport->x, work->viewport->y, work->viewport->width, - work->viewport->height, work->viewport->min_depth, work->viewport->max_depth}; - vkCmdSetViewport(b->command_buffer, 0, 1, &viewport); - VkRect2D scissor{{0, 0}, {work->width, work->height}}; - if (work->scissor) { int32_t x = std::clamp(work->scissor->x, 0, static_cast(work->width)); - int32_t y = std::clamp(work->scissor->y, 0, static_cast(work->height)); - scissor.offset = {x, y}; scissor.extent.width = std::min(work->scissor->width, work->width - static_cast(x)); - scissor.extent.height = std::min(work->scissor->height, work->height - static_cast(y)); } - vkCmdSetScissor(b->command_buffer, 0, 1, &scissor); - if (work->index_buffer) { - vkCmdBindIndexBuffer(b->command_buffer, index_buffer.buffer, 0, - work->index_buffer->is_32_bit ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_UINT16); - uint32_t count = static_cast(work->index_buffer->data.size / - (work->index_buffer->is_32_bit ? sizeof(uint32_t) : sizeof(uint16_t))); - vkCmdDrawIndexed(b->command_buffer, count, work->instance_count, 0, 0, 0); - } else { - uint32_t count = work->primitive_type == 0x11 ? 4 : work->vertex_count; - vkCmdDraw(b->command_buffer, count, work->instance_count, 0, 0); - } - vkCmdEndRenderPass(b->command_buffer); - for (guest_image* target : targets) image_barrier(b, target, VK_IMAGE_LAYOUT_GENERAL, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - return end_immediate(b); - }(); - if (operation == SE_GPU_OK && work->target_count == 0) operation = present_image(b, targets[0]); - release(); return operation; -} -se_gpu_result SE_GPU_CALL se_gpu_submit_compute(se_gpu_backend* b, const se_gpu_compute* work) { - if (!b || !work || work->struct_size < sizeof(*work) || !work->spirv.data || - work->spirv.size == 0 || (work->spirv.size % 4) != 0 || - !work->groups_x || !work->groups_y || !work->groups_z || - (work->texture_count && !work->textures) || (work->memory_buffer_count && !work->memory_buffers)) - return SE_GPU_INVALID_ARGUMENT; - std::vector buffers(work->memory_buffer_count); - std::vector transient_images; - std::vector images(work->texture_count); - std::vector texture_views(work->texture_count); - std::vector samplers(work->texture_count); - VkDescriptorSetLayout descriptor_layout{}; VkPipelineLayout pipeline_layout{}; - VkDescriptorPool descriptor_pool{}; VkDescriptorSet descriptor_set{}; - VkShaderModule shader{}; VkPipeline pipeline{}; - auto release = [&] { - if (pipeline) vkDestroyPipeline(b->device, pipeline, nullptr); - if (shader) vkDestroyShaderModule(b->device, shader, nullptr); - if (descriptor_pool) vkDestroyDescriptorPool(b->device, descriptor_pool, nullptr); - if (pipeline_layout) vkDestroyPipelineLayout(b->device, pipeline_layout, nullptr); - if (descriptor_layout) vkDestroyDescriptorSetLayout(b->device, descriptor_layout, nullptr); - for (VkSampler sampler : samplers) if (sampler) vkDestroySampler(b->device, sampler, nullptr); - for (uint32_t index = 0; index < work->texture_count; ++index) - if (texture_views[index] && images[index] && texture_views[index] != images[index]->view) - vkDestroyImageView(b->device, texture_views[index], nullptr); - for (auto& image : transient_images) destroy_image(b, &image); - for (auto& buffer : buffers) destroy_buffer(b, &buffer); - }; - se_gpu_result operation = [&]() -> se_gpu_result { - for (uint32_t index = 0; index < work->memory_buffer_count; ++index) { - const auto& source = work->memory_buffers[index]; - se_gpu_result result = create_buffer(b, source.data.data, source.data.size, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &buffers[index]); - if (result != SE_GPU_OK) return result; - } - transient_images.reserve(work->texture_count); - for (uint32_t index = 0; index < work->texture_count; ++index) { - const auto& source = work->textures[index]; guest_image* image{}; - if (source.address) { - uint32_t row_length = source.tile_mode == 0 ? std::max(source.pitch, source.width) : source.width; - se_gpu_result result = cached_image(b, source.address, source.width, source.height, - source.format, source.number_type, row_length, &source.rgba_pixels, &image); - if (result != SE_GPU_OK) return result; - } else { - transient_images.emplace_back(); image = &transient_images.back(); - se_gpu_result result = create_image(b, source.width, source.height, - source.format, source.number_type, image); - uint32_t row_length = source.tile_mode == 0 ? std::max(source.pitch, source.width) : source.width; - if (result == SE_GPU_OK) result = upload_image(b, image, source.rgba_pixels, row_length); - if (result != SE_GPU_OK) return result; - } - images[index] = image; - se_gpu_result view_result = create_texture_view(b, image, source, &texture_views[index]); - if (view_result != SE_GPU_OK) return view_result; - } - for (uint32_t index = 0; index < work->texture_count; ++index) - if (!work->textures[index].is_storage) { - se_gpu_result result = create_sampler(b, work->textures[index].sampler, &samplers[index]); - if (result != SE_GPU_OK) return result; - } - std::vector bindings; - if (work->memory_buffer_count) { - VkDescriptorSetLayoutBinding binding{}; binding.binding = 0; - binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - binding.descriptorCount = work->memory_buffer_count; binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; - bindings.push_back(binding); - } - for (uint32_t index = 0; index < work->texture_count; ++index) { - VkDescriptorSetLayoutBinding binding{}; binding.binding = index + 1; - binding.descriptorType = work->textures[index].is_storage ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - binding.descriptorCount = 1; binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; bindings.push_back(binding); - } - if (!bindings.empty()) { - VkDescriptorSetLayoutCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = static_cast(bindings.size()); info.pBindings = bindings.data(); - if (vkCreateDescriptorSetLayout(b->device, &info, nullptr, &descriptor_layout) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute descriptor layout creation failed"); - } - VkPipelineLayoutCreateInfo pipeline_layout_info{}; - pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - if (descriptor_layout) { pipeline_layout_info.setLayoutCount = 1; pipeline_layout_info.pSetLayouts = &descriptor_layout; } - if (vkCreatePipelineLayout(b->device, &pipeline_layout_info, nullptr, &pipeline_layout) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute pipeline layout creation failed"); - if (!bindings.empty()) { - std::vector sizes; - if (work->memory_buffer_count) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, work->memory_buffer_count}); - uint32_t sampled{}, storage{}; - for (uint32_t index = 0; index < work->texture_count; ++index) - work->textures[index].is_storage ? ++storage : ++sampled; - if (sampled) sizes.push_back({VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, sampled}); - if (storage) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, storage}); - VkDescriptorPoolCreateInfo pool{}; pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool.maxSets = 1; pool.poolSizeCount = static_cast(sizes.size()); pool.pPoolSizes = sizes.data(); - if (vkCreateDescriptorPool(b->device, &pool, nullptr, &descriptor_pool) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute descriptor pool creation failed"); - VkDescriptorSetAllocateInfo allocate{}; allocate.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - allocate.descriptorPool = descriptor_pool; allocate.descriptorSetCount = 1; - allocate.pSetLayouts = &descriptor_layout; - if (vkAllocateDescriptorSets(b->device, &allocate, &descriptor_set) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute descriptor allocation failed"); - std::vector buffer_info(work->memory_buffer_count); - for (uint32_t index = 0; index < work->memory_buffer_count; ++index) - buffer_info[index] = {buffers[index].buffer, 0, buffers[index].size}; - std::vector image_info(work->texture_count); - std::vector writes; - if (work->memory_buffer_count) { - VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = descriptor_set; write.dstBinding = 0; - write.descriptorCount = work->memory_buffer_count; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - write.pBufferInfo = buffer_info.data(); writes.push_back(write); - } - for (uint32_t index = 0; index < work->texture_count; ++index) { - bool storage_image = work->textures[index].is_storage != 0; - image_info[index] = {storage_image ? VK_NULL_HANDLE : samplers[index], texture_views[index], - VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - write.dstSet = descriptor_set; write.dstBinding = index + 1; write.descriptorCount = 1; - write.descriptorType = storage_image ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &image_info[index]; - writes.push_back(write); - } - vkUpdateDescriptorSets(b->device, static_cast(writes.size()), writes.data(), 0, nullptr); - } - VkShaderModuleCreateInfo shader_info{}; shader_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - shader_info.codeSize = work->spirv.size; shader_info.pCode = static_cast(work->spirv.data); - if (vkCreateShaderModule(b->device, &shader_info, nullptr, &shader) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute shader module creation failed"); - VkComputePipelineCreateInfo pipeline_info{}; pipeline_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; - pipeline_info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - pipeline_info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; pipeline_info.stage.module = shader; - pipeline_info.stage.pName = "main"; pipeline_info.layout = pipeline_layout; - if (vkCreateComputePipelines(b->device, b->pipeline_cache, 1, &pipeline_info, nullptr, &pipeline) != VK_SUCCESS) - return fail(b, SE_GPU_VULKAN_ERROR, "compute pipeline creation failed"); - se_gpu_result begun = begin_immediate(b); if (begun != SE_GPU_OK) return begun; - vkCmdBindPipeline(b->command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - if (descriptor_set) vkCmdBindDescriptorSets(b->command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, - pipeline_layout, 0, 1, &descriptor_set, 0, nullptr); - vkCmdDispatch(b->command_buffer, work->groups_x, work->groups_y, work->groups_z); - VkMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT; - vkCmdPipelineBarrier(b->command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); - return end_immediate(b); - }(); - release(); return operation; -} -se_gpu_result SE_GPU_CALL se_gpu_register_display_buffer(se_gpu_backend* b, uint64_t address, uint32_t format) { - if (!b || !address) return SE_GPU_INVALID_ARGUMENT; - b->display_formats[address] = format; return SE_GPU_OK; -} -se_gpu_result SE_GPU_CALL se_gpu_present_guest_image( - se_gpu_backend* b, uint64_t address, uint32_t width, uint32_t height, uint32_t pitch) { - if (!b || !address || !width || !height || pitch < width) return SE_GPU_INVALID_ARGUMENT; - auto found = b->guest_images.find(address); if (found == b->guest_images.end()) return SE_GPU_NOT_FOUND; - return present_image(b, &found->second); -} -se_gpu_result SE_GPU_CALL se_gpu_has_guest_image( - se_gpu_backend* b, uint64_t address, uint32_t format, uint32_t number_type) { - if (!b || !address) return SE_GPU_INVALID_ARGUMENT; - auto found = b->guest_images.find(address); if (found == b->guest_images.end()) return SE_GPU_NOT_FOUND; - VkFormat expected = guest_format(format, number_type); - return expected == VK_FORMAT_UNDEFINED || found->second.format == expected ? SE_GPU_OK : SE_GPU_NOT_FOUND; -} -se_gpu_result SE_GPU_CALL se_gpu_blit_guest_image( - se_gpu_backend* b, uint64_t source_address, uint32_t source_width, uint32_t source_height, - uint32_t source_format, uint64_t destination_address, uint32_t destination_width, - uint32_t destination_height, uint32_t destination_format) { - if (!b || !source_address || !destination_address || !source_width || !source_height || - !destination_width || !destination_height) return SE_GPU_INVALID_ARGUMENT; - auto source = b->guest_images.find(source_address); - if (source == b->guest_images.end()) return SE_GPU_NOT_FOUND; - if (guest_format(source_format, 0) != VK_FORMAT_UNDEFINED && - source->second.format != guest_format(source_format, 0)) return SE_GPU_NOT_FOUND; - guest_image* destination{}; - se_gpu_result created = cached_image(b, destination_address, destination_width, destination_height, - destination_format, 0, destination_width, nullptr, &destination); - if (created != SE_GPU_OK) return created; - source = b->guest_images.find(source_address); - return source == b->guest_images.end() ? SE_GPU_NOT_FOUND : blit_image(b, &source->second, destination); -} -se_gpu_result SE_GPU_CALL se_gpu_render_target_output_kind( - uint32_t format, uint32_t number_type, uint32_t* output_kind) { - if (!output_kind) return SE_GPU_INVALID_ARGUMENT; - bool supported = false; uint32_t kind = 0; - if ((format == 4 || format == 5 || format == 10 || format == 12) && number_type == 4) { - supported = true; kind = 1; - } else if ((format == 4 || format == 5 || format == 10 || format == 12) && number_type == 5) { - supported = true; kind = 2; - } else { - switch (format) { - case 1: case 3: case 4: case 5: case 7: case 9: case 10: case 12: case 13: case 14: - case 22: case 29: case 36: case 56: case 62: case 64: case 71: case 75: - case 0x30004: case 0x30005: - supported = true; kind = 0; break; - case 49: case 0x10004: case 0x10005: case 0x1000a: case 0x1000c: - supported = true; kind = 1; break; - case 0x20004: case 0x20005: case 0x2000a: case 0x2000c: - supported = true; kind = 2; break; - default: break; - } - } - if (!supported) return SE_GPU_NOT_FOUND; - *output_kind = kind; return SE_GPU_OK; -} -} diff --git a/src/SharpEmu.Gpu.Vulkan.Native/tests/abi_test.cpp b/src/SharpEmu.Gpu.Vulkan.Native/tests/abi_test.cpp deleted file mode 100644 index 4024a37..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/tests/abi_test.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright (C) 2026 SharpEmu Emulator Project - * SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "sharpemu_gpu_vulkan.h" -#include -#include -#include -#include -#include - -std::vector read_file(const char* path) { - std::ifstream file(path, std::ios::binary); - return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; -} - -int main(int argc, char** argv) { - if (se_gpu_abi_version() != SE_GPU_ABI_VERSION) return 1; - uint32_t output_kind{}; - if (se_gpu_render_target_output_kind(10, 4, &output_kind) != SE_GPU_OK || output_kind != 1) return 5; - if (se_gpu_render_target_output_kind(0xffffffffu, 0, &output_kind) != SE_GPU_NOT_FOUND) return 6; - se_gpu_create_info info{}; info.struct_size = sizeof(info); info.abi_version = SE_GPU_ABI_VERSION; - info.width = 64; info.height = 64; info.title_utf8 = "SharpEmu native GPU test"; - se_gpu_backend* backend{}; - if (se_gpu_create(&info, &backend) != SE_GPU_OK) return 2; - if (argc != 4) { se_gpu_destroy(backend); return 8; } - std::vector shader = read_file(argv[1]); - se_gpu_compute compute{}; compute.struct_size = sizeof(compute); - compute.spirv = {shader.data(), shader.size()}; compute.groups_x = 1; compute.groups_y = 1; compute.groups_z = 1; - if (shader.empty() || se_gpu_submit_compute(backend, &compute) != SE_GPU_OK) { - se_gpu_destroy(backend); return 9; - } - std::vector vertex = read_file(argv[2]); - std::vector fragment = read_file(argv[3]); - se_gpu_draw draw{}; draw.struct_size = sizeof(draw); draw.width = 64; draw.height = 64; - draw.vertex_spirv = {vertex.data(), vertex.size()}; draw.pixel_spirv = {fragment.data(), fragment.size()}; - std::vector padded_texture(8, 0xff804020u); - se_gpu_texture texture{}; texture.struct_size = sizeof(texture); texture.width = 2; texture.height = 2; - texture.format = 10; texture.pitch = 4; texture.dst_select = 0x324; - texture.rgba_pixels = {padded_texture.data(), padded_texture.size() * sizeof(uint32_t)}; - draw.textures = &texture; draw.texture_count = 1; - draw.vertex_count = 3; draw.instance_count = 1; draw.primitive_type = 4; - if (vertex.empty() || fragment.empty() || se_gpu_submit_draw(backend, &draw) != SE_GPU_OK) { - se_gpu_destroy(backend); return 10; - } - std::vector pixels(64 * 64); - for (uint32_t frame = 0; frame < 3; ++frame) { - std::fill(pixels.begin(), pixels.end(), 0xff000000u | frame * 0x00303030u); - if (se_gpu_present_bgra(backend, pixels.data(), pixels.size() * sizeof(uint32_t), 64, 64, 256) != SE_GPU_OK) { - se_gpu_destroy(backend); return 3; - } - uint32_t close{}; if (se_gpu_poll(backend, &close) != SE_GPU_OK || close) { - se_gpu_destroy(backend); return 4; - } - se_gpu_input input{}; input.struct_size = sizeof(input); - if (se_gpu_input_snapshot(backend, &input) != SE_GPU_OK) { se_gpu_destroy(backend); return 7; } - } - se_gpu_destroy(backend); return 0; -} diff --git a/src/SharpEmu.Gpu.Vulkan.Native/tests/exports_test.cpp b/src/SharpEmu.Gpu.Vulkan.Native/tests/exports_test.cpp deleted file mode 100644 index 4607894..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/tests/exports_test.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright (C) 2026 SharpEmu Emulator Project - * SPDX-License-Identifier: GPL-2.0-or-later */ -#include "sharpemu_gpu_vulkan.h" - -int main() { - if (se_gpu_abi_version() != SE_GPU_ABI_VERSION) return 1; - - uint32_t output_kind{}; - if (se_gpu_render_target_output_kind(10, 4, &output_kind) != SE_GPU_OK || output_kind != 1) return 2; - if (se_gpu_render_target_output_kind(10, 5, &output_kind) != SE_GPU_OK || output_kind != 2) return 3; - if (se_gpu_render_target_output_kind(0xffffffffu, 0, &output_kind) != SE_GPU_NOT_FOUND) return 4; - - return 0; -} diff --git a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.comp b/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.comp deleted file mode 100644 index 6f7ff68..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.comp +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#version 450 -layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -void main() {} diff --git a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.frag b/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.frag deleted file mode 100644 index d248f4d..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.frag +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#version 450 -layout(location = 0) out vec4 color; -layout(set = 0, binding = 1) uniform sampler2D sourceTexture; -void main() { color = texture(sourceTexture, gl_FragCoord.xy / vec2(64.0)); } diff --git a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.vert b/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.vert deleted file mode 100644 index 561ee7b..0000000 --- a/src/SharpEmu.Gpu.Vulkan.Native/tests/smoke.vert +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (C) 2026 SharpEmu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#version 450 -vec2 positions[3] = vec2[](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0)); -void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); } diff --git a/src/SharpEmu.Libs/Gpu/GuestGpu.cs b/src/SharpEmu.Libs/Gpu/GuestGpu.cs index 24dc420..4a163f5 100644 --- a/src/SharpEmu.Libs/Gpu/GuestGpu.cs +++ b/src/SharpEmu.Libs/Gpu/GuestGpu.cs @@ -13,13 +13,25 @@ namespace SharpEmu.Libs.Gpu; /// internal static class GuestGpu { - private static readonly Lazy Instance = new(static () => - string.Equals( - Environment.GetEnvironmentVariable("SHARPEMU_GPU_BACKEND"), - "native", - StringComparison.OrdinalIgnoreCase) - ? new NativeVulkanGuestGpuBackend() - : new VulkanGuestGpuBackend()); + private static readonly Lazy Instance = new(CreateBackend); public static IGuestGpuBackend Current => Instance.Value; + + private static IGuestGpuBackend CreateBackend() + { + if (!string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_GPU_BACKEND"), + "native", + StringComparison.OrdinalIgnoreCase)) + { + return new VulkanGuestGpuBackend(); + } + + if (!NativeVulkanApi.IsAvailable(out var error)) + { + Console.Error.WriteLine($"[LOADER][WARN] {error}"); + } + + return new NativeVulkanGuestGpuBackend(); + } } diff --git a/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanApi.cs b/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanApi.cs index 1998e40..c922c98 100644 --- a/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanApi.cs +++ b/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanApi.cs @@ -24,6 +24,31 @@ internal static unsafe partial class NativeVulkanApi internal const uint AbiVersion = 1; private const string Library = "sharpemu_gpu_vulkan"; + internal static string LibraryFileName => + OperatingSystem.IsWindows() ? "sharpemu_gpu_vulkan.dll" : + OperatingSystem.IsMacOS() ? "libsharpemu_gpu_vulkan.dylib" : + "libsharpemu_gpu_vulkan.so"; + + internal static bool IsAvailable(out string error) + { + if (NativeLibrary.TryLoad( + Library, + typeof(NativeVulkanApi).Assembly, + DllImportSearchPath.SafeDirectories | DllImportSearchPath.AssemblyDirectory, + out var handle)) + { + NativeLibrary.Free(handle); + error = string.Empty; + return true; + } + + error = + $"The optional native Vulkan backend was selected, but {LibraryFileName} could not be loaded. " + + "Place the library from https://github.com/sharpemu/sharpemu.vulkan next to the SharpEmu executable " + + "and ensure its SDL3 and Vulkan runtime dependencies are installed."; + return false; + } + [StructLayout(LayoutKind.Sequential)] internal struct CreateInfo { diff --git a/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanGuestGpuBackend.cs b/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanGuestGpuBackend.cs index 9ef12ac..7141e51 100644 --- a/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanGuestGpuBackend.cs +++ b/src/SharpEmu.Libs/Gpu/NativeVulkan/NativeVulkanGuestGpuBackend.cs @@ -34,7 +34,16 @@ internal sealed unsafe class NativeVulkanGuestGpuBackend : IGuestGpuBackend } } _ready.Wait(); - if (_startError is not null) throw new InvalidOperationException("Native Vulkan startup failed", _startError); + if (_startError is not null) + { + if (_startError is DllNotFoundException) + { + _ = NativeVulkanApi.IsAvailable(out var error); + throw new InvalidOperationException(error, _startError); + } + + throw new InvalidOperationException("Native Vulkan startup failed", _startError); + } } public bool TryCompileVertexShader(Gen5ShaderState state, Gen5ShaderEvaluation evaluation, diff --git a/src/SharpEmu.Libs/SharpEmu.Libs.csproj b/src/SharpEmu.Libs/SharpEmu.Libs.csproj index ddee094..9b0babf 100644 --- a/src/SharpEmu.Libs/SharpEmu.Libs.csproj +++ b/src/SharpEmu.Libs/SharpEmu.Libs.csproj @@ -37,46 +37,5 @@ SPDX-License-Identifier: GPL-2.0-or-later true - $(MSBuildThisFileDirectory)..\SharpEmu.Gpu.Vulkan.Native - $(RepoRoot)artifacts\native\gpu-vulkan - - - $(NativeGpuBuildDir)\bin\sharpemu_gpu_vulkan.dll - sharpemu_gpu_vulkan.dll - $(NativeGpuBuildDir)\bin\SDL3.dll - - - $(NativeGpuBuildDir)\bin\libsharpemu_gpu_vulkan.so - libsharpemu_gpu_vulkan.so - - - $(NativeGpuBuildDir)\bin\libsharpemu_gpu_vulkan.dylib - libsharpemu_gpu_vulkan.dylib - - - - - - - - - - - - - -