Compare commits

...

4 commits

Author SHA1 Message Date
Stenzek 2a21418cbe
System: Fix triple popup on missing BIOS 2024-05-13 00:06:56 +10:00
Stenzek be920acf38
System: Get rid of double popup on renderer create error 2024-05-13 00:06:56 +10:00
Stenzek 5808e14b7e
GameDatabase: Warning fix 2024-05-13 00:06:56 +10:00
Stenzek 03f9708911
GPUDevice: Move SPIR-V compilation to base class 2024-05-13 00:06:56 +10:00
15 changed files with 327 additions and 207 deletions

View file

@ -36,6 +36,12 @@ endif()
if(ENABLE_VULKAN OR APPLE)
find_package(Shaderc REQUIRED)
if(LINUX AND ENABLE_VULKAN)
# We need to add the rpath for shaderc to the executable.
get_filename_component(SHADERC_LIBRARY_DIRECTORY ${SHADERC_LIBRARY} DIRECTORY)
list(APPEND CMAKE_BUILD_RPATH ${SHADERC_LIBRARY_DIRECTORY})
endif()
endif()
if(APPLE)

View file

@ -55,6 +55,10 @@ BINARY=duckstation-qt
APPDIRNAME=DuckStation.AppDir
STRIP=strip
declare -a MANUAL_LIBS=(
"libshaderc_shared.so"
)
declare -a MANUAL_QT_LIBS=(
"libQt6WaylandEglClientHwIntegration.so.6"
)
@ -90,6 +94,24 @@ fi
OUTDIR=$(realpath "./$APPDIRNAME")
rm -fr "$OUTDIR"
echo "Locating extra libraries..."
EXTRA_LIBS_ARGS=""
for lib in "${MANUAL_LIBS[@]}"; do
srcpath=$(find "$DEPSDIR" -name "$lib")
if [ ! -f "$srcpath" ]; then
echo "Missinge extra library $lib. Exiting."
exit 1
fi
echo "Found $lib at $srcpath."
if [ "$EXTRA_LIBS_ARGS" == "" ]; then
EXTRA_LIBS_ARGS="--library=$srcpath"
else
EXTRA_LIBS_ARGS="$EXTRA_LIBS_ARGS,$srcpath"
fi
done
# Why the nastyness? linuxdeploy strips our main binary, and there's no option to turn it off.
# It also doesn't strip the Qt libs. We can't strip them after running linuxdeploy, because
# patchelf corrupts the libraries (but they still work), but patchelf+strip makes them crash
@ -112,9 +134,9 @@ EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so" \
DEPLOY_PLATFORM_THEMES="1" \
QMAKE="$DEPSDIR/bin/qmake" \
NO_STRIP="1" \
$LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/duckstation-qt" \
$LINUXDEPLOY --plugin qt --appdir="$OUTDIR" --executable="$BUILDDIR/bin/duckstation-qt" $EXTRA_LIBS_ARGS \
--desktop-file="$ROOTDIR/scripts/org.duckstation.DuckStation.desktop" \
--icon-file="$ROOTDIR/scripts/org.duckstation.DuckStation.png"
--icon-file="$ROOTDIR/scripts/org.duckstation.DuckStation.png" \
echo "Copying resources into AppDir..."
cp -a "$BUILDDIR/bin/resources" "$OUTDIR/usr/bin"

View file

