Make external Vulkan backend optional

This commit is contained in:
Spooks
2026-07-15 16:10:37 -06:00
parent c707f4d2ce
commit 94463576f8
18 changed files with 76 additions and 1925 deletions
-29
View File
@@ -58,21 +58,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<PropertyGroup>
<NoWarn>$(NoWarn);1591</NoWarn>
<NativeGpuBuildDir>$(RepoRoot)artifacts\native\gpu-vulkan\bin</NativeGpuBuildDir>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<NativeGpuHostLibrary>$(NativeGpuBuildDir)\sharpemu_gpu_vulkan.dll</NativeGpuHostLibrary>
<NativeGpuHostFileName>sharpemu_gpu_vulkan.dll</NativeGpuHostFileName>
<NativeGpuHostSdlLibrary>$(NativeGpuBuildDir)\SDL3.dll</NativeGpuHostSdlLibrary>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<NativeGpuHostLibrary>$(NativeGpuBuildDir)\libsharpemu_gpu_vulkan.so</NativeGpuHostLibrary>
<NativeGpuHostFileName>libsharpemu_gpu_vulkan.so</NativeGpuHostFileName>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<NativeGpuHostLibrary>$(NativeGpuBuildDir)\libsharpemu_gpu_vulkan.dylib</NativeGpuHostLibrary>
<NativeGpuHostFileName>libsharpemu_gpu_vulkan.dylib</NativeGpuHostFileName>
</PropertyGroup>
<ItemGroup>
@@ -101,18 +86,4 @@ SPDX-License-Identifier: GPL-2.0-or-later
</ItemGroup>
</Target>
<!-- Project-reference content does not reliably flow through a single-file
publish. Copy the optional native GPU runtime at the executable boundary
after bundling so NativeLibrary can resolve it by ABI name. -->
<Target Name="StageNativeGpuRuntime" AfterTargets="Publish"
Condition="Exists('$(NativeGpuHostLibrary)')">
<Copy SourceFiles="$(NativeGpuHostLibrary)"
DestinationFiles="$(PublishDir)$(NativeGpuHostFileName)"
SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NativeGpuHostSdlLibrary)"
DestinationFiles="$(PublishDir)SDL3.dll"
SkipUnchangedFiles="true"
Condition="'$(NativeGpuHostSdlLibrary)' != '' And Exists('$(NativeGpuHostSdlLibrary)')" />
</Target>
</Project>
+4 -3
View File
@@ -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.",
+2 -2
View File
@@ -211,8 +211,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
</StackPanel>
<ComboBox Grid.Column="1" x:Name="RenderingBackendBox" Width="200" SelectedIndex="0"
VerticalAlignment="Center" CornerRadius="8">
<ComboBoxItem x:Name="RenderingBackendLegacyItem" Content="Silk.NET Vulkan (Legacy)" />
<ComboBoxItem x:Name="RenderingBackendNativeItem" Content="Native Vulkan" />
<ComboBoxItem x:Name="RenderingBackendLegacyItem" Content="Silk.NET Vulkan (Default)" />
<ComboBoxItem x:Name="RenderingBackendNativeItem" Content="Native Vulkan (Experimental)" />
</ComboBox>
</Grid>
+16
View File
@@ -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));
@@ -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
$<TARGET_RUNTIME_DLLS:sharpemu_gpu_vulkan>
$<TARGET_FILE_DIR:sharpemu_gpu_vulkan>
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()
@@ -1,105 +0,0 @@
/* Copyright (C) 2026 SharpEmu Emulator Project
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <stddef.h>
#include <stdint.h>
#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
File diff suppressed because it is too large Load Diff
@@ -1,58 +0,0 @@
/* Copyright (C) 2026 SharpEmu Emulator Project
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "sharpemu_gpu_vulkan.h"
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iterator>
#include <vector>
std::vector<char> read_file(const char* path) {
std::ifstream file(path, std::ios::binary);
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
}
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<char> 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<char> vertex = read_file(argv[2]);
std::vector<char> 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<uint32_t> 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<uint32_t> 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;
}
@@ -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;
}
@@ -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() {}
@@ -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)); }
@@ -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); }
+19 -7
View File
@@ -13,13 +13,25 @@ namespace SharpEmu.Libs.Gpu;
/// </summary>
internal static class GuestGpu
{
private static readonly Lazy<IGuestGpuBackend> Instance = new(static () =>
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_GPU_BACKEND"),
"native",
StringComparison.OrdinalIgnoreCase)
? new NativeVulkanGuestGpuBackend()
: new VulkanGuestGpuBackend());
private static readonly Lazy<IGuestGpuBackend> 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();
}
}
@@ -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
{
@@ -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,
-41
View File
@@ -37,46 +37,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NativeGpuProjectDir>$(MSBuildThisFileDirectory)..\SharpEmu.Gpu.Vulkan.Native</NativeGpuProjectDir>
<NativeGpuBuildDir>$(RepoRoot)artifacts\native\gpu-vulkan</NativeGpuBuildDir>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<NativeGpuLibrary>$(NativeGpuBuildDir)\bin\sharpemu_gpu_vulkan.dll</NativeGpuLibrary>
<NativeGpuFileName>sharpemu_gpu_vulkan.dll</NativeGpuFileName>
<NativeGpuSdlLibrary>$(NativeGpuBuildDir)\bin\SDL3.dll</NativeGpuSdlLibrary>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<NativeGpuLibrary>$(NativeGpuBuildDir)\bin\libsharpemu_gpu_vulkan.so</NativeGpuLibrary>
<NativeGpuFileName>libsharpemu_gpu_vulkan.so</NativeGpuFileName>
</PropertyGroup>
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<NativeGpuLibrary>$(NativeGpuBuildDir)\bin\libsharpemu_gpu_vulkan.dylib</NativeGpuLibrary>
<NativeGpuFileName>libsharpemu_gpu_vulkan.dylib</NativeGpuFileName>
</PropertyGroup>
<ItemGroup Condition="'$(NativeGpuLibrary)' != ''">
<None Include="$(NativeGpuLibrary)"
Link="$(NativeGpuFileName)"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest" />
<None Include="$(NativeGpuSdlLibrary)"
Condition="'$(NativeGpuSdlLibrary)' != ''"
Link="SDL3.dll"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
<!-- Native outputs must exist before MSBuild evaluates/copies Content items.
Hooking Build is too late on a clean checkout: CopyFilesToOutputDirectory
runs first and fails because the library has not been produced yet. -->
<Target Name="BuildNativeGuestGpu" BeforeTargets="PrepareForBuild" Condition="'$(NativeGpuLibrary)' != ''">
<Exec Command="cmake -S &quot;$(NativeGpuProjectDir)&quot; -B &quot;$(NativeGpuBuildDir)&quot; -DCMAKE_BUILD_TYPE=$(Configuration) -DBUILD_TESTING=OFF" />
<Exec Command="cmake --build &quot;$(NativeGpuBuildDir)&quot; --config $(Configuration)" />
<Copy SourceFiles="$(NativeGpuLibrary)" DestinationFolder="$(TargetDir)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(NativeGpuSdlLibrary)"
DestinationFolder="$(TargetDir)"
SkipUnchangedFiles="true"
Condition="'$(NativeGpuSdlLibrary)' != ''" />
</Target>
</Project>