mirror of
https://github.com/par274/sharpemu.git
synced 2026-09-01 06:13:38 +08:00
Make external Vulkan backend optional
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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); }
|
||||
@@ -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,
|
||||
|
||||
@@ -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 "$(NativeGpuProjectDir)" -B "$(NativeGpuBuildDir)" -DCMAKE_BUILD_TYPE=$(Configuration) -DBUILD_TESTING=OFF" />
|
||||
<Exec Command="cmake --build "$(NativeGpuBuildDir)" --config $(Configuration)" />
|
||||
<Copy SourceFiles="$(NativeGpuLibrary)" DestinationFolder="$(TargetDir)" SkipUnchangedFiles="true" />
|
||||
<Copy SourceFiles="$(NativeGpuSdlLibrary)"
|
||||
DestinationFolder="$(TargetDir)"
|
||||
SkipUnchangedFiles="true"
|
||||
Condition="'$(NativeGpuSdlLibrary)' != ''" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user