@ -1,16 +1,20 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "bios.h"
#include "cpu_disasm.h"
#include "host.h"
#include "settings.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/md5_digest.h"
#include "common/path.h"
#include "cpu_disasm.h"
#include "host.h"
#include "settings.h"
#include <cerrno>
Log_SetChannel(BIOS);
static constexpr BIOS::Hash MakeHashFromString(const char str[])
@ -164,13 +168,13 @@ BIOS::Hash BIOS::GetImageHash(const BIOS::Image& image)
return hash;
}
std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename)
std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename, Error* error)
{
Image ret(BIOS_SIZE);
auto fp = FileSystem::OpenManagedCFile(filename, "rb");
auto fp = FileSystem::OpenManagedCFile(filename, "rb", error);
if (!fp)
{
Log_ErrorPrintf("Failed to open BIOS image '%s', errno=%d", filename, errno);
Error::AddPrefixFmt(error, "Failed to open BIOS '{}': ", Path::GetFileName(filename));
return std::nullopt;
}
@ -180,14 +184,15 @@ std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename)
if (size != BIOS_SIZE && size != BIOS_SIZE_PS2 && size != BIOS_SIZE_PS3)
{
Log_ErrorPrintf("BIOS image '%s' size mismatch, expecting either %u or %u or %u bytes but got %u bytes", filename,
BIOS_SIZE, BIOS_SIZE_PS2, BIOS_SIZE_PS3, size);
Error::SetStringFmt(error, "BIOS image '{}' size mismatch, expecting either {} or {} bytes but got {} bytes",
Path::GetFileName(filename), static_cast<unsigned>(BIOS_SIZE),
static_cast<unsigned>(BIOS_SIZE_PS2), size);
return std::nullopt;
}
if (std::fread(ret.data(), 1, ret.size(), fp.get()) != ret.size())
{
Log_ErrorPrintf("Failed to read BIOS image '%s'", filename);
Error::SetErrno(error, TinyString::from_format("Failed to read BIOS '{}': ", Path::GetFileName(filename)), errno);
return std::nullopt;
}
@ -326,7 +331,7 @@ DiscRegion BIOS::GetPSExeDiscRegion(const PSEXEHeader& header)
return DiscRegion::Other;
}
std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region)
std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* error)
{
std::string bios_name;
switch (region)
@ -345,31 +350,36 @@ std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region)
break;
}
std::optional<Image> image;
if (bios_name.empty())
{
// auto-detect
return FindBIOSImageInDirectory(region, EmuFolders::Bios.c_str());
image = FindBIOSImageInDirectory(region, EmuFolders::Bios.c_str(), error);
}
// try the configured path
std::optional<Image> image = LoadImageFromFile(Path::Combine(EmuFolders::Bios, bios_name).c_str());
if (!image.has_value())
else
{
Host::ReportFormattedErrorAsync("Error", TRANSLATE("HostInterface", "Failed to load configured BIOS file '%s'"),
bios_name.c_str());
return std::nullopt;
// try the configured path
image = LoadImageFromFile(Path::Combine(EmuFolders::Bios, bios_name).c_str(), error);
}
const ImageInfo* ii = GetInfoForImage(image.value());
if (!ii || !IsValidBIOSForRegion(region, ii->region))
Log_WarningPrintf("BIOS '%s' does not match region. This may cause issues.", bios_name.c_str());
// verify region
if (image.has_value())
{
const ImageInfo* ii = GetInfoForImage(image.value());
if (!ii || !IsValidBIOSForRegion(region, ii->region))
{
Log_WarningFmt("BIOS region {} does not match requested region {}. This may cause issues.",
Settings::GetConsoleRegionName(region), Settings::GetConsoleRegionName(ii->region));
}
}
return image;
}
std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory)
std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error)
{
Log_InfoPrintf("Searching for a %s BIOS in '%s'...", Settings::GetConsoleRegionDisplayName(region), directory);
Log_InfoFmt("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory);
FileSystem::FindResultsArray results;
FileSystem::FindFiles(
@ -383,20 +393,21 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
{
if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
{
Log_WarningPrintf("Skipping '%s': incorrect size", fd.FileName.c_str());
Log_WarningFmt("Skipping '{}': incorrect size", fd.FileName.c_str());
continue;
}
std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str());
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str(), nullptr);
if (!found_image)
continue;
const ImageInfo* ii = GetInfoForImage(found_image.value());
if (ii && IsValidBIOSForRegion(region, ii->region))
{
Log_InfoPrintf("Using BIOS '%s': %s", fd.FileName.c_str(), ii->description);
return found_image;
Log_InfoFmt("Using BIOS '{}': {}", fd.FileName.c_str(), ii->description);
fallback_image = std::move(found_image);
return fallback_image;
}
// don't let an unknown bios take precedence over a known one
@ -410,53 +421,24 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
if (!fallback_image.has_value())
{
Host::ReportFormattedErrorAsync("Error", TRANSLATE("HostInterface", "No BIOS image found for %s region"),
Settings::GetConsoleRegionDisplayName(region));
return std::nullopt;
Error::SetStringFmt(error, TRANSLATE_FS("System", "No BIOS image found for {} region."),
Settings::GetConsoleRegionName(region));
return fallback_image;
}
if (!fallback_info)
{
Log_WarningPrintf("Using unknown BIOS '%s'. This may crash.", fallback_path.c_str());
Log_WarningFmt("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path));
}
else
{
Log_WarningPrintf("Falling back to possibly-incompatible image '%s': %s", fallback_path.c_str(),
fallback_info->description);
Log_WarningFmt("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path),
fallback_info->description);
}
return fallback_image;
}
std::string BIOS::FindBIOSPathWithHash(const char* directory, const Hash& hash)
{
FileSystem::FindResultsArray files;
FileSystem::FindFiles(directory, "*",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &files);
std::string ret;
for (FILESYSTEM_FIND_DATA& fd : files)
{
if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
continue;
std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str());
if (!found_image)
continue;
const BIOS::Hash found_hash = GetImageHash(found_image.value());
if (found_hash == hash)
{
ret = std::move(full_path);
break;
}
}
return ret;
}
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImagesInDirectory(const char* directory)
{
std::vector<std::pair<std::string, const ImageInfo*>> results;
@ -471,7 +453,7 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImages
continue;
std::string full_path(Path::Combine(directory, fd.FileName));
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str());
std::optional<Image> found_image = LoadImageFromFile(full_path.c_str(), nullptr);
if (!found_image)
continue;
@ -484,5 +466,5 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> BIOS::FindBIOSImages
bool BIOS::HasAnyBIOSImages()
{
return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str()).has_value();
return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str(), nullptr).has_value();
}

View file

@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>.
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "types.h"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class Error;
namespace BIOS {
enum : u32
{
@ -58,7 +62,7 @@ struct PSEXEHeader
static_assert(sizeof(PSEXEHeader) == 0x800);
#pragma pack(pop)
std::optional<Image> LoadImageFromFile(const char* filename);
std::optional<Image> LoadImageFromFile(const char* filename, Error* error);
Hash GetImageHash(const Image& image);
const ImageInfo* GetInfoForImage(const Image& image);
@ -74,14 +78,11 @@ bool IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size);
DiscRegion GetPSExeDiscRegion(const PSEXEHeader& header);
/// Loads the BIOS image for the specified region.
std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region);
std::optional<std::vector<u8>> GetBIOSImage(ConsoleRegion region, Error* error);
/// Searches for a BIOS image for the specified region in the specified directory. If no match is found, the first
/// BIOS image within 512KB and 4MB will be used.
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory);
/// Returns a BIOS image which matches the specified hash.
std::string FindBIOSPathWithHash(const char* directory, const BIOS::Hash& hash);
std::optional<std::vector<u8>> FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error);
/// Returns a list of filenames and descriptions for BIOS images in a directory.
std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDirectory(const char* directory);

View file

@ -356,7 +356,6 @@ const char* GameDatabase::GetTraitName(Trait trait)
const char* GameDatabase::GetTraitDisplayName(Trait trait)
{
return Host::TranslateToCString("GameDatabase", s_trait_display_names[static_cast<size_t>(trait)]);
"";
}
const char* GameDatabase::GetCompatibilityRatingName(CompatibilityRating rating)

View file

@ -268,7 +268,7 @@ void Host::ReportFormattedDebuggerMessage(const char* format, ...)
ReportDebuggerMessage(message);
}
bool Host::CreateGPUDevice(RenderAPI api)
bool Host::CreateGPUDevice(RenderAPI api, Error* error)
{
DebugAssert(!g_gpu_device);
@ -292,29 +292,32 @@ bool Host::CreateGPUDevice(RenderAPI api)
if (g_settings.gpu_disable_texture_copy_to_self)
disabled_features |= GPUDevice::FEATURE_MASK_TEXTURE_COPY_TO_SELF;
Error error;
Error create_error;
if (!g_gpu_device || !g_gpu_device->Create(
g_settings.gpu_adapter,
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::IsVSyncEffectivelyEnabled(),
g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &error))
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
{
Log_ErrorPrintf("Failed to create GPU device.");
Log_ErrorFmt("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReportErrorAsync(
"Error", fmt::format("Failed to create render device:\n\n{}\n\nThis may be due to your GPU not supporting the "
"chosen renderer ({}), or because your graphics drivers need to be updated.",
error.GetDescription(), GPUDevice::RenderAPIToString(api)));
Error::SetStringFmt(
error,
TRANSLATE_FS("System", "Failed to create render device:\n\n{0}\n\nThis may be due to your GPU not supporting the "
"chosen renderer ({1}), or because your graphics drivers need to be updated."),
create_error.GetDescription(), GPUDevice::RenderAPIToString(api));
return false;
}
if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_show_osd_messages, &error))
if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_show_osd_messages,
&create_error))
{
Host::ReportErrorAsync("Error", fmt::format("Failed to initialize ImGuiManager: {}", error.GetDescription()));
Log_ErrorFmt("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
g_gpu_device->Destroy();
g_gpu_device.reset();
return false;
@ -379,4 +382,3 @@ void Host::ReleaseGPUDevice()
g_gpu_device->Destroy();
g_gpu_device.reset();
}

View file

@ -18,6 +18,7 @@
#include <string_view>
#include <vector>
class Error;
class SettingsInterface;
struct WindowInfo;
enum class AudioBackend : u8;
@ -91,7 +92,7 @@ void DisplayLoadingScreen(const char* message, int progress_min = -1, int progre
void RunOnCPUThread(std::function<void()> function, bool block = false);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api);
bool CreateGPUDevice(RenderAPI api, Error* error);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow();

View file

@ -102,13 +102,13 @@ static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirec
static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name,
std::vector<u8>* out_executable_data);
static bool LoadBIOS(const std::string& override_bios_path);
static bool LoadBIOS(const std::string& override_bios_path, Error* error);
static void InternalReset();
static void ClearRunningGame();
static void DestroySystem();
static std::string GetMediaPathFromSaveState(const char* path);
static bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state);
static bool CreateGPU(GPURenderer renderer, bool is_switching);
static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error);
static bool SaveUndoLoadState();
static void WarnAboutUnsafeSettings();
static void LogUnsafeSettingsToConsole(const SmallStringBase& messages);
@ -126,7 +126,7 @@ static void DoRewind();
static void SaveRunaheadState();
static bool DoRunahead();
static bool Initialize(bool force_software_renderer);
static bool Initialize(bool force_software_renderer, Error* error);
static bool FastForwardToFirstFrame();
static bool UpdateGameSettingsLayer();
@ -938,10 +938,11 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
Host::ReleaseGPUDevice();
}
if (!CreateGPU(renderer, true))
Error error;
if (!CreateGPU(renderer, true, &error))
{
if (!IsStartupCancelled())
Host::ReportErrorAsync("Error", "Failed to recreate GPU.");
Host::ReportErrorAsync("Error", error.GetDescription());
DestroySystem();
return false;
@ -1464,7 +1465,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
// Load BIOS image.
if (!LoadBIOS(parameters.override_bios))
if (!LoadBIOS(parameters.override_bios, error))
{
s_state = State::Shutdown;
ClearRunningGame();
@ -1474,7 +1475,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
// Component setup.
if (!Initialize(parameters.force_software_renderer))
if (!Initialize(parameters.force_software_renderer, error))
{
s_state = State::Shutdown;
ClearRunningGame();
@ -1579,7 +1580,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
return true;
}
bool System::Initialize(bool force_software_renderer)
bool System::Initialize(bool force_software_renderer, Error* error)
{
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10);
@ -1636,7 +1637,7 @@ bool System::Initialize(bool force_software_renderer)
CPU::CodeCache::Initialize();
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false))
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error))
{
Bus::Shutdown();
CPU::Shutdown();
@ -2076,7 +2077,7 @@ void System::RecreateSystem()
PauseSystem(true);
}
bool System::CreateGPU(GPURenderer renderer, bool is_switching)
bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
{
const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
@ -2085,13 +2086,13 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching)
{
if (g_gpu_device)
{
Log_WarningPrintf("Recreating GPU device, expecting %s got %s", GPUDevice::RenderAPIToString(api),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
Log_WarningFmt("Recreating GPU device, expecting {} got {}", GPUDevice::RenderAPIToString(api),
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
PostProcessing::Shutdown();
}
Host::ReleaseGPUDevice();
if (!Host::CreateGPUDevice(api))
if (!Host::CreateGPUDevice(api, error))
{
Host::ReleaseRenderWindow();
return false;
@ -2108,8 +2109,8 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching)
if (!g_gpu)
{
Log_ErrorPrintf("Failed to initialize %s renderer, falling back to software renderer",
Settings::GetRendererName(renderer));
Log_ErrorFmt("Failed to initialize {} renderer, falling back to software renderer",
Settings::GetRendererName(renderer));
Host::AddFormattedOSDMessage(
30.0f, TRANSLATE("OSDMessage", "Failed to initialize %s renderer, falling back to software renderer."),
Settings::GetRendererName(renderer));
@ -2117,7 +2118,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching)
g_gpu = GPU::CreateSoftwareRenderer();
if (!g_gpu)
{
Log_ErrorPrintf("Failed to create fallback software renderer.");
Log_ErrorPrint("Failed to create fallback software renderer.");
if (!s_keep_gpu_device_on_shutdown)
{
PostProcessing::Shutdown();
@ -2250,20 +2251,29 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
return !sw.HasError();
}
bool System::LoadBIOS(const std::string& override_bios_path)
bool System::LoadBIOS(const std::string& override_bios_path, Error* error)
{
std::optional<BIOS::Image> bios_image(
override_bios_path.empty() ? BIOS::GetBIOSImage(s_region) : FileSystem::ReadBinaryFile(override_bios_path.c_str()));
if (!bios_image.has_value())
std::optional<BIOS::Image> bios_image;
if (!override_bios_path.empty())
{
Host::ReportFormattedErrorAsync("Error", TRANSLATE("System", "Failed to load %s BIOS."),
Settings::GetConsoleRegionName(s_region));
return false;
bios_image = FileSystem::ReadBinaryFile(override_bios_path.c_str(), error);
if (!bios_image.has_value())
{
Error::AddPrefixFmt(error, TRANSLATE_FS("System", "Failed to load {} BIOS."),
Settings::GetConsoleRegionName(s_region));
return false;
}
}
else
{
bios_image = BIOS::GetBIOSImage(s_region, error);
if (!bios_image.has_value())
return false;
}
if (bios_image->size() != static_cast<u32>(Bus::BIOS_SIZE))
{
Host::ReportFormattedErrorAsync("Error", TRANSLATE("System", "Incorrect BIOS image size"));
Error::SetStringView(error, TRANSLATE_SV("System", "Incorrect BIOS image size"));
return false;
}

View file

@ -718,8 +718,11 @@ void EmuThread::startFullscreenUI()
setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>());
m_run_fullscreen_ui = true;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)) || !FullscreenUI::Initialize())
Error error;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), &error) ||
!FullscreenUI::Initialize())
{
Host::ReportErrorAsync("Error", error.GetDescription());
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
m_run_fullscreen_ui = false;

View file

@ -169,7 +169,8 @@ if(ENABLE_VULKAN)
endif()
if(ENABLE_VULKAN OR APPLE)
target_link_libraries(util PUBLIC Shaderc::shaderc_shared)
# shaderc is loaded dynamically to reduce module loads on startup.
target_include_directories(util PUBLIC ${SHADERC_INCLUDE_DIR})
endif()
if(NOT ANDROID)

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "gpu_device.h"
@ -8,6 +8,7 @@
#include "shadergen.h"
#include "common/assert.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
@ -36,6 +37,10 @@ Log_SetChannel(GPUDevice);
#include "vulkan_device.h"
#endif
#if defined(ENABLE_VULKAN) || defined(__APPLE__)
#include "shaderc/shaderc.h"
#endif
std::unique_ptr<GPUDevice> g_gpu_device;
static std::string s_pipeline_cache_path;
@ -1105,3 +1110,166 @@ std::unique_ptr<GPUDevice> GPUDevice::CreateDeviceForAPI(RenderAPI api)
return {};
}
}
#if defined(ENABLE_VULKAN) || defined(__APPLE__)
#define SHADERC_INIT_FUNCTIONS(X) \
X(shaderc_compiler_initialize) \
X(shaderc_compiler_release)
#define SHADERC_FUNCTIONS(X) \
X(shaderc_compile_options_initialize) \
X(shaderc_compile_options_release) \
X(shaderc_compile_options_set_source_language) \
X(shaderc_compile_options_set_generate_debug_info) \
X(shaderc_compile_options_set_emit_non_semantic_debug_info) \
X(shaderc_compile_options_set_optimization_level) \
X(shaderc_compile_options_set_target_env) \
X(shaderc_compile_into_spv) \
X(shaderc_result_release) \
X(shaderc_result_get_length) \
X(shaderc_result_get_num_warnings) \
X(shaderc_result_get_compilation_status) \
X(shaderc_result_get_bytes) \
X(shaderc_result_get_error_message)
// TODO: NOT thread safe, yet.
namespace dyn_shaderc {
static bool Open();
static DynamicLibrary s_library;
static std::unique_ptr<struct shaderc_compiler, void (*)(shaderc_compiler_t)> s_compiler(nullptr, nullptr);
#define ADD_FUNC(F) static decltype(&::F) F;
SHADERC_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
} // namespace dyn_shaderc
bool dyn_shaderc::Open()
{
if (s_library.IsOpen())
return true;
Error error;
if (!s_library.Open(DynamicLibrary::GetVersionedFilename("shaderc_shared").c_str(), &error))
{
Log_ErrorFmt("Failed to load shaderc: {}", error.GetDescription());
return false;
}
#define LOAD_FUNC(F) \
if (!s_library.GetSymbol(#F, &F)) \
{ \
Log_ErrorFmt("Failed to find function {}", #F); \
s_library.Close(); \
return false; \
}
#define LOAD_INIT_FUNC(F) \
decltype(&::F) p##F; \
if (!s_library.GetSymbol(#F, &p##F)) \
{ \
Log_ErrorFmt("Failed to find function {}", #F); \
s_library.Close(); \
return false; \
}
SHADERC_FUNCTIONS(LOAD_FUNC)
SHADERC_INIT_FUNCTIONS(LOAD_INIT_FUNC)
#undef LOAD_FUNC
#undef LOAD_INIT_FUNC
s_compiler = decltype(s_compiler)(pshaderc_compiler_initialize(), pshaderc_compiler_release);
return true;
}
#undef SHADERC_FUNCTIONS
#undef SHADERC_INIT_FUNCTIONS
bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_view source, const char* entry_point,
bool nonsemantic_debug_info, DynamicHeapArray<u8>* out_binary)
{
static constexpr const std::array<shaderc_shader_kind, static_cast<size_t>(GPUShaderStage::MaxCount)> stage_kinds = {{
shaderc_glsl_vertex_shader,
shaderc_glsl_fragment_shader,
shaderc_glsl_geometry_shader,
shaderc_glsl_compute_shader,
}};
// TODO: Move to library.
static constexpr const std::pair<shaderc_compilation_status, const char*> status_names[] = {
{shaderc_compilation_status_success, "shaderc_compilation_status_success"},
{shaderc_compilation_status_invalid_stage, "shaderc_compilation_status_invalid_stage"},
{shaderc_compilation_status_compilation_error, "shaderc_compilation_status_compilation_error"},
{shaderc_compilation_status_internal_error, "shaderc_compilation_status_internal_error"},
{shaderc_compilation_status_null_result_object, "shaderc_compilation_status_null_result_object"},
{shaderc_compilation_status_invalid_assembly, "shaderc_compilation_status_invalid_assembly"},
{shaderc_compilation_status_validation_error, "shaderc_compilation_status_validation_error"},
{shaderc_compilation_status_transformation_error, "shaderc_compilation_status_transformation_error"},
{shaderc_compilation_status_configuration_error, "shaderc_compilation_status_configuration_error"},
};
if (!dyn_shaderc::Open())
return false;
const std::unique_ptr<struct shaderc_compile_options, void (*)(shaderc_compile_options_t)> options(
dyn_shaderc::shaderc_compile_options_initialize(), dyn_shaderc::shaderc_compile_options_release);
if (!options)
{
Log_ErrorPrint("Failed to create shaderc options.");
return false;
}
dyn_shaderc::shaderc_compile_options_set_source_language(options.get(), shaderc_source_language_glsl);
dyn_shaderc::shaderc_compile_options_set_target_env(options.get(), shaderc_target_env_vulkan, 0);
if (m_debug_device)
{
dyn_shaderc::shaderc_compile_options_set_generate_debug_info(options.get());
if (nonsemantic_debug_info)
dyn_shaderc::shaderc_compile_options_set_emit_non_semantic_debug_info(options.get());
dyn_shaderc::shaderc_compile_options_set_optimization_level(options.get(), shaderc_optimization_level_zero);
}
else
{
dyn_shaderc::shaderc_compile_options_set_optimization_level(options.get(), shaderc_optimization_level_performance);
}
const std::unique_ptr<struct shaderc_compilation_result, void (*)(shaderc_compilation_result_t)> result(
dyn_shaderc::shaderc_compile_into_spv(dyn_shaderc::s_compiler.get(), source.data(), source.length(),
stage_kinds[static_cast<size_t>(stage)], "source", entry_point,
options.get()),
dyn_shaderc::shaderc_result_release);
const shaderc_compilation_status status = result ? dyn_shaderc::shaderc_result_get_compilation_status(result.get()) :
shaderc_compilation_status_null_result_object;
if (status != shaderc_compilation_status_success)
{
const char* status_name = "UNKNOWN";
for (const auto& it : status_names)
{
if (status == it.first)
{
status_name = it.second;
break;
}
}
const std::string_view errors(result ? dyn_shaderc::shaderc_result_get_error_message(result.get()) :
"null result object");
Log_ErrorFmt("Failed to compile shader to SPIR-V: {}\n{}", status_name, errors);
DumpBadShader(source, errors);
return false;
}
const size_t num_warnings = dyn_shaderc::shaderc_result_get_num_warnings(result.get());
if (num_warnings > 0)
Log_WarningFmt("Shader compiled with warnings:\n{}", dyn_shaderc::shaderc_result_get_error_message(result.get()));
const size_t spirv_size = dyn_shaderc::shaderc_result_get_length(result.get());
DebugAssert(spirv_size > 0);
out_binary->resize(spirv_size);
std::memcpy(out_binary->data(), dyn_shaderc::shaderc_result_get_bytes(result.get()), spirv_size);
return true;
}
#endif

View file

@ -719,6 +719,11 @@ protected:
void TrimTexturePool();
#if defined(ENABLE_VULKAN) || defined(__APPLE__)
bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_view source, const char* entry_point,
bool nonsemantic_debug_info, DynamicHeapArray<u8>* out_binary);
#endif
Features m_features = {};
u32 m_max_texture_size = 0;
u32 m_max_multisamples = 0;

View file

@ -16,7 +16,6 @@
#define FMT_EXCEPTIONS 0
#include "fmt/format.h"
#include "shaderc/shaderc.hpp"
#include "spirv_cross_c.h"
#include <array>
@ -59,8 +58,6 @@ static constexpr std::array<MTLPixelFormat, static_cast<u32>(GPUTexture::Format:
MTLPixelFormatBGR10A2Unorm, // RGB10A2
};
static std::unique_ptr<shaderc::Compiler> s_shaderc_compiler;
static NSString* StringViewToNSString(std::string_view str)
{
if (str.empty())
@ -657,44 +654,12 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
{
static constexpr bool dump_shaders = false;
static constexpr const std::array<shaderc_shader_kind, static_cast<size_t>(GPUShaderStage::MaxCount)> stage_kinds = {{
shaderc_glsl_vertex_shader,
shaderc_glsl_fragment_shader,
shaderc_glsl_geometry_shader,
shaderc_glsl_compute_shader,
}};
// TODO: NOT thread safe, yet.
if (!s_shaderc_compiler)
s_shaderc_compiler = std::make_unique<shaderc::Compiler>();
shaderc::CompileOptions spv_options;
spv_options.SetSourceLanguage(shaderc_source_language_glsl);
spv_options.SetTargetEnvironment(shaderc_target_env_vulkan, 0);
if (m_debug_device)
{
spv_options.SetOptimizationLevel(shaderc_optimization_level_zero);
spv_options.SetGenerateDebugInfo();
}
else
{
spv_options.SetOptimizationLevel(shaderc_optimization_level_performance);
}
const shaderc::SpvCompilationResult result = s_shaderc_compiler->CompileGlslToSpv(
source.data(), source.length(), stage_kinds[static_cast<size_t>(stage)], "source", entry_point, spv_options);
if (result.GetCompilationStatus() != shaderc_compilation_status_success)
{
const std::string errors = result.GetErrorMessage();
DumpBadShader(source, errors);
Log_ErrorFmt("Failed to compile shader to SPIR-V:\n{}", errors);
DynamicHeapArray<u8> local_binary;
DynamicHeapArray<u8>* dest_binary = out_binary ? out_binary : &local_binary;
if (!CompileGLSLShaderToVulkanSpv(stage, source, entry_point, false, dest_binary))
return {};
}
else if (result.GetNumWarnings() > 0)
{
Log_WarningFmt("Shader compiled with warnings:\n{}", result.GetErrorMessage());
}
AssertMsg((dest_binary->size() % 4) == 0, "Compile result should be 4 byte aligned.");
spvc_context sctx;
spvc_result sres;
@ -710,8 +675,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
sctx, [](void*, const char* error) { Log_ErrorFmt("SPIRV-Cross reported an error: {}", error); }, nullptr);
spvc_parsed_ir sir;
if ((sres = spvc_context_parse_spirv(sctx, result.cbegin(), std::distance(result.cbegin(), result.cend()), &sir)) !=
SPVC_SUCCESS)
if ((sres = spvc_context_parse_spirv(sctx, reinterpret_cast<const u32*>(dest_binary->data()), dest_binary->size() / 4, &sir)) != SPVC_SUCCESS)
{
Log_ErrorFmt("spvc_context_parse_spirv() failed: {}", static_cast<int>(sres));
DumpBadShader(source, std::string_view());

View file

@ -27,7 +27,7 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)SDL2</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;SDL2.lib;shaderc_shared.lib;zlib.lib;zstd.lib</AdditionalDependencies>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;libjpeg.lib;libpng16.lib;libwebp.lib;SDL2.lib;zlib.lib;zstd.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View file

@ -8,12 +8,8 @@
#include "common/assert.h"
#include "common/log.h"
#include "shaderc/shaderc.hpp"
Log_SetChannel(VulkanDevice);
static std::unique_ptr<shaderc::Compiler> s_shaderc_compiler;
VulkanShader::VulkanShader(GPUShaderStage stage, VkShaderModule mod) : GPUShader(stage), m_module(mod)
{
}
@ -48,57 +44,17 @@ std::unique_ptr<GPUShader> VulkanDevice::CreateShaderFromSource(GPUShaderStage s
const char* entry_point,
DynamicHeapArray<u8>* out_binary)
{
static constexpr const std::array<shaderc_shader_kind, static_cast<size_t>(GPUShaderStage::MaxCount)> stage_kinds = {{
shaderc_glsl_vertex_shader,
shaderc_glsl_fragment_shader,
shaderc_glsl_geometry_shader,
shaderc_glsl_compute_shader,
}};
// TODO: NOT thread safe, yet.
if (!s_shaderc_compiler)
s_shaderc_compiler = std::make_unique<shaderc::Compiler>();
shaderc::CompileOptions options;
options.SetSourceLanguage(shaderc_source_language_glsl);
options.SetTargetEnvironment(shaderc_target_env_vulkan, 0);
if (m_debug_device)
DynamicHeapArray<u8> local_binary;
DynamicHeapArray<u8>* dest_binary = out_binary ? out_binary : &local_binary;
if (!CompileGLSLShaderToVulkanSpv(stage, source, entry_point, m_optional_extensions.vk_khr_shader_non_semantic_info,
dest_binary))
{
options.SetGenerateDebugInfo();
if (m_optional_extensions.vk_khr_shader_non_semantic_info)
options.SetEmitNonSemanticDebugInfo();
options.SetOptimizationLevel(shaderc_optimization_level_zero);
}
else
{
options.SetOptimizationLevel(shaderc_optimization_level_performance);
}
const shaderc::SpvCompilationResult result = s_shaderc_compiler->CompileGlslToSpv(
source.data(), source.length(), stage_kinds[static_cast<size_t>(stage)], "source", entry_point, options);
if (result.GetCompilationStatus() != shaderc_compilation_status_success)
{
const std::string errors = result.GetErrorMessage();
DumpBadShader(source, errors);
Log_ErrorFmt("Failed to compile shader to SPIR-V:\n{}", errors);
return {};
}
else if (result.GetNumWarnings() > 0)
{
Log_WarningFmt("Shader compiled with warnings:\n{}", result.GetErrorMessage());
}
const size_t spirv_size = std::distance(result.cbegin(), result.cend()) * sizeof(*result.cbegin());
DebugAssert(spirv_size > 0);
if (out_binary)
{
out_binary->resize(spirv_size);
std::copy(result.cbegin(), result.cend(), reinterpret_cast<uint32_t*>(out_binary->data()));
}
AssertMsg((dest_binary->size() % 4) == 0, "Compile result should be 4 byte aligned.");
return CreateShaderFromBinary(stage, std::span<const u8>(reinterpret_cast<const u8*>(result.cbegin()), spirv_size));
return CreateShaderFromBinary(stage, dest_binary->cspan());
}
//////////////////////////////////////////////////////////////////////////