GPUDevice: Extract swap chain to separate class

This commit is contained in:
Stenzek 2024-10-12 22:18:48 +10:00
parent d361601942
commit e5c5ba22d7
No known key found for this signature in database
70 changed files with 2472 additions and 2227 deletions

View File

@ -4341,10 +4341,11 @@ void FullscreenUI::DrawDisplaySettingsPage()
options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty());
if (selected_adapter)
{
for (const std::string& mode : selected_adapter->fullscreen_modes)
for (const GPUDevice::ExclusiveFullscreenMode& mode : selected_adapter->fullscreen_modes)
{
const bool checked = (strvalue.has_value() && strvalue.value() == mode);
options.emplace_back(mode, checked);
const TinyString mode_str = mode.ToString();
const bool checked = (strvalue.has_value() && strvalue.value() == mode_str);
options.emplace_back(std::string(mode_str.view()), checked);
}
}

View File

@ -1145,8 +1145,16 @@ void GPU::UpdateCommandTickEvent()
void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
float* display_y) const
{
if (!g_gpu_device->HasMainSwapChain()) [[unlikely]]
{
*display_x = 0.0f;
*display_y = 0.0f;
return;
}
GSVector4i display_rc, draw_rc;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rc, &draw_rc);
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(), true,
true, &display_rc, &draw_rc);
// convert coordinates to active display region, then to full display region
const float scaled_display_x =
@ -1644,7 +1652,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm
if (display)
{
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8);
plconfig.SetTargetFormats(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() :
GPUTexture::Format::RGBA8);
std::string vs = shadergen.GenerateDisplayVertexShader();
std::string fs;
@ -1839,10 +1848,13 @@ GPUDevice::PresentResult GPU::PresentDisplay()
{
FlushRender();
if (!g_gpu_device->HasMainSwapChain())
return GPUDevice::PresentResult::SkipPresent;
GSVector4i display_rect;
GSVector4i draw_rect;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), !g_settings.debugging.show_vram,
true, &display_rect, &draw_rect);
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
!g_settings.debugging.show_vram, true, &display_rect, &draw_rect);
return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram);
}
@ -1887,13 +1899,12 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
}
}
const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetWindowFormat();
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth();
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight();
const bool really_postfx =
(postfx && PostProcessing::DisplayChain.IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() &&
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetMainSwapChain()->GetFormat();
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetMainSwapChain()->GetHeight();
const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
const GSVector4i real_draw_rect =
g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect;
if (really_postfx)
@ -1904,9 +1915,15 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
else
{
if (target)
{
g_gpu_device->SetRenderTarget(target);
else if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
return pres;
}
else
{
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
if (pres != GPUDevice::PresentResult::OK)
return pres;
}
}
if (display_texture)
@ -2559,7 +2576,7 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
GPUTexture::Format* out_format)
{
const GPUTexture::Format hdformat =
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8;
auto render_texture =
g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat);
@ -2605,8 +2622,8 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const
{
*width = g_gpu_device->GetWindowWidth();
*height = g_gpu_device->GetWindowHeight();
*width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1;
*height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1;
CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect);
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram);

View File

@ -525,7 +525,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
// When using very high upscaling, it's possible that we don't have enough VRAM for two sets of buffers.
// Purge the pool, and idle the GPU so that all video memory is freed prior to creating the new buffers.
g_gpu_device->PurgeTexturePool();
g_gpu_device->ExecuteAndWaitForGPUIdle();
g_gpu_device->WaitForGPUIdle();
if (!CreateBuffers())
Panic("Failed to recreate buffers.");
@ -680,7 +680,7 @@ u32 GPU_HW::CalculateResolutionScale() const
{
// Auto scaling.
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0 || m_crtc_state.display_vram_width == 0 ||
m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable)
m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable || !g_gpu_device->HasMainSwapChain())
{
// When the system is starting and all borders crop is enabled, the registers are zero, and
// display_height therefore is also zero. Keep the existing resolution until it updates.
@ -689,8 +689,8 @@ u32 GPU_HW::CalculateResolutionScale() const
else
{
GSVector4i display_rect, draw_rect;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rect,
&draw_rect);
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
true, true, &display_rect, &draw_rect);
// We use the draw rect to determine scaling. This way we match the resolution as best we can, regardless of the
// anamorphic aspect ratio.

View File

@ -243,14 +243,14 @@ void GTE::UpdateAspectRatio()
{
case DisplayAspectRatio::MatchWindow:
{
if (!g_gpu_device)
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
{
s_config.aspect_ratio = DisplayAspectRatio::R4_3;
return;
}
num = g_gpu_device->GetWindowWidth();
denom = g_gpu_device->GetWindowHeight();
num = g_gpu_device->GetMainSwapChain()->GetWidth();
denom = g_gpu_device->GetMainSwapChain()->GetHeight();
}
break;

View File

@ -274,13 +274,19 @@ std::string Host::GetHTTPUserAgent()
return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str);
}
bool Host::CreateGPUDevice(RenderAPI api, Error* error)
bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error)
{
DebugAssert(!g_gpu_device);
INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
g_gpu_device = GPUDevice::CreateDeviceForAPI(api);
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device && g_gpu_device->SupportsExclusiveFullscreen())
{
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
@ -300,18 +306,30 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
if (g_settings.gpu_disable_raster_order_views)
disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS;
// Don't dump shaders on debug builds for Android, users will complain about storage...
#if !defined(__ANDROID__) || defined(_DEBUG)
const std::string_view shader_dump_directory(EmuFolders::DataRoot);
#else
const std::string_view shader_dump_directory;
#endif
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::GetEffectiveVSyncMode(),
System::ShouldAllowPresentThrottle(), exclusive_fullscreen_control,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
std::optional<WindowInfo> wi;
if (!g_gpu_device ||
!(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() ||
!g_gpu_device->Create(
g_settings.gpu_adapter, static_cast<GPUDevice::FeatureMask>(disabled_features), shader_dump_directory,
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, wi.value(), System::GetEffectiveVSyncMode(),
System::ShouldAllowPresentThrottle(), fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
g_gpu_device->Destroy();
g_gpu_device.reset();
if (wi.has_value())
Host::ReleaseRenderWindow();
Error::SetStringFmt(
error,
@ -327,27 +345,52 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
return false;
}
InputManager::SetDisplayWindowSize(static_cast<float>(g_gpu_device->GetWindowWidth()),
static_cast<float>(g_gpu_device->GetWindowHeight()));
InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight());
return true;
}
void Host::UpdateDisplayWindow()
void Host::UpdateDisplayWindow(bool fullscreen)
{
if (!g_gpu_device)
return;
if (!g_gpu_device->UpdateWindow())
const GPUVSyncMode vsync_mode =
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetVSyncMode() : GPUVSyncMode::Disabled;
const bool allow_present_throttle =
g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed();
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
if (fullscreen && g_gpu_device->SupportsExclusiveFullscreen())
{
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
fullscreen_mode =
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
}
std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
{
exclusive_fullscreen_control =
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
}
g_gpu_device->DestroyMainSwapChain();
Error error;
std::optional<WindowInfo> wi;
if (!(wi = Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error))
.has_value() ||
!g_gpu_device->RecreateMainSwapChain(wi.value(), vsync_mode, allow_present_throttle,
fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &error))
{
Host::ReportFatalError("Failed to change window after update", error.GetDescription());
return;
}
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
System::HostDisplayResized();
@ -365,15 +408,21 @@ void Host::UpdateDisplayWindow()
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{
if (!g_gpu_device)
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return;
DEV_LOG("Display window resized to {}x{}", width, height);
g_gpu_device->ResizeWindow(width, height, scale);
Error error;
if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error))
{
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
UpdateDisplayWindow(Host::IsFullscreen());
return;
}
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
@ -404,4 +453,6 @@ void Host::ReleaseGPUDevice()
INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
g_gpu_device->Destroy();
g_gpu_device.reset();
Host::ReleaseRenderWindow();
}

View File

@ -82,11 +82,25 @@ void DisplayLoadingScreen(const char* message, int progress_min = -1, int progre
/// Safely executes a function on the VM thread.
void RunOnCPUThread(std::function<void()> function, bool block = false);
/// Called when the core is creating a render device.
/// This could also be fullscreen transition.
std::optional<WindowInfo> AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error);
/// Called when the core is finished with a render window.
void ReleaseRenderWindow();
/// Returns true if the hosting application is currently fullscreen.
bool IsFullscreen();
/// Alters fullscreen state of hosting application.
void SetFullscreen(bool enabled);
/// Attempts to create the rendering device backend.
bool CreateGPUDevice(RenderAPI api, Error* error);
bool CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error);
/// Handles fullscreen transitions and such.
void UpdateDisplayWindow();
void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale);

View File

@ -84,7 +84,7 @@ static std::tuple<float, float> GetMinMax(std::span<const float> values)
void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/,
int progress_value /*= -1*/)
{
if (!g_gpu_device)
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
{
INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
return;
@ -160,10 +160,11 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
// TODO: Glass effect or something.
if (g_gpu_device->BeginPresent() == GPUDevice::PresentResult::OK)
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
{
g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(false);
g_gpu_device->RenderImGui(swap_chain);
g_gpu_device->EndPresent(swap_chain, false);
}
ImGui::NewFrame();

View File

@ -158,8 +158,6 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f);
sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false);
inhibit_screensaver = si.GetBoolValue("Main", "InhibitScreensaver", true);
start_paused = si.GetBoolValue("Main", "StartPaused", false);
start_fullscreen = si.GetBoolValue("Main", "StartFullscreen", false);
pause_on_focus_loss = si.GetBoolValue("Main", "PauseOnFocusLoss", false);
pause_on_controller_disconnection = si.GetBoolValue("Main", "PauseOnControllerDisconnection", false);
save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true);
@ -505,8 +503,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
{
si.SetBoolValue("Main", "SyncToHostRefreshRate", sync_to_host_refresh_rate);
si.SetBoolValue("Main", "InhibitScreensaver", inhibit_screensaver);
si.SetBoolValue("Main", "StartPaused", start_paused);
si.SetBoolValue("Main", "StartFullscreen", start_fullscreen);
si.SetBoolValue("Main", "PauseOnFocusLoss", pause_on_focus_loss);
si.SetBoolValue("Main", "PauseOnControllerDisconnection", pause_on_controller_disconnection);
si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit);
@ -1657,10 +1653,11 @@ float Settings::GetDisplayAspectRatioValue() const
{
case DisplayAspectRatio::MatchWindow:
{
if (!g_gpu_device)
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return s_display_aspect_ratio_values[static_cast<size_t>(DEFAULT_DISPLAY_ASPECT_RATIO)];
return static_cast<float>(g_gpu_device->GetWindowWidth()) / static_cast<float>(g_gpu_device->GetWindowHeight());
return static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
}
case DisplayAspectRatio::Custom:
@ -2110,7 +2107,6 @@ std::string EmuFolders::Bios;
std::string EmuFolders::Cache;
std::string EmuFolders::Cheats;
std::string EmuFolders::Covers;
std::string EmuFolders::Dumps;
std::string EmuFolders::GameIcons;
std::string EmuFolders::GameSettings;
std::string EmuFolders::InputProfiles;
@ -2130,7 +2126,6 @@ void EmuFolders::SetDefaults()
Cache = Path::Combine(DataRoot, "cache");
Cheats = Path::Combine(DataRoot, "cheats");
Covers = Path::Combine(DataRoot, "covers");
Dumps = Path::Combine(DataRoot, "dump");
GameIcons = Path::Combine(DataRoot, "gameicons");
GameSettings = Path::Combine(DataRoot, "gamesettings");
InputProfiles = Path::Combine(DataRoot, "inputprofiles");
@ -2162,7 +2157,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache");
Cheats = LoadPathFromSettings(si, DataRoot, "Folders", "Cheats", "cheats");
Covers = LoadPathFromSettings(si, DataRoot, "Folders", "Covers", "covers");
Dumps = LoadPathFromSettings(si, DataRoot, "Folders", "Dumps", "dump");
GameIcons = LoadPathFromSettings(si, DataRoot, "Folders", "GameIcons", "gameicons");
GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings");
InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles");
@ -2179,7 +2173,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
DEV_LOG("Cache Directory: {}", Cache);
DEV_LOG("Cheats Directory: {}", Cheats);
DEV_LOG("Covers Directory: {}", Covers);
DEV_LOG("Dumps Directory: {}", Dumps);
DEV_LOG("Game Icons Directory: {}", GameIcons);
DEV_LOG("Game Settings Directory: {}", GameSettings);
DEV_LOG("Input Profile Directory: {}", InputProfiles);
@ -2201,7 +2194,6 @@ void EmuFolders::Save(SettingsInterface& si)
si.SetStringValue("Folders", "Cache", Path::MakeRelative(Cache, DataRoot).c_str());
si.SetStringValue("Folders", "Cheats", Path::MakeRelative(Cheats, DataRoot).c_str());
si.SetStringValue("Folders", "Covers", Path::MakeRelative(Covers, DataRoot).c_str());
si.SetStringValue("Folders", "Dumps", Path::MakeRelative(Dumps, DataRoot).c_str());
si.SetStringValue("Folders", "GameIcons", Path::MakeRelative(GameIcons, DataRoot).c_str());
si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str());
si.SetStringValue("Folders", "InputProfiles", Path::MakeRelative(InputProfiles, DataRoot).c_str());
@ -2242,7 +2234,6 @@ bool EmuFolders::EnsureFoldersExist()
result = FileSystem::EnsureDirectoryExists(Path::Combine(Cache, "achievement_images").c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Cheats.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Covers.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Dumps.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(GameIcons.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(InputProfiles.c_str(), false) && result;

View File

@ -78,8 +78,6 @@ struct Settings
float turbo_speed = 0.0f;
bool sync_to_host_refresh_rate : 1 = false;
bool inhibit_screensaver : 1 = true;
bool start_paused : 1 = false;
bool start_fullscreen : 1 = false;
bool pause_on_focus_loss : 1 = false;
bool pause_on_controller_disconnection : 1 = false;
bool save_state_on_exit : 1 = true;
@ -572,7 +570,6 @@ extern std::string Bios;
extern std::string Cache;
extern std::string Cheats;
extern std::string Covers;
extern std::string Dumps;
extern std::string GameIcons;
extern std::string GameSettings;
extern std::string InputProfiles;

View File

@ -145,21 +145,26 @@ static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_
static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length);
/// Settings that are looked up on demand.
static bool ShouldStartFullscreen();
static bool ShouldStartPaused();
/// Checks for settings changes, std::move() the old settings away for comparing beforehand.
static void CheckForSettingsChanges(const Settings& old_settings);
static void WarnAboutUnsafeSettings();
static void LogUnsafeSettingsToConsole(const SmallStringBase& messages);
static bool Initialize(bool force_software_renderer, Error* error);
static bool Initialize(bool force_software_renderer, bool fullscreen, Error* error);
static bool LoadBIOS(Error* error);
static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error);
static void InternalReset();
static void ClearRunningGame();
static void DestroySystem();
static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error);
static bool CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error);
static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true);
static void HandleHostGPUDeviceLost();
static void HandleExclusiveFullscreenLost();
/// Returns true if boot is being fast forwarded.
static bool IsFastForwardingBoot();
@ -1201,10 +1206,11 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
{
PostProcessing::Shutdown();
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
Error error;
if (!CreateGPU(renderer, true, &error))
if (!CreateGPU(renderer, true, Host::IsFullscreen(), &error))
{
if (!IsStartupCancelled())
Host::ReportErrorAsync("Error", error.GetDescription());
@ -1265,6 +1271,12 @@ void System::HandleHostGPUDeviceLost()
Host::OSD_CRITICAL_ERROR_DURATION);
}
void System::HandleExclusiveFullscreenLost()
{
WARNING_LOG("Lost exclusive fullscreen.");
Host::SetFullscreen(false);
}
void System::LoadSettings(bool display_osd_messages)
{
std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
@ -1373,6 +1385,9 @@ void System::SetDefaultSettings(SettingsInterface& si)
temp.Save(si, false);
si.SetBoolValue("Main", "StartPaused", false);
si.SetBoolValue("Main", "StartFullscreen", false);
#if !defined(_WIN32) && !defined(__ANDROID__)
// On Linux, default the console to whether standard input is currently available.
si.SetBoolValue("Logging", "LogToConsole", Log::IsConsoleOutputCurrentlyAvailable());
@ -1793,7 +1808,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
}
// Component setup.
if (!Initialize(parameters.force_software_renderer, error))
if (!Initialize(parameters.force_software_renderer, parameters.override_fullscreen.value_or(ShouldStartFullscreen()),
error))
{
s_boot_mode = System::BootMode::None;
s_state = State::Shutdown;
@ -1853,7 +1869,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (parameters.start_media_capture)
StartMediaCapture({});
if (g_settings.start_paused || parameters.override_start_paused.value_or(false))
if (ShouldStartPaused() || parameters.override_start_paused.value_or(false))
PauseSystem(true);
UpdateSpeedLimiterState();
@ -1861,7 +1877,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
return true;
}
bool System::Initialize(bool force_software_renderer, Error* error)
bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* error)
{
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10);
@ -1911,7 +1927,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
Bus::Initialize();
CPU::Initialize();
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error))
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error))
{
CPU::Shutdown();
Bus::Shutdown();
@ -1928,10 +1944,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
{
g_gpu.reset();
if (!s_keep_gpu_device_on_shutdown)
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Shutdown();
CPU::Shutdown();
@ -2018,7 +2031,6 @@ void System::DestroySystem()
else
{
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
s_bios_hash = {};
@ -2189,12 +2201,13 @@ void System::FrameDone()
const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number);
s_last_presented_internal_frame_number = s_internal_frame_number;
const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame &&
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
g_gpu_device->ShouldSkipPresentingFrame()) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
const bool skip_this_frame =
(((s_skip_presenting_duplicate_frames && !is_unique_frame &&
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
(g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->ShouldSkipPresentingFrame())) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
if (!skip_this_frame)
{
s_skipped_frame_count = 0;
@ -2211,7 +2224,7 @@ void System::FrameDone()
const bool do_present = PresentDisplay(true, 0);
Throttle(current_time);
if (do_present)
g_gpu_device->SubmitPresent();
g_gpu_device->SubmitPresent(g_gpu_device->GetMainSwapChain());
}
else
{
@ -2373,7 +2386,7 @@ void System::IncrementInternalFrameNumber()
s_internal_frame_number++;
}
bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
bool System::CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error)
{
const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
@ -2388,7 +2401,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
}
Host::ReleaseGPUDevice();
if (!Host::CreateGPUDevice(api, error))
if (!Host::CreateGPUDevice(api, fullscreen, error))
{
Host::ReleaseRenderWindow();
return false;
@ -3436,9 +3449,9 @@ void System::UpdateSpeedLimiterState()
s_syncing_to_host = false;
s_syncing_to_host_with_vsync = false;
if (g_settings.sync_to_host_refresh_rate)
if (g_settings.sync_to_host_refresh_rate && g_gpu_device->HasMainSwapChain())
{
const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate;
const float host_refresh_rate = g_gpu_device->GetMainSwapChain()->GetWindowInfo().surface_refresh_rate;
if (host_refresh_rate > 0.0f)
{
const float ratio = host_refresh_rate / System::GetThrottleFrequency();
@ -3499,14 +3512,23 @@ void System::UpdateDisplayVSync()
// Avoid flipping vsync on and off by manually throttling when vsync is on.
const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode();
const bool allow_present_throttle = ShouldAllowPresentThrottle();
if (g_gpu_device->GetVSyncMode() == vsync_mode && g_gpu_device->IsPresentThrottleAllowed() == allow_present_throttle)
if (!g_gpu_device->HasMainSwapChain() ||
(g_gpu_device->GetMainSwapChain()->GetVSyncMode() == vsync_mode &&
g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed() == allow_present_throttle))
{
return;
}
VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast<size_t>(vsync_mode)],
s_syncing_to_host_with_vsync ? " (for throttling)" : "",
allow_present_throttle ? " (present throttle allowed)" : "");
g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle);
Error error;
if (!g_gpu_device->GetMainSwapChain()->SetVSyncMode(vsync_mode, allow_present_throttle, &error))
{
ERROR_LOG("Failed to update vsync mode to {}: {}", vsync_modes[static_cast<size_t>(vsync_mode)],
error.GetDescription());
}
}
GPUVSyncMode System::GetEffectiveVSyncMode()
@ -4230,6 +4252,16 @@ bool System::SwitchMediaSubImage(u32 index)
return true;
}
bool System::ShouldStartFullscreen()
{
return Host::GetBoolSettingValue("Main", "StartFullscreen", false);
}
bool System::ShouldStartPaused()
{
return Host::GetBoolSettingValue("Main", "StartPaused", false);
}
void System::CheckForSettingsChanges(const Settings& old_settings)
{
if (IsValid() &&
@ -5193,7 +5225,7 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur
u32 capture_height =
Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT);
const GPUTexture::Format capture_format =
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8;
const float fps = System::GetThrottleFrequency();
if (capture_video)
{
@ -5640,11 +5672,14 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows();
const GPUDevice::PresentResult pres = g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent();
const GPUDevice::PresentResult pres =
g_gpu_device->HasMainSwapChain() ?
(g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain())) :
GPUDevice::PresentResult::SkipPresent;
if (pres == GPUDevice::PresentResult::OK)
{
g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(explicit_present, present_time);
g_gpu_device->RenderImGui(g_gpu_device->GetMainSwapChain());
g_gpu_device->EndPresent(g_gpu_device->GetMainSwapChain(), explicit_present, present_time);
if (g_gpu_device->IsGPUTimingEnabled())
{
@ -5656,9 +5691,13 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
{
if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]]
HandleHostGPUDeviceLost();
else if (pres == GPUDevice::PresentResult::ExclusiveFullscreenLost)
HandleExclusiveFullscreenLost();
else
g_gpu_device->FlushCommands();
// Still need to kick ImGui or it gets cranky.
ImGui::Render();
ImGui::EndFrame();
}
ImGuiManager::NewFrame();

View File

@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const
static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
}
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
std::optional<WindowInfo> DisplayWidget::getWindowInfo(Error* error)
{
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this));
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this, error));
if (ret.has_value())
{
m_last_window_width = ret->surface_width;

View File

@ -11,6 +11,8 @@
#include <QtWidgets/QWidget>
#include <optional>
class Error;
class QCloseEvent;
class DisplayWidget final : public QWidget
@ -26,7 +28,7 @@ public:
int scaledWindowWidth() const;
int scaledWindowHeight() const;
std::optional<WindowInfo> getWindowInfo();
std::optional<WindowInfo> getWindowInfo(Error* error);
void updateRelativeMode(bool enabled);
void updateCursor(bool hidden);

View File

@ -852,9 +852,9 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_
m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"), QVariant(QString()));
if (current_adapter)
{
for (const std::string& mode_name : current_adapter->fullscreen_modes)
for (const GPUDevice::ExclusiveFullscreenMode& mode : current_adapter->fullscreen_modes)
{
const QString qmodename = QString::fromStdString(mode_name);
const QString qmodename = QtUtils::StringViewToQString(mode.ToString());
m_ui.fullscreenMode->addItem(qmodename, QVariant(qmodename));
}
}

View File

@ -83,6 +83,7 @@ static bool s_use_central_widget = false;
// UI thread VM validity.
static bool s_system_valid = false;
static bool s_system_paused = false;
static std::atomic_uint32_t s_system_locked{false};
static QString s_current_game_title;
static QString s_current_game_serial;
static QString s_current_game_path;
@ -220,30 +221,25 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
#endif
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
bool surfaceless, bool use_main_window_pos)
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
bool use_main_window_pos, Error* error)
{
DEV_LOG("acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false",
surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false");
DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false",
use_main_window_pos ? "true" : "false");
QWidget* container =
m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
const bool is_fullscreen = isRenderingFullscreen();
const bool is_rendering_to_main = isRenderingToMain();
const bool changing_surfaceless = (!m_display_widget != surfaceless);
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
!changing_surfaceless)
{
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
}
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
// .. except on Wayland, where everything tends to break if you don't recreate.
const bool has_container = (m_display_container != nullptr);
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main &&
has_container == needs_container && !needs_container && !changing_surfaceless)
if (m_display_created && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
!needs_container && !changing_surfaceless)
{
DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
@ -257,12 +253,11 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
}
else
{
container->showNormal();
if (use_main_window_pos)
container->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
container->showNormal();
}
updateDisplayWidgetCursor();
@ -270,7 +265,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
updateWindowState();
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return m_display_widget->getWindowInfo();
return m_display_widget->getWindowInfo(error);
}
destroyDisplayWidget(surfaceless);
@ -282,7 +277,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
createDisplayWidget(fullscreen, render_to_main, use_main_window_pos);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(error);
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
@ -2852,11 +2847,13 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem()
MainWindow::SystemLock::SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
: m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen)
{
s_system_locked.fetch_add(1, std::memory_order_release);
}
MainWindow::SystemLock::SystemLock(SystemLock&& lock)
: m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen)
{
s_system_locked.fetch_add(1, std::memory_order_release);
lock.m_dialog_parent = nullptr;
lock.m_was_paused = true;
lock.m_was_fullscreen = false;
@ -2864,6 +2861,8 @@ MainWindow::SystemLock::SystemLock(SystemLock&& lock)
MainWindow::SystemLock::~SystemLock()
{
DebugAssert(s_system_locked.load(std::memory_order_relaxed) > 0);
s_system_locked.fetch_sub(1, std::memory_order_release);
if (m_was_fullscreen)
g_emu_thread->setFullscreen(true, true);
if (!m_was_paused)
@ -2874,4 +2873,9 @@ void MainWindow::SystemLock::cancelResume()
{
m_was_paused = true;
m_was_fullscreen = false;
}
}
bool QtHost::IsSystemLocked()
{
return (s_system_locked.load(std::memory_order_acquire) > 0);
}

View File

@ -126,8 +126,8 @@ private Q_SLOTS:
bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message);
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
bool surfaceless, bool use_main_window_pos);
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
bool use_main_window_pos, Error* error);
void displayResizeRequested(qint32 width, qint32 height);
void releaseRenderWindow();
void focusDisplayWidget();

View File

@ -625,13 +625,6 @@ void EmuThread::loadSettings(SettingsInterface& si)
//
}
void EmuThread::setInitialState(std::optional<bool> override_fullscreen)
{
m_is_fullscreen = override_fullscreen.value_or(Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
m_is_rendering_to_main = shouldRenderToMain();
m_is_surfaceless = false;
}
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{
if (g_main_window)
@ -642,12 +635,16 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
updatePerformanceCounters();
}
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main)
// don't mess with fullscreen while locked
if (!QtHost::IsSystemLocked())
{
m_is_rendering_to_main = render_to_main;
if (g_gpu_device)
g_gpu_device->UpdateWindow();
const bool render_to_main = shouldRenderToMain();
if (m_is_rendering_to_main != render_to_main && !m_is_fullscreen)
{
m_is_rendering_to_main = render_to_main;
if (g_gpu_device)
Host::UpdateDisplayWindow(m_is_fullscreen);
}
}
}
@ -780,16 +777,15 @@ void EmuThread::startFullscreenUI()
// we want settings loaded so we choose the correct renderer
// this also sorts out input sources.
System::LoadSettings(false);
setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>());
m_is_rendering_to_main = shouldRenderToMain();
m_run_fullscreen_ui = true;
Error error;
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), &error) ||
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer),
s_start_fullscreen_ui_fullscreen, &error) ||
!FullscreenUI::Initialize())
{
Host::ReportErrorAsync("Error", error.GetDescription());
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
m_run_fullscreen_ui = false;
return;
}
@ -825,7 +821,6 @@ void EmuThread::stopFullscreenUI()
return;
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -841,7 +836,7 @@ void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
if (System::IsValidOrInitializing())
return;
setInitialState(params->override_fullscreen);
m_is_rendering_to_main = shouldRenderToMain();
Error error;
if (!System::BootSystem(std::move(*params), &error))
@ -974,7 +969,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
m_is_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow();
Host::UpdateDisplayWindow(fullscreen);
}
bool Host::IsFullscreen()
@ -984,6 +979,10 @@ bool Host::IsFullscreen()
void Host::SetFullscreen(bool enabled)
{
// don't mess with fullscreen while locked
if (QtHost::IsSystemLocked())
return;
g_emu_thread->setFullscreen(enabled, true);
}
@ -999,7 +998,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return;
m_is_surfaceless = surfaceless;
Host::UpdateDisplayWindow();
Host::UpdateDisplayWindow(false);
}
void EmuThread::requestDisplaySize(float scale)
@ -1016,20 +1015,18 @@ void EmuThread::requestDisplaySize(float scale)
System::RequestDisplaySize(scale);
}
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error)
{
DebugAssert(g_gpu_device);
u32 fs_width, fs_height;
float fs_refresh_rate;
m_is_exclusive_fullscreen = (m_is_fullscreen && g_gpu_device->SupportsExclusiveFullscreen() &&
GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate));
const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
m_is_fullscreen = fullscreen;
const bool window_fullscreen = m_is_fullscreen && !exclusive_fullscreen;
const bool render_to_main = !fullscreen && m_is_rendering_to_main;
const bool use_main_window_pos = shouldRenderToMain();
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless,
use_main_window_pos);
return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos,
error);
}
void EmuThread::releaseRenderWindow()
@ -1811,11 +1808,11 @@ void EmuThread::run()
m_event_loop->processEvents(QEventLoop::AllEvents);
System::Internal::IdlePollUpdate();
if (g_gpu_device)
if (g_gpu_device && g_gpu_device->HasMainSwapChain())
{
System::PresentDisplay(false, 0);
if (!g_gpu_device->IsVSyncModeBlocking())
g_gpu_device->ThrottlePresentation();
if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking())
g_gpu_device->GetMainSwapChain()->ThrottlePresentation();
}
}
}
@ -2005,9 +2002,10 @@ void Host::CommitBaseSettingChanges()
QtHost::QueueSettingsSave();
}
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error)
{
return g_emu_thread->acquireRenderWindow(recreate_window);
return g_emu_thread->acquireRenderWindow(fullscreen, exclusive_fullscreen, error);
}
void Host::ReleaseRenderWindow()

View File

@ -98,7 +98,7 @@ public:
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error);
void connectDisplaySignals(DisplayWidget* widget);
void releaseRenderWindow();
@ -135,8 +135,8 @@ Q_SIGNALS:
void systemPaused();
void systemResumed();
void gameListRefreshed();
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main,
bool surfaceless, bool use_main_window_pos);
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless,
bool use_main_window_pos, Error* error);
void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void focusDisplayWidgetRequested();
@ -220,7 +220,6 @@ private:
void createBackgroundControllerPollTimer();
void destroyBackgroundControllerPollTimer();
void setInitialState(std::optional<bool> override_fullscreen);
void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
std::function<void(bool)> callback) const;
@ -233,8 +232,6 @@ private:
bool m_run_fullscreen_ui = false;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_exclusive_fullscreen = false;
bool m_lost_exclusive_fullscreen = false;
bool m_is_surfaceless = false;
bool m_save_state_on_shutdown = false;
@ -320,6 +317,9 @@ bool ShouldShowDebugOptions();
bool IsSystemValid();
bool IsSystemPaused();
/// Returns true if any lock is in place.
bool IsSystemLocked();
/// Accessors for game information.
const QString& GetCurrentGameTitle();
const QString& GetCurrentGameSerial();

View File

@ -7,6 +7,7 @@
#include "core/game_list.h"
#include "core/system.h"
#include "common/error.h"
#include "common/log.h"
#include <QtCore/QCoreApplication>
@ -316,7 +317,7 @@ qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
}
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget)
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error)
{
WindowInfo wi;
@ -344,14 +345,14 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget)
}
else
{
qCritical() << "Unknown PNI platform " << platform_name;
Error::SetStringFmt(error, "Unknown PNI platform {}", platform_name.toStdString());
return std::nullopt;
}
#endif
const qreal dpr = GetDevicePixelRatioForWidget(widget);
wi.surface_width = static_cast<u32>(static_cast<qreal>(widget->width()) * dpr);
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
wi.surface_width = static_cast<u16>(static_cast<qreal>(widget->width()) * dpr);
wi.surface_height = static_cast<u16>(static_cast<qreal>(widget->height()) * dpr);
wi.surface_scale = static_cast<float>(dpr);
// Query refresh rate, we need it for sync.

View File

@ -19,7 +19,7 @@
#include <initializer_list>
#include <optional>
class ByteStream;
class Error;
class QComboBox;
class QFrame;
@ -116,7 +116,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
qreal GetDevicePixelRatioForWidget(const QWidget* widget);
/// Returns the common window info structure for a Qt widget.
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, Error* error = nullptr);
/// Saves a window's geometry to configuration. Returns false if the configuration was changed.
bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true);

View File

@ -110,7 +110,6 @@ if(ENABLE_OPENGL)
opengl_context_wgl.cpp
opengl_context_wgl.h
)
target_link_libraries(util PRIVATE "opengl32.lib")
endif()
if(LINUX OR BSD OR ANDROID)
@ -183,6 +182,9 @@ if(NOT ANDROID)
sdl_input_source.cpp
sdl_input_source.h
)
target_compile_definitions(util PUBLIC
ENABLE_SDL
)
target_link_libraries(util PUBLIC
cubeb
SDL2::SDL2

View File

@ -22,7 +22,7 @@
#include <d3dcompiler.h>
#include <dxgi1_5.h>
LOG_CHANNEL(D3D11Device);
LOG_CHANNEL(GPUDevice);
// We need to synchronize instance creation because of adapter enumeration from the UI thread.
static std::mutex s_instance_mutex;
@ -45,7 +45,10 @@ void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name)
#endif
}
D3D11Device::D3D11Device() = default;
D3D11Device::D3D11Device()
{
m_render_api = RenderAPI::D3D11;
}
D3D11Device::~D3D11Device()
{
@ -53,13 +56,11 @@ D3D11Device::~D3D11Device()
Assert(!m_device);
}
bool D3D11Device::HasSurface() const
{
return static_cast<bool>(m_swap_chain);
}
bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
std::unique_lock lock(s_instance_mutex);
@ -125,17 +126,14 @@ bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
INFO_LOG("Max device feature level: {}",
D3DCommon::GetFeatureLevelString(D3DCommon::GetRenderAPIVersionForFeatureLevel(m_max_feature_level)));
BOOL allow_tearing_supported = false;
hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
sizeof(allow_tearing_supported));
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
SetFeatures(disabled_features);
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
if (!wi.IsSurfaceless())
{
Error::SetStringView(error, "Failed to create swap chain");
return false;
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
exclusive_fullscreen_control, error);
if (!m_main_swap_chain)
return false;
}
if (!CreateBuffers())
@ -152,6 +150,7 @@ void D3D11Device::DestroyDevice()
std::unique_lock lock(s_instance_mutex);
DestroyBuffers();
m_main_swap_chain.reset();
m_context.Reset();
m_device.Reset();
}
@ -160,7 +159,6 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
{
const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel();
m_render_api = RenderAPI::D3D11;
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
m_max_multisamples = 1;
@ -202,96 +200,107 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
}
}
u32 D3D11Device::GetSwapChainBufferCount() const
D3D11SwapChain::D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode)
: GPUSwapChain(wi, vsync_mode, allow_present_throttle)
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
if (fullscreen_mode)
InitializeExclusiveFullscreenMode(fullscreen_mode);
}
bool D3D11Device::CreateSwapChain()
D3D11SwapChain::~D3D11SwapChain()
{
if (m_window_info.type != WindowInfo::Type::Win32)
return false;
m_swap_chain_rtv.Reset();
DestroySwapChain();
}
const DXGI_FORMAT dxgi_format = D3DCommon::GetFormatMapping(s_swap_chain_format).resource_format;
bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode)
{
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
DXGI_MODE_DESC fullscreen_mode = {};
ComPtr<IDXGIOutput> fullscreen_output;
if (Host::IsFullscreen())
{
u32 fullscreen_width, fullscreen_height;
float fullscreen_refresh_rate;
m_is_exclusive_fullscreen =
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width,
fullscreen_height, fullscreen_refresh_rate, dxgi_format,
&fullscreen_mode, fullscreen_output.GetAddressOf());
m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
D3D11Device::GetDXGIFactory(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
return m_fullscreen_mode.has_value();
}
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
m_vsync_mode = GPUVSyncMode::FIFO;
}
}
else
u32 D3D11SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
}
bool D3D11SwapChain::CreateSwapChain(Error* error)
{
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox)
{
m_is_exclusive_fullscreen = false;
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
m_vsync_mode = GPUVSyncMode::FIFO;
}
m_using_flip_model_swap_chain =
!Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || m_is_exclusive_fullscreen;
!Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || IsExclusiveFullscreen();
IDXGIFactory5* const dxgi_factory = D3D11Device::GetDXGIFactory();
ID3D11Device1* const d3d_device = D3D11Device::GetD3DDevice();
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = dxgi_format;
swap_chain_desc.Format = fm.resource_format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = GetSwapChainBufferCount();
swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode);
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
if (IsExclusiveFullscreen())
{
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
fs_sd_desc.Height = fullscreen_mode.Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling;
fs_sd_desc.Width = m_fullscreen_mode->Width;
fs_sd_desc.Height = m_fullscreen_mode->Height;
fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = m_fullscreen_mode->Scaling;
fs_desc.Windowed = FALSE;
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &fs_sd_desc, &fs_desc, m_fullscreen_output.Get(),
m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr))
{
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false;
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
m_fullscreen_output.Reset();
m_fullscreen_mode.reset();
m_using_allow_tearing = (m_using_flip_model_swap_chain && D3DCommon::SupportsAllowTearing(dxgi_factory));
}
}
if (!m_is_exclusive_fullscreen)
if (!IsExclusiveFullscreen())
{
VERBOSE_LOG("Creating a {}x{} {} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
m_using_allow_tearing = (m_using_flip_model_swap_chain && !IsExclusiveFullscreen() &&
D3DCommon::SupportsAllowTearing(D3D11Device::GetDXGIFactory()));
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
}
if (FAILED(hr) && m_using_flip_model_swap_chain)
@ -302,11 +311,11 @@ bool D3D11Device::CreateSwapChain()
m_using_flip_model_swap_chain = false;
m_using_allow_tearing = false;
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr);
return false;
}
}
@ -319,25 +328,29 @@ bool D3D11Device::CreateSwapChain()
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
}
if (!CreateSwapChainRTV())
{
DestroySwapChain();
return false;
}
// Render a frame as soon as possible to clear out whatever was previously being displayed.
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), s_clear_color.data());
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
return true;
}
bool D3D11Device::CreateSwapChainRTV()
void D3D11SwapChain::DestroySwapChain()
{
if (!m_swap_chain)
return;
// switch out of fullscreen before destroying
BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset();
}
bool D3D11SwapChain::CreateRTV(Error* error)
{
ComPtr<ID3D11Texture2D> backbuffer;
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf()));
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "GetBuffer() failed: ", hr);
return false;
}
@ -346,16 +359,17 @@ bool D3D11Device::CreateSwapChainRTV()
CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0,
backbuffer_desc.ArraySize);
hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.ReleaseAndGetAddressOf());
hr = D3D11Device::GetD3DDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc,
m_swap_chain_rtv.ReleaseAndGetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("CreateRenderTargetView for swap chain failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateRenderTargetView(): ", hr);
m_swap_chain_rtv.Reset();
return false;
}
m_window_info.surface_width = backbuffer_desc.Width;
m_window_info.surface_height = backbuffer_desc.Height;
m_window_info.surface_width = static_cast<u16>(backbuffer_desc.Width);
m_window_info.surface_height = static_cast<u16>(backbuffer_desc.Height);
m_window_info.surface_format = s_swap_chain_format;
VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
@ -374,65 +388,77 @@ bool D3D11Device::CreateSwapChainRTV()
return true;
}
void D3D11Device::DestroySwapChain()
bool D3D11SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
if (!m_swap_chain)
return;
m_swap_chain_rtv.Reset();
// switch out of fullscreen before destroying
BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset();
m_is_exclusive_fullscreen = false;
}
bool D3D11Device::UpdateWindow()
{
DestroySwapChain();
if (!AcquireWindow(false))
return false;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
{
ERROR_LOG("Failed to create swap chain on updated window");
return false;
}
return true;
}
void D3D11Device::DestroySurface()
{
DestroySwapChain();
}
void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
if (!m_swap_chain || m_is_exclusive_fullscreen)
return;
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
m_window_info.surface_scale = new_scale;
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
m_swap_chain_rtv.Reset();
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
if (FAILED(hr)) [[unlikely]]
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
{
Error::SetHResult(error, "ResizeBuffers() failed: ", hr);
return false;
}
if (!CreateSwapChainRTV())
Panic("Failed to recreate swap chain RTV after resize");
return CreateRTV(error);
}
bool D3D11SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
{
m_allow_present_throttle = allow_present_throttle;
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen())
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
mode = GPUVSyncMode::FIFO;
}
if (m_vsync_mode == mode)
return true;
const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode);
const u32 new_buffer_count = GetNewBufferCount(mode);
m_vsync_mode = mode;
if (old_buffer_count == new_buffer_count)
return true;
// Buffer count change => needs recreation.
m_swap_chain_rtv.Reset();
DestroySwapChain();
return CreateSwapChain(error) && CreateRTV(error);
}
std::unique_ptr<GPUSwapChain> D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error)
{
std::unique_ptr<D3D11SwapChain> ret;
if (wi.type != WindowInfo::Type::Win32)
{
Error::SetStringView(error, "Cannot create a swap chain on non-win32 window.");
return ret;
}
ret = std::make_unique<D3D11SwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode);
if (ret->CreateSwapChain(error) && ret->CreateRTV(error))
{
// Render a frame as soon as possible to clear out whatever was previously being displayed.
m_context->ClearRenderTargetView(ret->GetRTV(), s_clear_color.data());
ret->GetSwapChain()->Present(0, ret->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0);
}
else
{
ret.reset();
}
return ret;
}
bool D3D11Device::SupportsExclusiveFullscreen() const
@ -471,9 +497,16 @@ std::string D3D11Device::GetDriverInfo() const
return ret;
}
void D3D11Device::ExecuteAndWaitForGPUIdle()
void D3D11Device::FlushCommands()
{
m_context->Flush();
TrimTexturePool();
}
void D3D11Device::WaitForGPUIdle()
{
m_context->Flush();
TrimTexturePool();
}
bool D3D11Device::CreateBuffers()
@ -610,63 +643,29 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t)
T->CommitClear(m_context.Get());
}
void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
GPUDevice::PresentResult D3D11Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
m_allow_present_throttle = allow_present_throttle;
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
mode = GPUVSyncMode::FIFO;
}
if (m_vsync_mode == mode)
return;
const u32 old_buffer_count = GetSwapChainBufferCount();
m_vsync_mode = mode;
if (!m_swap_chain)
return;
if (GetSwapChainBufferCount() != old_buffer_count)
{
DestroySwapChain();
if (!CreateSwapChain())
Panic("Failed to recreate swap chain after vsync change.");
}
}
GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
{
if (!m_swap_chain)
{
// Note: Really slow on Intel...
m_context->Flush();
TrimTexturePool();
return PresentResult::SkipPresent;
}
D3D11SwapChain* const SC = static_cast<D3D11SwapChain*>(swap_chain);
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen;
if (m_is_exclusive_fullscreen &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
if (SC->IsExclusiveFullscreen() &&
(FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{
Host::SetFullscreen(false);
TrimTexturePool();
return PresentResult::SkipPresent;
return PresentResult::ExclusiveFullscreenLost;
}
// When using vsync, the time here seems to include the time for the buffer to become available.
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
// the time, when it's more like a couple of percent.
if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), GSVector4::rgba32(clear_color).F32);
m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr);
m_context->ClearRenderTargetView(SC->GetRTV(), GSVector4::rgba32(clear_color).F32);
m_context->OMSetRenderTargets(1, SC->GetRTVArray(), nullptr);
s_stats.num_render_passes++;
m_num_current_render_targets = 0;
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
@ -675,17 +674,19 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
return PresentResult::OK;
}
void D3D11Device::EndPresent(bool explicit_present, u64 present_time)
void D3D11Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
{
D3D11SwapChain* const SC = static_cast<D3D11SwapChain*>(swap_chain);
DebugAssert(!explicit_present && present_time == 0);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled)
if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() != GPUVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(sync_interval, flags);
const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
const UINT flags =
(SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
SC->GetSwapChain()->Present(sync_interval, flags);
if (m_gpu_timing_enabled)
KickTimestampQuery();
@ -693,7 +694,7 @@ void D3D11Device::EndPresent(bool explicit_present, u64 present_time)
TrimTexturePool();
}
void D3D11Device::SubmitPresent()
void D3D11Device::SubmitPresent(GPUSwapChain* swap_chain)
{
Panic("Not supported by this API.");
}
@ -1124,4 +1125,4 @@ void D3D11Device::DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex)
void D3D11Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type)
{
Panic("Barriers are not supported");
}
}

View File

@ -32,21 +32,23 @@ public:
~D3D11Device();
ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast<D3D11Device*>(g_gpu_device.get()); }
ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); }
ALWAYS_INLINE static ID3D11Device1* GetD3DDevice() { return GetInstance().m_device.Get(); }
ALWAYS_INLINE static ID3D11DeviceContext1* GetD3DContext() { return GetInstance().m_context.Get(); }
ALWAYS_INLINE static IDXGIFactory5* GetDXGIFactory() { return GetInstance().m_dxgi_factory.Get(); }
ALWAYS_INLINE static D3D_FEATURE_LEVEL GetMaxFeatureLevel() { return GetInstance().m_max_feature_level; }
bool HasSurface() const override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
bool SupportsExclusiveFullscreen() const override;
void DestroySurface() override;
std::string GetDriverInfo() const override;
void ExecuteAndWaitForGPUIdle() override;
void FlushCommands() override;
void WaitForGPUIdle() override;
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
@ -97,21 +99,21 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override;
void SubmitPresent() override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent(GPUSwapChain* swap_chain) override;
void UnbindPipeline(D3D11Pipeline* pl);
void UnbindTexture(D3D11Texture* tex);
protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) override;
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
private:
@ -136,11 +138,6 @@ private:
void SetFeatures(FeatureMask disabled_features);
u32 GetSwapChainBufferCount() const;
bool CreateSwapChain();
bool CreateSwapChainRTV();
void DestroySwapChain();
bool CreateBuffers();
void DestroyBuffers();
@ -161,8 +158,6 @@ private:
ComPtr<ID3DUserDefinedAnnotation> m_annotation;
ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
RasterizationStateMap m_rasterization_states;
DepthStateMap m_depth_states;
@ -170,10 +165,6 @@ private:
InputLayoutMap m_input_layouts;
D3D_FEATURE_LEVEL m_max_feature_level = D3D_FEATURE_LEVEL_10_0;
bool m_allow_tearing_supported = false;
bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
D3D11StreamBuffer m_vertex_buffer;
D3D11StreamBuffer m_index_buffer;
@ -207,4 +198,45 @@ private:
float m_accumulated_gpu_time = 0.0f;
};
class D3D11SwapChain : public GPUSwapChain
{
public:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
friend D3D11Device;
D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode);
~D3D11SwapChain() override;
ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); }
ALWAYS_INLINE ID3D11RenderTargetView* GetRTV() const { return m_swap_chain_rtv.Get(); }
ALWAYS_INLINE ID3D11RenderTargetView* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); }
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);
bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode);
bool CreateSwapChain(Error* error);
bool CreateRTV(Error* error);
void DestroySwapChain();
ComPtr<IDXGISwapChain1> m_swap_chain;
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
ComPtr<IDXGIOutput> m_fullscreen_output;
std::optional<DXGI_MODE_DESC> m_fullscreen_mode;
bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false;
};
void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name);

View File

@ -9,7 +9,7 @@
#include "common/error.h"
#include "common/log.h"
LOG_CHANNEL(D3D11Device);
LOG_CHANNEL(GPUDevice);
D3D11StreamBuffer::D3D11StreamBuffer()
{

View File

@ -13,7 +13,7 @@
#include <array>
LOG_CHANNEL(D3D11Device);
LOG_CHANNEL(GPUDevice);
std::unique_ptr<GPUTexture> D3D11Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,

View File

@ -8,8 +8,6 @@
#include "d3d12_texture.h"
#include "d3d_common.h"
#include "core/host.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/bitutils.h"
@ -27,7 +25,7 @@
#include <limits>
#include <mutex>
LOG_CHANNEL(D3D12Device);
LOG_CHANNEL(GPUDevice);
// Tweakables
enum : u32
@ -69,6 +67,8 @@ static u32 s_debug_scope_depth = 0;
D3D12Device::D3D12Device()
{
m_render_api = RenderAPI::D3D12;
#ifdef _DEBUG
s_debug_scope_depth = 0;
#endif
@ -117,8 +117,11 @@ D3D12Device::ComPtr<ID3D12RootSignature> D3D12Device::CreateRootSignature(const
return rs;
}
bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
bool D3D12Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
std::unique_lock lock(s_instance_mutex);
@ -238,8 +241,13 @@ bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
if (!CreateCommandLists(error) || !CreateDescriptorHeaps(error))
return false;
if (!m_window_info.IsSurfaceless() && !CreateSwapChain(error))
return false;
if (!wi.IsSurfaceless())
{
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
exclusive_fullscreen_control, error);
if (!m_main_swap_chain)
return false;
}
if (!CreateRootSignatures(error) || !CreateBuffers(error))
return false;
@ -256,7 +264,9 @@ void D3D12Device::DestroyDevice()
if (InRenderPass())
EndRenderPass();
WaitForGPUIdle();
WaitForAllFences();
m_main_swap_chain.reset();
DestroyDeferredObjects(m_current_fence_value);
DestroySamplers();
@ -264,7 +274,6 @@ void D3D12Device::DestroyDevice()
DestroyBuffers();
DestroyDescriptorHeaps();
DestroyRootSignatures();
DestroySwapChain();
DestroyCommandLists();
m_pipeline_library.Reset();
@ -656,7 +665,7 @@ void D3D12Device::WaitForFence(u64 fence)
DestroyDeferredObjects(m_completed_fence_value);
}
void D3D12Device::WaitForGPUIdle()
void D3D12Device::WaitForAllFences()
{
u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
for (u32 i = 0; i < (NUM_COMMAND_LISTS - 1); i++)
@ -666,7 +675,16 @@ void D3D12Device::WaitForGPUIdle()
}
}
void D3D12Device::ExecuteAndWaitForGPUIdle()
void D3D12Device::FlushCommands()
{
if (InRenderPass())
EndRenderPass();
SubmitCommandList(false);
TrimTexturePool();
}
void D3D12Device::WaitForGPUIdle()
{
if (InRenderPass())
EndRenderPass();
@ -790,54 +808,54 @@ void D3D12Device::DestroyDeferredObjects(u64 fence_value)
}
}
bool D3D12Device::HasSurface() const
D3D12SwapChain::D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode)
: GPUSwapChain(wi, vsync_mode, allow_present_throttle)
{
return static_cast<bool>(m_swap_chain);
if (fullscreen_mode)
InitializeExclusiveFullscreenMode(fullscreen_mode);
}
u32 D3D12Device::GetSwapChainBufferCount() const
D3D12SwapChain::~D3D12SwapChain()
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
DestroyRTVs();
DestroySwapChain();
}
bool D3D12Device::CreateSwapChain(Error* error)
bool D3D12SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode)
{
if (m_window_info.type != WindowInfo::Type::Win32)
{
Error::SetStringView(error, "D3D12 expects a Win32 window.");
return false;
}
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
DXGI_MODE_DESC fullscreen_mode = {};
ComPtr<IDXGIOutput> fullscreen_output;
if (Host::IsFullscreen())
{
u32 fullscreen_width, fullscreen_height;
float fullscreen_refresh_rate;
m_is_exclusive_fullscreen =
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width,
fullscreen_height, fullscreen_refresh_rate, fm.resource_format,
&fullscreen_mode, fullscreen_output.GetAddressOf());
m_fullscreen_mode =
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode,
fm.resource_format, m_fullscreen_output.GetAddressOf());
return m_fullscreen_mode.has_value();
}
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
m_vsync_mode = GPUVSyncMode::FIFO;
}
}
else
u32 D3D12SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
}
bool D3D12SwapChain::CreateSwapChain(D3D12Device& dev, Error* error)
{
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox)
{
m_is_exclusive_fullscreen = false;
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
m_vsync_mode = GPUVSyncMode::FIFO;
}
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
@ -845,45 +863,44 @@ bool D3D12Device::CreateSwapChain(Error* error)
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = fm.resource_format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = GetSwapChainBufferCount();
swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode);
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
m_using_allow_tearing = (m_allow_tearing_supported && !m_is_exclusive_fullscreen);
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
if (IsExclusiveFullscreen())
{
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
fs_sd_desc.Height = fullscreen_mode.Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling;
fs_sd_desc.Width = m_fullscreen_mode->Width;
fs_sd_desc.Height = m_fullscreen_mode->Height;
fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = m_fullscreen_mode->Scaling;
fs_desc.Windowed = FALSE;
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &fs_sd_desc, &fs_desc,
m_fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr))
{
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false;
m_using_allow_tearing = m_allow_tearing_supported;
m_fullscreen_output.Reset();
m_fullscreen_mode.reset();
}
}
if (!m_is_exclusive_fullscreen)
if (!IsExclusiveFullscreen())
{
VERBOSE_LOG("Creating a {}x{} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf());
m_using_allow_tearing = D3DCommon::SupportsAllowTearing(dev.GetDXGIFactory());
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &swap_chain_desc, nullptr,
nullptr, m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr))
{
Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr);
@ -891,22 +908,14 @@ bool D3D12Device::CreateSwapChain(Error* error)
}
}
hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
hr = dev.GetDXGIFactory()->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
if (FAILED(hr))
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
if (!CreateSwapChainRTV(error))
{
DestroySwapChain();
return false;
}
// Render a frame as soon as possible to clear out whatever was previously being displayed.
RenderBlankFrame();
return true;
}
bool D3D12Device::CreateSwapChainRTV(Error* error)
bool D3D12SwapChain::CreateRTV(D3D12Device& dev, Error* error)
{
DXGI_SWAP_CHAIN_DESC swap_chain_desc;
HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc);
@ -925,148 +934,162 @@ bool D3D12Device::CreateSwapChainRTV(Error* error)
if (FAILED(hr))
{
Error::SetHResult(error, "GetBuffer for RTV failed: ", hr);
DestroySwapChainRTVs();
DestroyRTVs();
return false;
}
D3D12::SetObjectName(backbuffer.Get(), TinyString::from_format("Swap Chain Buffer #{}", i));
D3D12DescriptorHandle rtv;
if (!m_rtv_heap_manager.Allocate(&rtv))
if (!dev.GetRTVHeapManager().Allocate(&rtv))
{
Error::SetStringView(error, "Failed to allocate RTV handle.");
DestroySwapChainRTVs();
DestroyRTVs();
return false;
}
m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv);
dev.GetDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv);
m_swap_chain_buffers.emplace_back(std::move(backbuffer), rtv);
}
m_window_info.surface_width = swap_chain_desc.BufferDesc.Width;
m_window_info.surface_height = swap_chain_desc.BufferDesc.Height;
m_window_info.surface_width = static_cast<u16>(swap_chain_desc.BufferDesc.Width);
m_window_info.surface_height = static_cast<u16>(swap_chain_desc.BufferDesc.Height);
m_window_info.surface_format = s_swap_chain_format;
VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
if (m_window_info.type == WindowInfo::Type::Win32)
BOOL fullscreen = FALSE;
DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
SUCCEEDED(m_swap_chain->GetDesc(&desc)))
{
BOOL fullscreen = FALSE;
DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
SUCCEEDED(m_swap_chain->GetDesc(&desc)))
{
m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
}
m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
}
m_current_swap_chain_buffer = 0;
return true;
}
void D3D12Device::DestroySwapChainRTVs()
void D3D12SwapChain::DestroyRTVs()
{
if (m_swap_chain_buffers.empty())
return;
D3D12Device& dev = D3D12Device::GetInstance();
// Runtime gets cranky if we don't submit the current buffer...
if (InRenderPass())
EndRenderPass();
SubmitCommandList(true);
if (dev.InRenderPass())
dev.EndRenderPass();
dev.SubmitCommandList(true);
for (auto it = m_swap_chain_buffers.rbegin(); it != m_swap_chain_buffers.rend(); ++it)
{
m_rtv_heap_manager.Free(it->second.index);
dev.GetRTVHeapManager().Free(it->second.index);
it->first.Reset();
}
m_swap_chain_buffers.clear();
m_current_swap_chain_buffer = 0;
}
void D3D12Device::DestroySwapChain()
void D3D12SwapChain::DestroySwapChain()
{
if (!m_swap_chain)
return;
DestroySwapChainRTVs();
// switch out of fullscreen before destroying
BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset();
m_is_exclusive_fullscreen = false;
}
void D3D12Device::RenderBlankFrame()
bool D3D12SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
{
if (InRenderPass())
EndRenderPass();
m_allow_present_throttle = allow_present_throttle;
auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr);
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT);
SubmitCommandList(false);
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
}
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen())
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
mode = GPUVSyncMode::FIFO;
}
bool D3D12Device::UpdateWindow()
{
WaitForGPUIdle();
DestroySwapChain();
if (!AcquireWindow(false))
return false;
if (m_window_info.IsSurfaceless())
if (m_vsync_mode == mode)
return true;
Error error;
if (!CreateSwapChain(&error))
{
ERROR_LOG("Failed to create swap chain on updated window: {}", error.GetDescription());
return false;
}
const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode);
const u32 new_buffer_count = GetNewBufferCount(mode);
m_vsync_mode = mode;
if (old_buffer_count == new_buffer_count)
return true;
RenderBlankFrame();
return true;
// Buffer count change => needs recreation.
DestroyRTVs();
DestroySwapChain();
D3D12Device& dev = D3D12Device::GetInstance();
return CreateSwapChain(dev, error) && CreateRTV(dev, error);
}
void D3D12Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
if (!m_swap_chain)
return;
m_window_info.surface_scale = new_scale;
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
DestroySwapChainRTVs();
DestroyRTVs();
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
if (FAILED(hr))
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
Error error;
if (!CreateSwapChainRTV(&error))
{
ERROR_LOG("Failed to recreate swap chain RTV after resize", error.GetDescription());
Panic("Failed to recreate swap chain RTV after resize");
}
return CreateRTV(D3D12Device::GetInstance(), error);
}
void D3D12Device::DestroySurface()
std::unique_ptr<GPUSwapChain> D3D12Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error)
{
DestroySwapChainRTVs();
DestroySwapChain();
std::unique_ptr<D3D12SwapChain> ret;
if (wi.type != WindowInfo::Type::Win32)
{
Error::SetStringView(error, "Cannot create a swap chain on non-win32 window.");
return ret;
}
ret = std::make_unique<D3D12SwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode);
if (ret->CreateSwapChain(*this, error) && ret->CreateRTV(*this, error))
{
// Render a frame as soon as possible to clear out whatever was previously being displayed.
RenderBlankFrame(ret.get());
}
else
{
ret.reset();
}
return ret;
}
void D3D12Device::RenderBlankFrame(D3D12SwapChain* swap_chain)
{
if (InRenderPass())
EndRenderPass();
const D3D12SwapChain::BufferPair& swap_chain_buf = swap_chain->GetCurrentBuffer();
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr);
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT);
SubmitCommandList(false);
swap_chain->GetSwapChain()->Present(0, swap_chain->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0);
swap_chain->AdvanceBuffer();
}
bool D3D12Device::SupportsTextureFormat(GPUTexture::Format format) const
@ -1105,79 +1128,77 @@ std::string D3D12Device::GetDriverInfo() const
return ret;
}
void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
mode = GPUVSyncMode::FIFO;
}
if (m_vsync_mode == mode)
return;
const u32 old_buffer_count = GetSwapChainBufferCount();
m_vsync_mode = mode;
if (!m_swap_chain)
return;
if (GetSwapChainBufferCount() != old_buffer_count)
{
DestroySwapChain();
Error error;
if (!CreateSwapChain(&error))
{
ERROR_LOG("Failed to recreate swap chain after vsync change: {}", error.GetDescription());
Panic("Failed to recreate swap chain after vsync change.");
}
}
}
GPUDevice::PresentResult D3D12Device::BeginPresent(u32 clear_color)
GPUDevice::PresentResult D3D12Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
if (InRenderPass())
EndRenderPass();
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
if (!m_swap_chain)
{
SubmitCommandList(false);
TrimTexturePool();
return PresentResult::SkipPresent;
}
// TODO: Check if the device was lost.
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen;
if (m_is_exclusive_fullscreen &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
if (SC->IsExclusiveFullscreen() &&
(FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
FlushCommands();
TrimTexturePool();
return PresentResult::SkipPresent;
return PresentResult::ExclusiveFullscreenLost;
}
BeginSwapChainRenderPass(clear_color);
m_current_swap_chain = SC;
const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer();
ID3D12GraphicsCommandList4* const cmdlist = GetCommandList();
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
// All textures should be in shader read only optimal already, but just in case..
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
for (u32 i = 0; i < num_textures; i++)
{
if (m_current_textures[i])
m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second,
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}},
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
GSVector4::store<false>(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color));
cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE);
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_num_current_render_targets = 0;
m_dirty_flags =
(m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0);
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
m_current_depth_target = nullptr;
m_in_render_pass = true;
s_stats.num_render_passes++;
// Clear pipeline, it's likely incompatible.
m_current_pipeline = nullptr;
return PresentResult::OK;
}
void D3D12Device::EndPresent(bool explicit_present, u64 present_time)
void D3D12Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
{
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
DebugAssert(present_time == 0);
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass();
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
DebugAssert(SC == m_current_swap_chain);
m_current_swap_chain = nullptr;
const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer();
SC->AdvanceBuffer();
ID3D12GraphicsCommandList* cmdlist = GetCommandList();
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
@ -1187,18 +1208,19 @@ void D3D12Device::EndPresent(bool explicit_present, u64 present_time)
TrimTexturePool();
if (!explicit_present)
SubmitPresent();
SubmitPresent(swap_chain);
}
void D3D12Device::SubmitPresent()
void D3D12Device::SubmitPresent(GPUSwapChain* swap_chain)
{
DebugAssert(m_swap_chain);
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
if (m_device_was_lost) [[unlikely]]
return;
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(sync_interval, flags);
const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
const UINT flags =
(SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
SC->GetSwapChain()->Present(sync_interval, flags);
}
#ifdef _DEBUG
@ -1251,7 +1273,6 @@ void D3D12Device::InsertDebugMessage(const char* msg)
void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features)
{
m_render_api = RenderAPI::D3D12;
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
m_max_multisamples = 1;
@ -1286,11 +1307,6 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab
m_features.pipeline_cache = true;
m_features.prefer_unused_textures = true;
BOOL allow_tearing_supported = false;
HRESULT hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
sizeof(allow_tearing_supported));
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
m_features.raster_order_views = false;
if (!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS))
{
@ -1841,7 +1857,7 @@ void D3D12Device::BeginRenderPass()
else
{
// Re-rendering to swap chain.
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
const auto& swap_chain_buf = m_current_swap_chain->GetCurrentBuffer();
rt_desc[0] = {swap_chain_buf.second,
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, {}},
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
@ -1869,43 +1885,6 @@ void D3D12Device::BeginRenderPass()
SetInitialPipelineState();
}
void D3D12Device::BeginSwapChainRenderPass(u32 clear_color)
{
DebugAssert(!InRenderPass());
ID3D12GraphicsCommandList4* const cmdlist = GetCommandList();
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_RENDER_TARGET);
// All textures should be in shader read only optimal already, but just in case..
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
for (u32 i = 0; i < num_textures; i++)
{
if (m_current_textures[i])
m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second,
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}},
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
GSVector4::store<false>(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color));
cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE);
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_num_current_render_targets = 0;
m_dirty_flags =
(m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0);
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
m_current_depth_target = nullptr;
m_in_render_pass = true;
s_stats.num_render_passes++;
// Clear pipeline, it's likely incompatible.
m_current_pipeline = nullptr;
}
bool D3D12Device::InRenderPass()
{
return m_in_render_pass;

View File

@ -37,6 +37,8 @@ namespace D3D12MA {
class Allocator;
}
class D3D12SwapChain;
class D3D12Device final : public GPUDevice
{
public:
@ -58,17 +60,16 @@ public:
D3D12Device();
~D3D12Device() override;
bool HasSurface() const override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;
void ExecuteAndWaitForGPUIdle() override;
void FlushCommands() override;
void WaitForGPUIdle() override;
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
@ -119,23 +120,22 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override;
void SubmitPresent() override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent(GPUSwapChain* swap_chain) override;
// Global state accessors
ALWAYS_INLINE static D3D12Device& GetInstance() { return *static_cast<D3D12Device*>(g_gpu_device.get()); }
ALWAYS_INLINE IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); }
ALWAYS_INLINE ID3D12Device1* GetDevice() const { return m_device.Get(); }
ALWAYS_INLINE ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.Get(); }
ALWAYS_INLINE IDXGIFactory5* GetDXGIFactory() { return m_dxgi_factory.Get(); }
ALWAYS_INLINE D3D12MA::Allocator* GetAllocator() const { return m_allocator.Get(); }
void WaitForGPUIdle();
void WaitForAllFences();
// Descriptor manager access.
D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; }
@ -173,6 +173,12 @@ public:
// Also invokes callbacks for completion.
void WaitForFence(u64 fence_counter);
// Ends a render pass if we're currently in one.
// When Bind() is next called, the pass will be restarted.
void BeginRenderPass();
void EndRenderPass();
bool InRenderPass();
/// Ends any render pass, executes the command buffer, and invalidates cached state.
void SubmitCommandList(bool wait_for_completion);
void SubmitCommandList(bool wait_for_completion, const std::string_view reason);
@ -183,8 +189,10 @@ public:
void UnbindTextureBuffer(D3D12TextureBuffer* buf);
protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) override;
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
@ -232,12 +240,6 @@ private:
void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr);
void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features);
u32 GetSwapChainBufferCount() const;
bool CreateSwapChain(Error* error);
bool CreateSwapChainRTV(Error* error);
void DestroySwapChainRTVs();
void DestroySwapChain();
bool CreateCommandLists(Error* error);
void DestroyCommandLists();
bool CreateRootSignatures(Error* error);
@ -252,7 +254,7 @@ private:
void DestroySamplers();
void DestroyDeferredObjects(u64 fence_value);
void RenderBlankFrame();
void RenderBlankFrame(D3D12SwapChain* swap_chain);
void MoveToNextCommandList();
bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
@ -280,13 +282,6 @@ private:
bool UpdateParametersForLayout(u32 dirty);
bool UpdateRootParameters(u32 dirty);
// Ends a render pass if we're currently in one.
// When Bind() is next called, the pass will be restarted.
void BeginRenderPass();
void BeginSwapChainRenderPass(u32 clear_color);
void EndRenderPass();
bool InRenderPass();
ComPtr<IDXGIAdapter1> m_adapter;
ComPtr<ID3D12Device1> m_device;
ComPtr<ID3D12CommandQueue> m_command_queue;
@ -299,15 +294,9 @@ private:
std::array<CommandList, NUM_COMMAND_LISTS> m_command_lists;
u32 m_current_command_list = NUM_COMMAND_LISTS - 1;
bool m_device_was_lost = false;
ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
std::vector<std::pair<ComPtr<ID3D12Resource>, D3D12DescriptorHandle>> m_swap_chain_buffers;
u32 m_current_swap_chain_buffer = 0;
bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
bool m_device_was_lost = false;
D3D12DescriptorHeapManager m_descriptor_heap_manager;
D3D12DescriptorHeapManager m_rtv_heap_manager;
@ -358,4 +347,52 @@ private:
D3D12TextureBuffer* m_current_texture_buffer = nullptr;
GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1);
GSVector4i m_current_scissor = {};
D3D12SwapChain* m_current_swap_chain = nullptr;
};
class D3D12SwapChain : public GPUSwapChain
{
public:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
friend D3D12Device;
using BufferPair = std::pair<ComPtr<ID3D12Resource>, D3D12DescriptorHandle>;
D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode);
~D3D12SwapChain() override;
ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); }
ALWAYS_INLINE const BufferPair& GetCurrentBuffer() const { return m_swap_chain_buffers[m_current_swap_chain_buffer]; }
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
void AdvanceBuffer()
{
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
}
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
private:
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);
bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode);
bool CreateSwapChain(D3D12Device& dev, Error* error);
bool CreateRTV(D3D12Device& dev, Error* error);
void DestroySwapChain();
void DestroyRTVs();
ComPtr<IDXGISwapChain1> m_swap_chain;
std::vector<BufferPair> m_swap_chain_buffers;
u32 m_current_swap_chain_buffer = 0;
bool m_using_allow_tearing = false;
ComPtr<IDXGIOutput> m_fullscreen_output;
std::optional<DXGI_MODE_DESC> m_fullscreen_mode;
};

View File

@ -14,7 +14,7 @@
#include <d3dcompiler.h>
LOG_CHANNEL(D3D12Device);
LOG_CHANNEL(GPUDevice);
D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(stage), m_bytecode(std::move(bytecode))
{

View File

@ -13,7 +13,7 @@
#include <algorithm>
LOG_CHANNEL(D3D12StreamBuffer);
LOG_CHANNEL(GPUDevice);
D3D12StreamBuffer::D3D12StreamBuffer() = default;

View File

@ -15,7 +15,7 @@
#include "D3D12MemAlloc.h"
LOG_CHANNEL(D3D12Device);
LOG_CHANNEL(GPUDevice);
D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,

View File

@ -19,7 +19,7 @@
#include <dxcapi.h>
#include <dxgi1_5.h>
LOG_CHANNEL(D3DCommon);
LOG_CHANNEL(GPUDevice);
namespace D3DCommon {
namespace {
@ -123,6 +123,14 @@ Microsoft::WRL::ComPtr<IDXGIFactory5> D3DCommon::CreateFactory(bool debug, Error
return factory;
}
bool D3DCommon::SupportsAllowTearing(IDXGIFactory5* factory)
{
BOOL allow_tearing_supported = false;
HRESULT hr = factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
sizeof(allow_tearing_supported));
return (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
}
static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList& adapter_names, std::string adapter_name)
{
if (std::any_of(adapter_names.begin(), adapter_names.end(),
@ -183,9 +191,11 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
{
for (const DXGI_MODE_DESC& mode : dmodes)
{
ai.fullscreen_modes.push_back(GPUDevice::GetFullscreenModeString(
mode.Width, mode.Height,
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
ai.fullscreen_modes.push_back(
GPUDevice::ExclusiveFullscreenMode{.width = mode.Width,
.height = mode.Height,
.refresh_rate = static_cast<float>(mode.RefreshRate.Numerator) /
static_cast<float>(mode.RefreshRate.Denominator)});
}
}
else
@ -211,10 +221,13 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
return adapters;
}
bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
u32 height, float refresh_rate, DXGI_FORMAT format,
DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output)
std::optional<DXGI_MODE_DESC>
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
DXGI_FORMAT format, IDXGIOutput** output)
{
std::optional<DXGI_MODE_DESC> ret;
// We need to find which monitor the window is located on.
const GSVector4i client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);
@ -260,7 +273,7 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
if (!first_output)
{
ERROR_LOG("No DXGI output found. Can't use exclusive fullscreen.");
return false;
return ret;
}
WARNING_LOG("No DXGI output found for window, using first.");
@ -268,22 +281,25 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
}
DXGI_MODE_DESC request_mode = {};
request_mode.Width = width;
request_mode.Height = height;
request_mode.Width = requested_fullscreen_mode->width;
request_mode.Height = requested_fullscreen_mode->height;
request_mode.Format = format;
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(requested_fullscreen_mode->refresh_rate * 1000.0f));
request_mode.RefreshRate.Denominator = 1000u;
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
ret = DXGI_MODE_DESC();
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, &ret.value(), nullptr)) ||
request_mode.Format != format)
{
ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
return false;
ret.reset();
return ret;
}
*output = intersecting_output.Get();
intersecting_output->AddRef();
return true;
return ret;
}
Microsoft::WRL::ComPtr<IDXGIAdapter1> D3DCommon::GetAdapterByName(IDXGIFactory5* factory, std::string_view name)

View File

@ -35,14 +35,16 @@ D3D_FEATURE_LEVEL GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter);
// create a dxgi factory
Microsoft::WRL::ComPtr<IDXGIFactory5> CreateFactory(bool debug, Error* error);
bool SupportsAllowTearing(IDXGIFactory5* factory);
// returns a list of all adapter names
GPUDevice::AdapterInfoList GetAdapterInfoList();
// returns the fullscreen mode to use for the specified dimensions
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode,
IDXGIOutput** output);
std::optional<DXGI_MODE_DESC>
GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
DXGI_FORMAT format, IDXGIOutput** output);
// get an adapter based on name
Microsoft::WRL::ComPtr<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, std::string_view name);

View File

@ -3,8 +3,6 @@
#include "gpu_device.h"
#include "compress_helpers.h"
#include "core/host.h" // TODO: Remove, needed for getting fullscreen mode.
#include "core/settings.h" // TODO: Remove, needed for dump directory.
#include "gpu_framebuffer_manager.h"
#include "shadergen.h"
@ -44,6 +42,7 @@ LOG_CHANNEL(GPUDevice);
std::unique_ptr<GPUDevice> g_gpu_device;
static std::string s_shader_dump_path;
static std::string s_pipeline_cache_path;
static size_t s_pipeline_cache_size;
static std::array<u8, SHA1Digest::DIGEST_SIZE> s_pipeline_cache_hash;
@ -226,6 +225,49 @@ size_t GPUFramebufferManagerBase::KeyHash::operator()(const Key& key) const
return XXH32(&key, sizeof(key), 0x1337);
}
GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle)
: m_window_info(wi), m_vsync_mode(vsync_mode), m_allow_present_throttle(allow_present_throttle)
{
}
GPUSwapChain::~GPUSwapChain() = default;
bool GPUSwapChain::ShouldSkipPresentingFrame()
{
// Only needed with FIFO. But since we're so fast, we allow it always.
if (!m_allow_present_throttle)
return false;
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const float throttle_period = 1.0f / throttle_rate;
const u64 now = Common::Timer::GetCurrentValue();
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < throttle_period)
return true;
m_last_frame_displayed_time = now;
return false;
}
void GPUSwapChain::ThrottlePresentation()
{
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
const u64 current_ts = Common::Timer::GetCurrentValue();
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
// allow time for the actual rendering.
const u64 max_variance = sleep_period * 2;
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
m_last_frame_displayed_time = current_ts + sleep_period;
else
m_last_frame_displayed_time += sleep_period;
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
}
GPUDevice::GPUDevice()
{
ResetStatistics();
@ -346,21 +388,18 @@ GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api)
return ret;
}
bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version,
bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
bool GPUDevice::Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
const WindowInfo& wi, GPUVSyncMode vsync, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
m_vsync_mode = vsync;
m_allow_present_throttle = allow_present_throttle;
m_debug_device = debug_device;
s_shader_dump_path = shader_dump_path;
if (!AcquireWindow(true))
{
Error::SetStringView(error, "Failed to acquire window from host.");
return false;
}
if (!CreateDevice(adapter, exclusive_fullscreen_control, disabled_features, error))
INFO_LOG("Main render window is {}x{}.", wi.surface_width, wi.surface_height);
if (!CreateDeviceAndMainSwapChain(adapter, disabled_features, wi, vsync, allow_present_throttle,
exclusive_fullscreen_mode, exclusive_fullscreen_control, error))
{
if (error && !error->IsValid())
error->SetStringView("Failed to create device.");
@ -383,14 +422,30 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p
void GPUDevice::Destroy()
{
s_shader_dump_path = {};
PurgeTexturePool();
if (HasSurface())
DestroySurface();
DestroyResources();
CloseShaderCache();
DestroyDevice();
}
bool GPUDevice::RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
m_main_swap_chain.reset();
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
exclusive_fullscreen_control, error);
return static_cast<bool>(m_main_swap_chain);
}
void GPUDevice::DestroyMainSwapChain()
{
m_main_swap_chain.reset();
}
bool GPUDevice::SupportsExclusiveFullscreen() const
{
return false;
@ -533,17 +588,6 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
return false;
}
bool GPUDevice::AcquireWindow(bool recreate_window)
{
std::optional<WindowInfo> wi = Host::AcquireRenderWindow(recreate_window);
if (!wi.has_value())
return false;
INFO_LOG("Render window is {}x{}.", wi->surface_width, wi->surface_height);
m_window_info = wi.value();
return true;
}
bool GPUDevice::CreateResources(Error* error)
{
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) ||
@ -587,7 +631,7 @@ bool GPUDevice::CreateResources(Error* error)
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
plconfig.blend.write_mask = 0x7;
plconfig.SetTargetFormats(HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8);
plconfig.SetTargetFormats(m_main_swap_chain ? m_main_swap_chain->GetFormat() : GPUTexture::Format::RGBA8);
plconfig.samples = 1;
plconfig.per_sample_shading = false;
plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags;
@ -619,23 +663,23 @@ void GPUDevice::DestroyResources()
m_shader_cache.Close();
}
void GPUDevice::RenderImGui()
void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
{
GL_SCOPE("RenderImGui");
ImGui::Render();
const ImDrawData* draw_data = ImGui::GetDrawData();
if (draw_data->CmdListsCount == 0)
if (draw_data->CmdListsCount == 0 || !swap_chain)
return;
SetPipeline(m_imgui_pipeline.get());
SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
const float L = 0.0f;
const float R = static_cast<float>(m_window_info.surface_width);
const float R = static_cast<float>(swap_chain->GetWidth());
const float T = 0.0f;
const float B = static_cast<float>(m_window_info.surface_height);
const float B = static_cast<float>(swap_chain->GetHeight());
const float ortho_projection[4][4] = {
{2.0f / (R - L), 0.0f, 0.0f, 0.0f},
{0.0f, 2.0f / (T - B), 0.0f, 0.0f},
@ -666,8 +710,7 @@ void GPUDevice::RenderImGui()
if (flip)
{
const s32 height = static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y);
const s32 flipped_y =
static_cast<s32>(m_window_info.surface_height) - static_cast<s32>(pcmd->ClipRect.y) - height;
const s32 flipped_y = static_cast<s32>(swap_chain->GetHeight()) - static_cast<s32>(pcmd->ClipRect.y) - height;
SetScissor(static_cast<s32>(pcmd->ClipRect.x), flipped_y, static_cast<s32>(pcmd->ClipRect.z - pcmd->ClipRect.x),
height);
}
@ -789,69 +832,59 @@ std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, GPUShad
return shader;
}
bool GPUDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
std::optional<GPUDevice::ExclusiveFullscreenMode> GPUDevice::ExclusiveFullscreenMode::Parse(std::string_view str)
{
const std::string mode = Host::GetBaseStringSettingValue("GPU", "FullscreenMode", "");
if (!mode.empty())
std::optional<ExclusiveFullscreenMode> ret;
std::string_view::size_type sep1 = str.find('x');
if (sep1 != std::string_view::npos)
{
const std::string_view mode_view = mode;
std::string_view::size_type sep1 = mode.find('x');
if (sep1 != std::string_view::npos)
{
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
std::optional<u32> owidth = StringUtil::FromChars<u32>(str.substr(0, sep1));
sep1++;
while (sep1 < str.length() && std::isspace(str[sep1]))
sep1++;
while (sep1 < mode.length() && std::isspace(mode[sep1]))
sep1++;
if (owidth.has_value() && sep1 < mode.length())
if (owidth.has_value() && sep1 < str.length())
{
std::string_view::size_type sep2 = str.find('@', sep1);
if (sep2 != std::string_view::npos)
{
std::string_view::size_type sep2 = mode.find('@', sep1);
if (sep2 != std::string_view::npos)
{
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1));
std::optional<u32> oheight = StringUtil::FromChars<u32>(str.substr(sep1, sep2 - sep1));
sep2++;
while (sep2 < str.length() && std::isspace(str[sep2]))
sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2]))
sep2++;
if (oheight.has_value() && sep2 < mode.length())
if (oheight.has_value() && sep2 < str.length())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(str.substr(sep2));
if (orefresh_rate.has_value())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2));
if (orefresh_rate.has_value())
{
*width = owidth.value();
*height = oheight.value();
*refresh_rate = orefresh_rate.value();
return true;
}
ret = ExclusiveFullscreenMode{
.width = owidth.value(), .height = oheight.value(), .refresh_rate = orefresh_rate.value()};
}
}
}
}
}
*width = 0;
*height = 0;
*refresh_rate = 0;
return false;
return ret;
}
std::string GPUDevice::GetFullscreenModeString(u32 width, u32 height, float refresh_rate)
TinyString GPUDevice::ExclusiveFullscreenMode::ToString() const
{
return fmt::format("{} x {} @ {} hz", width, height, refresh_rate);
}
std::string GPUDevice::GetShaderDumpPath(std::string_view name)
{
return Path::Combine(EmuFolders::Dumps, name);
return TinyString::from_format("{} x {} @ {} hz", width, height, refresh_rate);
}
void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors)
{
static u32 next_bad_shader_id = 0;
const std::string filename = GetShaderDumpPath(fmt::format("bad_shader_{}.txt", ++next_bad_shader_id));
if (s_shader_dump_path.empty())
return;
const std::string filename =
Path::Combine(s_shader_dump_path, TinyString::from_format("bad_shader_{}.txt", ++next_bad_shader_id));
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
if (fp)
{
@ -1124,42 +1157,6 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
return true;
}
bool GPUDevice::ShouldSkipPresentingFrame()
{
// Only needed with FIFO. But since we're so fast, we allow it always.
if (!m_allow_present_throttle)
return false;
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const float throttle_period = 1.0f / throttle_rate;
const u64 now = Common::Timer::GetCurrentValue();
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < throttle_period)
return true;
m_last_frame_displayed_time = now;
return false;
}
void GPUDevice::ThrottlePresentation()
{
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
const u64 current_ts = Common::Timer::GetCurrentValue();
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
// allow time for the actual rendering.
const u64 max_variance = sleep_period * 2;
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
m_last_frame_displayed_time = current_ts + sleep_period;
else
m_last_frame_displayed_time += sleep_period;
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
}
bool GPUDevice::SetGPUTimingEnabled(bool enabled)
{
return false;

View File

@ -453,6 +453,38 @@ protected:
u32 m_current_position = 0;
};
class GPUSwapChain
{
public:
GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle);
virtual ~GPUSwapChain();
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; }
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); }
ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; }
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
bool ShouldSkipPresentingFrame();
void ThrottlePresentation();
protected:
// TODO: Merge WindowInfo into this struct...
WindowInfo m_window_info;
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
bool m_allow_present_throttle = false;
u64 m_last_frame_displayed_time = 0;
};
class GPUDevice
{
public:
@ -485,6 +517,7 @@ public:
{
OK,
SkipPresent,
ExclusiveFullscreenLost,
DeviceLost,
};
@ -521,10 +554,22 @@ public:
u32 num_uploads;
};
// Parameters for exclusive fullscreen.
struct ExclusiveFullscreenMode
{
u32 width;
u32 height;
float refresh_rate;
TinyString ToString() const;
static std::optional<ExclusiveFullscreenMode> Parse(std::string_view str);
};
struct AdapterInfo
{
std::string name;
std::vector<std::string> fullscreen_modes;
std::vector<ExclusiveFullscreenMode> fullscreen_modes;
u32 max_texture_size;
u32 max_multisamples;
bool supports_sample_shading;
@ -565,15 +610,6 @@ public:
/// Returns a list of adapters for the given API.
static AdapterInfoList GetAdapterListForAPI(RenderAPI api);
/// Parses a fullscreen mode into its components (width * height @ refresh hz)
static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate);
/// Converts a fullscreen mode to a string.
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
/// Returns the directory bad shaders are saved to.
static std::string GetShaderDumpPath(std::string_view name);
/// Dumps out a shader that failed compilation.
static void DumpBadShader(std::string_view code, std::string_view errors);
@ -600,35 +636,44 @@ public:
ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; }
ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; }
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; }
ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); }
ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); }
// ALWAYS_INLINE u32 GetMainSwapChainWidth() const { return m_main_swap_chain->GetWidth(); }
// ALWAYS_INLINE u32 GetMainSwapChainHeight() const { return m_main_swap_chain->GetHeight(); }
// ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
// ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; }
ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler.get(); }
ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); }
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
GPUVSyncMode vsync, bool allow_present_throttle, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error);
bool Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, const WindowInfo& wi,
GPUVSyncMode vsync, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error);
void Destroy();
virtual bool HasSurface() const = 0;
virtual void DestroySurface() = 0;
virtual bool UpdateWindow() = 0;
virtual std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) = 0;
bool RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error);
void DestroyMainSwapChain();
virtual bool SupportsExclusiveFullscreen() const;
/// Call when the window size changes externally to recreate any resources.
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0;
virtual std::string GetDriverInfo() const = 0;
// Flushes current command buffer, but does not wait for completion.
virtual void FlushCommands() = 0;
// Executes current command buffer, waits for its completion, and destroys all pending resources.
virtual void ExecuteAndWaitForGPUIdle() = 0;
virtual void WaitForGPUIdle() = 0;
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
@ -710,17 +755,12 @@ public:
virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0;
/// Returns false if the window was completely occluded.
virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
virtual void EndPresent(bool explicit_submit, u64 submit_time = 0) = 0;
virtual void SubmitPresent() = 0;
virtual PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
virtual void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 submit_time = 0) = 0;
virtual void SubmitPresent(GPUSwapChain* swap_chain) = 0;
/// Renders ImGui screen elements. Call before EndPresent().
void RenderImGui();
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); }
ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; }
virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0;
void RenderImGui(GPUSwapChain* swap_chain);
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
@ -730,8 +770,6 @@ public:
static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height);
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve = true);
bool ShouldSkipPresentingFrame();
void ThrottlePresentation();
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
@ -745,8 +783,10 @@ public:
static void ResetStatistics();
protected:
virtual bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) = 0;
virtual bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) = 0;
virtual void DestroyDevice() = 0;
std::string GetShaderCacheBaseName(std::string_view type) const;
@ -762,8 +802,6 @@ protected:
std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) = 0;
bool AcquireWindow(bool recreate_window);
void TrimTexturePool();
bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source,
@ -784,8 +822,7 @@ protected:
u32 m_max_texture_size = 0;
u32 m_max_multisamples = 0;
WindowInfo m_window_info;
u64 m_last_frame_displayed_time = 0;
std::unique_ptr<GPUSwapChain> m_main_swap_chain;
GPUShaderCache m_shader_cache;
@ -852,8 +889,6 @@ private:
protected:
static Statistics s_stats;
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
bool m_allow_present_throttle = false;
bool m_gpu_timing_enabled = false;
bool m_debug_device = false;
};
@ -865,21 +900,6 @@ ALWAYS_INLINE void GPUDevice::PooledTextureDeleter::operator()(GPUTexture* const
g_gpu_device->RecycleTexture(std::unique_ptr<GPUTexture>(tex));
}
namespace Host {
/// Called when the core is creating a render device.
/// This could also be fullscreen transition.
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
/// Called when the core is finished with a render window.
void ReleaseRenderWindow();
/// Returns true if the hosting application is currently fullscreen.
bool IsFullscreen();
/// Alters fullscreen state of hosting application.
void SetFullscreen(bool enabled);
} // namespace Host
// Macros for debug messages.
#ifdef _DEBUG
struct GLAutoPop

View File

@ -231,7 +231,8 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
}
s_global_prescale = global_scale;
s_global_scale = std::max(g_gpu_device->GetWindowScale() * global_scale, 1.0f);
s_global_scale = std::max(
(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f) * global_scale, 1.0f);
s_scale_changed = false;
ImGui::CreateContext();
@ -250,8 +251,10 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
#endif
s_window_width = static_cast<float>(g_gpu_device->GetWindowWidth());
s_window_height = static_cast<float>(g_gpu_device->GetWindowHeight());
s_window_width =
g_gpu_device->HasMainSwapChain() ? static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f;
s_window_height =
g_gpu_device->HasMainSwapChain() ? static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight()) : 0.0f;
io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
io.DisplaySize = ImVec2(s_window_width, s_window_height);
@ -316,7 +319,8 @@ void ImGuiManager::RequestScaleUpdate()
void ImGuiManager::UpdateScale()
{
const float window_scale = g_gpu_device ? g_gpu_device->GetWindowScale() : 1.0f;
const float window_scale =
(g_gpu_device && g_gpu_device->HasMainSwapChain()) ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f;
const float scale = std::max(window_scale * s_global_prescale, 1.0f);
if ((!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()) && scale == s_global_scale)

View File

@ -607,11 +607,12 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
#ifdef _WIN32
"DInput",
"XInput",
#endif
#ifndef __ANDROID__
"SDL",
"RawInput",
#else
#endif
#ifdef ENABLE_SDL
"SDL",
#endif
#ifdef __ANDROID__
"Android",
#endif
}};
@ -640,14 +641,17 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
case InputSourceType::XInput:
return false;
#endif
#ifndef __ANDROID__
case InputSourceType::SDL:
return true;
case InputSourceType::RawInput:
return false;
#else
#endif
#ifdef ENABLE_SDL
case InputSourceType::SDL:
return true;
#endif
#ifdef __ANDROID__
case InputSourceType::Android:
return true;
#endif
@ -1229,7 +1233,7 @@ void InputManager::UpdatePointerCount()
return;
}
#ifndef __ANDROID__
#ifdef _WIN32
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
DebugAssert(ris);
@ -2048,9 +2052,10 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
#endif
#ifndef __ANDROID__
#ifdef ENABLE_SDL
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
#else
#endif
#ifdef __ANDROID__
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
#endif

View File

@ -27,11 +27,12 @@ enum class InputSourceType : u32
#ifdef _WIN32
DInput,
XInput,
#endif
#ifndef __ANDROID__
SDL,
RawInput,
#else
#endif
#ifdef ENABLE_SDL
SDL,
#endif
#ifdef __ANDROID__
Android,
#endif
Count,

View File

@ -184,6 +184,23 @@ private:
MetalStreamBuffer m_buffer;
};
class MetalSwapChain : public GPUSwapChain
{
public:
MetalSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, CAMetalLayer* layer);
~MetalSwapChain() override;
void Destroy(bool wait_for_gpu);
CAMetalLayer* GetLayer() const { return m_layer; }
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
private:
CAMetalLayer* m_layer = nil;
};
class MetalDevice final : public GPUDevice
{
friend MetalTexture;
@ -198,16 +215,16 @@ public:
MetalDevice();
~MetalDevice();
bool HasSurface() const override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;
void ExecuteAndWaitForGPUIdle() override;
void FlushCommands() override;
void WaitForGPUIdle() override;
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
@ -261,11 +278,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) override;
void EndPresent(bool explicit_submit, u64 present_time) override;
void SubmitPresent() override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 present_time) override;
void SubmitPresent(GPUSwapChain* swap_chain) override;
void WaitForFenceCounter(u64 counter);
@ -285,8 +300,10 @@ public:
static void DeferRelease(u64 fence_counter, id obj);
protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) override;
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
bool OpenPipelineCache(const std::string& path, Error* error) override;
bool CreatePipelineCache(const std::string& path, Error* error) override;
@ -315,8 +332,6 @@ private:
};
static_assert(sizeof(ClearPipelineConfig) == 8);
ALWAYS_INLINE NSView* GetWindowView() const { return (__bridge NSView*)m_window_info.window_handle; }
void SetFeatures(FeatureMask disabled_features);
bool LoadShaders();
@ -340,15 +355,12 @@ private:
void EndInlineUploading();
void EndAnyEncoding();
GSVector4i ClampToFramebufferSize(const GSVector4i rc) const;
void PreDrawCheck();
void SetInitialEncoderState();
void SetViewportInRenderEncoder();
void SetScissorInRenderEncoder();
bool CreateLayer();
void DestroyLayer();
void RenderBlankFrame();
void RenderBlankFrame(MetalSwapChain* swap_chain);
bool CreateBuffers();
void DestroyBuffers();
@ -358,10 +370,6 @@ private:
id<MTLDevice> m_device;
id<MTLCommandQueue> m_queue;
CAMetalLayer* m_layer = nil;
id<MTLDrawable> m_layer_drawable = nil;
MTLRenderPassDescriptor* m_layer_pass_desc = nil;
std::mutex m_fence_mutex;
u64 m_current_fence_counter = 0;
std::atomic<u64> m_completed_fence_counter{0};
@ -402,10 +410,11 @@ private:
id<MTLBuffer> m_current_ssbo = nil;
GSVector4i m_current_viewport = {};
GSVector4i m_current_scissor = {};
bool m_vsync_enabled = false;
bool m_pipeline_cache_modified = false;
GSVector4i m_current_framebuffer_size = {};
double m_accumulated_gpu_time = 0;
double m_last_gpu_time_end = 0;
id<MTLDrawable> m_layer_drawable = nil;
bool m_pipeline_cache_modified = false;
};

View File

@ -21,7 +21,7 @@
#include <mach/mach_time.h>
#include <pthread.h>
LOG_CHANNEL(MetalDevice);
LOG_CHANNEL(GPUDevice);
// TODO: Disable hazard tracking and issue barriers explicitly.
@ -128,34 +128,160 @@ static void RunOnMainThread(F&& f)
MetalDevice::MetalDevice() : m_current_viewport(0, 0, 1, 1), m_current_scissor(0, 0, 1, 1)
{
m_render_api = RenderAPI::Metal;
}
MetalDevice::~MetalDevice()
{
Assert(m_pipeline_archive == nil && m_layer == nil && m_device == nil);
Assert(m_pipeline_archive == nil && m_layer_drawable == nil && m_device == nil);
}
bool MetalDevice::HasSurface() const
MetalSwapChain::MetalSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
CAMetalLayer* layer)
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_layer(layer)
{
return (m_layer != nil);
}
void MetalDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
MetalSwapChain::~MetalSwapChain()
{
Destroy(true);
}
void MetalSwapChain::Destroy(bool wait_for_gpu)
{
if (!m_layer)
return;
if (wait_for_gpu)
MetalDevice::GetInstance().WaitForGPUIdle();
RunOnMainThread([this]() {
NSView* view = (__bridge NSView*)m_window_info.window_handle;
[view setLayer:nil];
[view setWantsLayer:FALSE];
[m_layer release];
m_layer = nullptr;
});
}
bool MetalSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
@autoreleasepool
{
m_window_info.surface_scale = new_scale;
if (new_width == m_window_info.surface_width && new_height == m_window_info.surface_height)
{
return true;
}
m_window_info.surface_width = new_width;
m_window_info.surface_height = new_height;
[m_layer setDrawableSize:CGSizeMake(new_width, new_height)];
return true;
}
}
bool MetalSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
{
// Metal does not support mailbox mode.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
return true;
m_vsync_mode = mode;
if (m_layer != nil)
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
return true;
}
bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
std::unique_ptr<GPUSwapChain> MetalDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error)
{
@autoreleasepool
{
CAMetalLayer* layer;
WindowInfo wi_copy(wi);
RunOnMainThread([this, &layer, &wi_copy, error]() {
@autoreleasepool
{
INFO_LOG("Creating a {}x{} Metal layer.", wi_copy.surface_width, wi_copy.surface_height);
layer = [CAMetalLayer layer]; // TODO: Does this need retain??
if (layer == nil)
{
Error::SetStringView(error, "Failed to create metal layer.");
return;
}
[layer setDevice:m_device];
[layer setDrawableSize:CGSizeMake(static_cast<float>(wi_copy.surface_width),
static_cast<float>(wi_copy.surface_height))];
// Default should be BGRA8.
const MTLPixelFormat layer_fmt = [layer pixelFormat];
wi_copy.surface_format = GetTextureFormatForMTLFormat(layer_fmt);
if (wi_copy.surface_format == GPUTexture::Format::Unknown)
{
ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
[layer setPixelFormat:MTLPixelFormatBGRA8Unorm];
wi_copy.surface_format = GPUTexture::Format::BGRA8;
}
VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(wi_copy.surface_format));
NSView* view = (__bridge NSView*)wi_copy.window_handle;
[view setWantsLayer:TRUE];
[view setLayer:layer];
}
});
if (!layer)
return {};
// Metal does not support mailbox mode.
vsync_mode = (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode;
[layer setDisplaySyncEnabled:vsync_mode == GPUVSyncMode::FIFO];
// Clear it out ASAP.
std::unique_ptr<MetalSwapChain> swap_chain =
std::make_unique<MetalSwapChain>(wi_copy, vsync_mode, allow_present_throttle, layer);
RenderBlankFrame(swap_chain.get());
return swap_chain;
}
}
void MetalDevice::RenderBlankFrame(MetalSwapChain* swap_chain)
{
@autoreleasepool
{
// has to be encoding, we don't "begin" a render pass here, so the inline encoder won't get flushed otherwise.
EndAnyEncoding();
id<MTLDrawable> drawable = [[swap_chain->GetLayer() nextDrawable] retain];
MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor];
desc.colorAttachments[0].loadAction = MTLLoadActionClear;
desc.colorAttachments[0].storeAction = MTLStoreActionStore;
desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
desc.colorAttachments[0].texture = [drawable texture];
id<MTLRenderCommandEncoder> encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:desc];
[encoder endEncoding];
[m_render_cmdbuf presentDrawable:drawable];
DeferRelease(drawable);
SubmitCommandBuffer();
}
}
bool MetalDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
@autoreleasepool
{
@ -199,15 +325,20 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]);
SetFeatures(disabled_features);
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
{
Error::SetStringView(error, "Failed to create layer.");
return false;
}
CreateCommandBuffer();
RenderBlankFrame();
if (!wi.IsSurfaceless())
{
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
exclusive_fullscreen_control, error);
if (!m_main_swap_chain)
{
Error::SetStringView(error, "Failed to create layer.");
return false;
}
RenderBlankFrame(static_cast<MetalSwapChain*>(m_main_swap_chain.get()));
}
if (!LoadShaders())
{
@ -228,7 +359,6 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
void MetalDevice::SetFeatures(FeatureMask disabled_features)
{
// Set version to Metal 2.3, that's all we're using. Use SPIRV-Cross version encoding.
m_render_api = RenderAPI::Metal;
m_render_api_version = 20300;
m_max_texture_size = GetMetalMaxTextureSize(m_device);
m_max_multisamples = GetMetalMaxMultisamples(m_device);
@ -416,6 +546,12 @@ void MetalDevice::DestroyDevice()
m_render_cmdbuf = nil;
}
if (m_main_swap_chain)
{
static_cast<MetalSwapChain*>(m_main_swap_chain.get())->Destroy(false);
m_main_swap_chain.reset();
}
DestroyBuffers();
for (auto& it : m_cleanup_objects)
@ -457,135 +593,6 @@ void MetalDevice::DestroyDevice()
}
}
bool MetalDevice::CreateLayer()
{
@autoreleasepool
{
RunOnMainThread([this]() {
@autoreleasepool
{
INFO_LOG("Creating a {}x{} Metal layer.", m_window_info.surface_width, m_window_info.surface_height);
const auto size =
CGSizeMake(static_cast<float>(m_window_info.surface_width), static_cast<float>(m_window_info.surface_height));
m_layer = [CAMetalLayer layer];
[m_layer setDevice:m_device];
[m_layer setDrawableSize:size];
// Default should be BGRA8.
const MTLPixelFormat layer_fmt = [m_layer pixelFormat];
m_window_info.surface_format = GetTextureFormatForMTLFormat(layer_fmt);
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
{
ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
[m_layer setPixelFormat:MTLPixelFormatBGRA8Unorm];
m_window_info.surface_format = GPUTexture::Format::BGRA8;
}
VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(m_window_info.surface_format));
NSView* view = GetWindowView();
[view setWantsLayer:TRUE];
[view setLayer:m_layer];
}
});
// Metal does not support mailbox mode.
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
DebugAssert(m_layer_pass_desc == nil);
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];
m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width;
m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height;
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
m_layer_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore;
m_layer_pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
return true;
}
}
void MetalDevice::DestroyLayer()
{
if (m_layer == nil)
return;
// Should wait for previous command buffers to finish, which might be rendering to drawables.
WaitForPreviousCommandBuffers();
[m_layer_pass_desc release];
m_layer_pass_desc = nil;
m_window_info.surface_format = GPUTexture::Format::Unknown;
RunOnMainThread([this]() {
NSView* view = GetWindowView();
[view setLayer:nil];
[view setWantsLayer:FALSE];
[m_layer release];
m_layer = nullptr;
});
}
void MetalDevice::RenderBlankFrame()
{
DebugAssert(!InRenderPass());
if (m_layer == nil)
return;
@autoreleasepool
{
id<MTLDrawable> drawable = [[m_layer nextDrawable] retain];
m_layer_pass_desc.colorAttachments[0].texture = [drawable texture];
id<MTLRenderCommandEncoder> encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc];
[encoder endEncoding];
[m_render_cmdbuf presentDrawable:drawable];
DeferRelease(drawable);
SubmitCommandBuffer();
}
}
bool MetalDevice::UpdateWindow()
{
if (InRenderPass())
EndRenderPass();
DestroyLayer();
if (!AcquireWindow(false))
return false;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
{
ERROR_LOG("Failed to create layer on updated window");
return false;
}
return true;
}
void MetalDevice::DestroySurface()
{
DestroyLayer();
}
void MetalDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
@autoreleasepool
{
m_window_info.surface_scale = new_window_scale;
if (static_cast<u32>(new_window_width) == m_window_info.surface_width &&
static_cast<u32>(new_window_height) == m_window_info.surface_height)
{
return;
}
m_window_info.surface_width = new_window_width;
m_window_info.surface_height = new_window_height;
[m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)];
m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width;
m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height;
}
}
std::string MetalDevice::GetDriverInfo() const
{
@autoreleasepool
@ -2082,6 +2089,8 @@ void MetalDevice::BeginRenderPass()
// Rendering to view, but we got interrupted...
desc.colorAttachments[0].texture = [m_layer_drawable texture];
desc.colorAttachments[0].loadAction = MTLLoadActionLoad;
desc.renderTargetWidth = m_current_framebuffer_size.width();
desc.renderTargetHeight = m_current_framebuffer_size.height();
}
else
{
@ -2157,6 +2166,10 @@ void MetalDevice::BeginRenderPass()
break;
}
}
MetalTexture* rt_or_ds =
(m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target;
m_current_framebuffer_size = GSVector4i(0, 0, rt_or_ds->GetWidth(), rt_or_ds->GetHeight());
}
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain];
@ -2218,7 +2231,7 @@ void MetalDevice::SetInitialEncoderState()
void MetalDevice::SetViewportInRenderEncoder()
{
const GSVector4i rc = ClampToFramebufferSize(m_current_viewport);
const GSVector4i rc = m_current_viewport.rintersect(m_current_framebuffer_size);
[m_render_encoder
setViewport:(MTLViewport){static_cast<double>(rc.left), static_cast<double>(rc.top),
static_cast<double>(rc.width()), static_cast<double>(rc.height()), 0.0, 1.0}];
@ -2226,21 +2239,12 @@ void MetalDevice::SetViewportInRenderEncoder()
void MetalDevice::SetScissorInRenderEncoder()
{
const GSVector4i rc = ClampToFramebufferSize(m_current_scissor);
const GSVector4i rc = m_current_scissor.rintersect(m_current_framebuffer_size);
[m_render_encoder
setScissorRect:(MTLScissorRect){static_cast<NSUInteger>(rc.left), static_cast<NSUInteger>(rc.top),
static_cast<NSUInteger>(rc.width()), static_cast<NSUInteger>(rc.height())}];
}
GSVector4i MetalDevice::ClampToFramebufferSize(const GSVector4i rc) const
{
const MetalTexture* rt_or_ds =
(m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target;
const s32 clamp_width = rt_or_ds ? rt_or_ds->GetWidth() : m_window_info.surface_width;
const s32 clamp_height = rt_or_ds ? rt_or_ds->GetHeight() : m_window_info.surface_height;
return rc.rintersect(GSVector4i(0, 0, clamp_width, clamp_height));
}
void MetalDevice::PreDrawCheck()
{
if (!InRenderPass())
@ -2413,35 +2417,35 @@ id<MTLBlitCommandEncoder> MetalDevice::GetBlitEncoder(bool is_inline)
}
}
GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color)
GPUDevice::PresentResult MetalDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
@autoreleasepool
{
if (m_layer == nil)
{
TrimTexturePool();
return PresentResult::SkipPresent;
}
EndAnyEncoding();
m_layer_drawable = [[m_layer nextDrawable] retain];
m_layer_drawable = [[static_cast<MetalSwapChain*>(swap_chain)->GetLayer() nextDrawable] retain];
if (m_layer_drawable == nil)
{
WARNING_LOG("Failed to get drawable from layer.");
SubmitCommandBuffer();
TrimTexturePool();
return PresentResult::SkipPresent;
}
SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
m_current_framebuffer_size = GSVector4i(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
SetViewportAndScissor(m_current_framebuffer_size);
// Set up rendering to layer.
const GSVector4 clear_color_v = GSVector4::rgba32(clear_color);
id<MTLTexture> layer_texture = [m_layer_drawable texture];
m_layer_pass_desc.colorAttachments[0].texture = layer_texture;
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
m_layer_pass_desc.colorAttachments[0].clearColor =
MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor];
desc.colorAttachments[0].texture = layer_texture;
desc.colorAttachments[0].loadAction = MTLLoadActionClear;
desc.colorAttachments[0].clearColor =
MTLClearColorMake(clear_color_v.r, clear_color_v.g, clear_color_v.g, clear_color_v.a);
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc] retain];
desc.renderTargetWidth = swap_chain->GetWidth();
desc.renderTargetHeight = swap_chain->GetHeight();
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain];
s_stats.num_render_passes++;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_num_current_render_targets = 0;
@ -2454,7 +2458,7 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color)
}
}
void MetalDevice::EndPresent(bool explicit_present, u64 present_time)
void MetalDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
{
DebugAssert(!explicit_present);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
@ -2480,7 +2484,7 @@ void MetalDevice::EndPresent(bool explicit_present, u64 present_time)
TrimTexturePool();
}
void MetalDevice::SubmitPresent()
void MetalDevice::SubmitPresent(GPUSwapChain* swap_chainwel)
{
Panic("Not supported by this API.");
}
@ -2585,12 +2589,18 @@ void MetalDevice::WaitForPreviousCommandBuffers()
WaitForFenceCounter(m_current_fence_counter - 1);
}
void MetalDevice::ExecuteAndWaitForGPUIdle()
void MetalDevice::WaitForGPUIdle()
{
SubmitCommandBuffer(true);
CleanupObjects();
}
void MetalDevice::FlushCommands()
{
SubmitCommandBuffer();
TrimTexturePool();
}
void MetalDevice::CleanupObjects()
{
const u64 counter = m_completed_fence_counter.load(std::memory_order_acquire);

View File

@ -1,12 +1,13 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
class Error;
struct WindowInfo;
namespace CocoaTools {
/// Creates metal layer on specified window surface.
bool CreateMetalLayer(WindowInfo* wi);
void* CreateMetalLayer(const WindowInfo& wi, Error* error);
/// Destroys metal layer on specified window surface.
void DestroyMetalLayer(WindowInfo* wi);
void DestroyMetalLayer(const WindowInfo& wi, void* layer);
} // namespace CocoaTools

View File

@ -8,7 +8,7 @@
#include "common/assert.h"
#include "common/log.h"
LOG_CHANNEL(MetalDevice);
LOG_CHANNEL(GPUDevice);
MetalStreamBuffer::MetalStreamBuffer() = default;

View File

@ -32,7 +32,7 @@
#endif
#endif
LOG_CHANNEL(OpenGLContext);
LOG_CHANNEL(GPUDevice);
static bool ShouldPreferESContext()
{
@ -105,13 +105,11 @@ static void DisableBrokenExtensions(const char* gl_vendor, const char* gl_render
}
}
OpenGLContext::OpenGLContext(const WindowInfo& wi) : m_wi(wi)
{
}
OpenGLContext::OpenGLContext() = default;
OpenGLContext::~OpenGLContext() = default;
std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error* error)
std::unique_ptr<OpenGLContext> OpenGLContext::Create(WindowInfo& wi, SurfaceHandle* surface, Error* error)
{
static constexpr std::array<Version, 14> vlist = {{{Profile::Core, 4, 6},
{Profile::Core, 4, 5},
@ -149,22 +147,22 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error
std::unique_ptr<OpenGLContext> context;
#if defined(_WIN32) && !defined(_M_ARM64)
context = OpenGLContextWGL::Create(wi, versions_to_try, error);
context = OpenGLContextWGL::Create(wi, surface, versions_to_try, error);
#elif defined(__APPLE__)
context = OpenGLContextAGL::Create(wi, versions_to_try, error);
context = OpenGLContextAGL::Create(wi, surface, versions_to_try, error);
#elif defined(__ANDROID__)
context = OpenGLContextEGLAndroid::Create(wi, versions_to_try, error);
context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error);
#else
#if defined(ENABLE_X11)
if (wi.type == WindowInfo::Type::X11)
context = OpenGLContextEGLX11::Create(wi, versions_to_try, error);
context = OpenGLContextEGLX11::Create(wi, surface, versions_to_try, error);
#endif
#if defined(ENABLE_WAYLAND)
if (wi.type == WindowInfo::Type::Wayland)
context = OpenGLContextEGLWayland::Create(wi, versions_to_try, error);
context = OpenGLContextEGLWayland::Create(wi, surface, versions_to_try, error);
#endif
if (wi.type == WindowInfo::Type::Surfaceless)
context = OpenGLContextEGL::Create(wi, versions_to_try, error);
context = OpenGLContextEGL::Create(wi, surface, versions_to_try, error);
#endif
if (!context)

View File

@ -15,9 +15,12 @@ class Error;
class OpenGLContext
{
public:
OpenGLContext(const WindowInfo& wi);
OpenGLContext();
virtual ~OpenGLContext();
using SurfaceHandle = void*;
static constexpr SurfaceHandle MAIN_SURFACE = nullptr;
enum class Profile
{
NoProfile,
@ -32,26 +35,22 @@ public:
int minor_version;
};
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_wi; }
ALWAYS_INLINE bool IsGLES() const { return (m_version.profile == Profile::ES); }
ALWAYS_INLINE u32 GetSurfaceWidth() const { return m_wi.surface_width; }
ALWAYS_INLINE u32 GetSurfaceHeight() const { return m_wi.surface_height; }
ALWAYS_INLINE GPUTexture::Format GetSurfaceFormat() const { return m_wi.surface_format; }
virtual void* GetProcAddress(const char* name) = 0;
virtual bool ChangeSurface(const WindowInfo& new_wi) = 0;
virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0;
virtual SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) = 0;
virtual void DestroySurface(SurfaceHandle handle) = 0;
virtual void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) = 0;
virtual bool SwapBuffers() = 0;
virtual bool IsCurrent() const = 0;
virtual bool MakeCurrent() = 0;
virtual bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) = 0;
virtual bool DoneCurrent() = 0;
virtual bool SupportsNegativeSwapInterval() const = 0;
virtual bool SetSwapInterval(s32 interval) = 0;
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) = 0;
virtual bool SetSwapInterval(s32 interval, Error* error = nullptr) = 0;
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) = 0;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, Error* error);
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface, Error* error);
protected:
WindowInfo m_wi;
Version m_version = {};
};

View File

@ -9,7 +9,7 @@
#include <dlfcn.h>
LOG_CHANNEL(OpenGLContext);
LOG_CHANNEL(GPUDevice);
OpenGLContextAGL::OpenGLContextAGL(const WindowInfo& wi) : OpenGLContext(wi)
{

View File

@ -13,7 +13,7 @@
#include <optional>
#include <vector>
LOG_CHANNEL(OpenGLContext);
LOG_CHANNEL(GPUDevice);
static DynamicLibrary s_egl_library;
static std::atomic_uint32_t s_egl_refcount = 0;
@ -67,34 +67,42 @@ static bool LoadGLADEGL(EGLDisplay display, Error* error)
return true;
}
OpenGLContextEGL::OpenGLContextEGL(const WindowInfo& wi) : OpenGLContext(wi)
OpenGLContextEGL::OpenGLContextEGL() : OpenGLContext()
{
LoadEGL();
}
OpenGLContextEGL::~OpenGLContextEGL()
{
DestroySurface();
DestroyContext();
if (m_context != EGL_NO_CONTEXT && eglGetCurrentContext() == m_context)
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_pbuffer_surface != EGL_NO_SURFACE)
eglDestroySurface(m_display, m_pbuffer_surface);
if (m_context != EGL_NO_CONTEXT)
eglDestroyContext(m_display, m_context);
UnloadEGL();
}
std::unique_ptr<OpenGLContext> OpenGLContextEGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error)
std::unique_ptr<OpenGLContext> OpenGLContextEGL::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>(wi);
if (!context->Initialize(versions_to_try, error))
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>();
if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr;
return context;
}
bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Error* error)
bool OpenGLContextEGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try,
Error* error)
{
if (!LoadGLADEGL(EGL_NO_DISPLAY, error))
return false;
m_display = GetPlatformDisplay(error);
m_display = GetPlatformDisplay(wi, error);
if (m_display == EGL_NO_DISPLAY)
return false;
@ -117,7 +125,7 @@ bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Erro
for (const Version& cv : versions_to_try)
{
if (CreateContextAndSurface(cv, nullptr, true))
if (CreateContextAndSurface(wi, surface, cv, nullptr, true, error))
return true;
}
@ -125,24 +133,30 @@ bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Erro
return false;
}
EGLDisplay OpenGLContextEGL::GetPlatformDisplay(Error* error)
EGLDisplay OpenGLContextEGL::GetPlatformDisplay(const WindowInfo& wi, Error* error)
{
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless");
EGLDisplay dpy =
TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless");
if (dpy == EGL_NO_DISPLAY)
dpy = GetFallbackDisplay(error);
dpy = GetFallbackDisplay(wi.display_connection, error);
return dpy;
}
EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
{
EGLSurface surface = TryCreatePlatformSurface(config, win, error);
if (!surface)
surface = CreateFallbackSurface(config, win, error);
EGLSurface surface = TryCreatePlatformSurface(config, wi.window_handle, error);
if (surface == EGL_NO_SURFACE)
surface = CreateFallbackSurface(config, wi.window_handle, error);
return surface;
}
EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char* platform_ext)
bool OpenGLContextEGL::SupportsSurfaceless() const
{
return GLAD_EGL_KHR_surfaceless_context;
}
EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext)
{
const char* extensions_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!extensions_str)
@ -160,7 +174,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
(PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
if (get_platform_display_ext)
{
dpy = get_platform_display_ext(platform, m_wi.display_connection, nullptr);
dpy = get_platform_display_ext(platform, display, nullptr);
m_use_ext_platform_base = (dpy != EGL_NO_DISPLAY);
if (!m_use_ext_platform_base)
{
@ -181,7 +195,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
return dpy;
}
EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* win, Error* error)
EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* window, Error* error)
{
EGLSurface surface = EGL_NO_SURFACE;
if (m_use_ext_platform_base)
@ -190,7 +204,7 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
(PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
if (create_platform_window_surface_ext)
{
surface = create_platform_window_surface_ext(m_display, config, win, nullptr);
surface = create_platform_window_surface_ext(m_display, config, window, nullptr);
if (surface == EGL_NO_SURFACE)
{
const EGLint err = eglGetError();
@ -206,11 +220,11 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
return surface;
}
EGLDisplay OpenGLContextEGL::GetFallbackDisplay(Error* error)
EGLDisplay OpenGLContextEGL::GetFallbackDisplay(void* display, Error* error)
{
WARNING_LOG("Using fallback eglGetDisplay() path.");
EGLDisplay dpy = eglGetDisplay(m_wi.display_connection);
EGLDisplay dpy = eglGetDisplay((EGLNativeDisplayType)display);
if (dpy == EGL_NO_DISPLAY)
{
const EGLint err = eglGetError();
@ -234,61 +248,56 @@ EGLSurface OpenGLContextEGL::CreateFallbackSurface(EGLConfig config, void* win,
return surface;
}
void OpenGLContextEGL::DestroyPlatformSurface(EGLSurface surface)
{
eglDestroySurface(m_display, surface);
}
void* OpenGLContextEGL::GetProcAddress(const char* name)
{
return reinterpret_cast<void*>(eglGetProcAddress(name));
}
bool OpenGLContextEGL::ChangeSurface(const WindowInfo& new_wi)
OpenGLContext::SurfaceHandle OpenGLContextEGL::CreateSurface(WindowInfo& wi, Error* error /* = nullptr */)
{
const bool was_current = (eglGetCurrentContext() == m_context);
if (was_current)
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_surface != EGL_NO_SURFACE)
if (wi.IsSurfaceless()) [[unlikely]]
{
eglDestroySurface(m_display, m_surface);
m_surface = EGL_NO_SURFACE;
Error::SetStringView(error, "Trying to create a surfaceless surface.");
return nullptr;
}
m_wi = new_wi;
if (!CreateSurface())
return false;
EGLSurface surface = CreatePlatformSurface(m_config, wi, error);
if (surface == EGL_NO_SURFACE)
return nullptr;
if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
{
ERROR_LOG("Failed to make context current again after surface change");
return false;
}
return true;
UpdateWindowInfoSize(wi, surface);
return (SurfaceHandle)surface;
}
void OpenGLContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
void OpenGLContextEGL::DestroySurface(SurfaceHandle handle)
{
if (new_surface_width == 0 && new_surface_height == 0)
{
EGLint surface_width, surface_height;
if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) [[likely]]
{
m_wi.surface_width = static_cast<u32>(surface_width);
m_wi.surface_height = static_cast<u32>(surface_height);
return;
}
else
{
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
}
}
// pbuffer surface?
if (!handle)
return;
m_wi.surface_width = new_surface_width;
m_wi.surface_height = new_surface_height;
EGLSurface surface = (EGLSurface)handle;
if (eglGetCurrentSurface(EGL_DRAW) == surface)
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
DestroyPlatformSurface(surface);
}
void OpenGLContextEGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
{
if (!handle)
return;
UpdateWindowInfoSize(wi, (EGLSurface)handle);
}
bool OpenGLContextEGL::SwapBuffers()
{
return eglSwapBuffers(m_display, m_surface);
return eglSwapBuffers(m_display, m_current_surface);
}
bool OpenGLContextEGL::IsCurrent() const
@ -296,20 +305,29 @@ bool OpenGLContextEGL::IsCurrent() const
return m_context && eglGetCurrentContext() == m_context;
}
bool OpenGLContextEGL::MakeCurrent()
bool OpenGLContextEGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */)
{
if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) [[unlikely]]
EGLSurface esurface = surface ? (EGLSurface)surface : GetSurfacelessSurface();
if (esurface == m_current_surface)
return true;
if (!eglMakeCurrent(m_display, esurface, esurface, m_context)) [[unlikely]]
{
ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
return false;
}
m_current_surface = esurface;
return true;
}
bool OpenGLContextEGL::DoneCurrent()
{
return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (!eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))
return false;
m_current_surface = EGL_NO_SURFACE;
return true;
}
bool OpenGLContextEGL::SupportsNegativeSwapInterval() const
@ -317,17 +335,24 @@ bool OpenGLContextEGL::SupportsNegativeSwapInterval() const
return m_supports_negative_swap_interval;
}
bool OpenGLContextEGL::SetSwapInterval(s32 interval)
bool OpenGLContextEGL::SetSwapInterval(s32 interval, Error* error /* = nullptr */)
{
return eglSwapInterval(m_display, interval);
if (!eglSwapInterval(m_display, interval))
{
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
return false;
}
return true;
}
std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(const WindowInfo& wi, Error* error)
std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error)
{
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>(wi);
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>();
context->m_display = m_display;
if (!context->CreateContextAndSurface(m_version, m_context, false))
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
{
Error::SetStringView(error, "Failed to create context/surface");
return nullptr;
@ -336,63 +361,33 @@ std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(const Windo
return context;
}
bool OpenGLContextEGL::CreateSurface()
EGLSurface OpenGLContextEGL::GetSurfacelessSurface()
{
if (m_wi.type == WindowInfo::Type::Surfaceless)
{
if (GLAD_EGL_KHR_surfaceless_context)
return true;
else
return CreatePBufferSurface();
}
Error error;
m_surface = CreatePlatformSurface(m_config, m_wi.window_handle, &error);
if (m_surface == EGL_NO_SURFACE)
{
ERROR_LOG("Failed to create platform surface: {}", error.GetDescription());
return false;
}
// Some implementations may require the size to be queried at runtime.
EGLint surface_width, surface_height;
if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height))
{
m_wi.surface_width = static_cast<u32>(surface_width);
m_wi.surface_height = static_cast<u32>(surface_height);
}
else
{
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
}
m_wi.surface_format = GetSurfaceTextureFormat();
return true;
return SupportsSurfaceless() ? EGL_NO_SURFACE : GetPBufferSurface(nullptr);
}
bool OpenGLContextEGL::CreatePBufferSurface()
EGLSurface OpenGLContextEGL::GetPBufferSurface(Error* error)
{
const u32 width = std::max<u32>(m_wi.surface_width, 1);
const u32 height = std::max<u32>(m_wi.surface_height, 1);
if (m_pbuffer_surface)
return m_pbuffer_surface;
// TODO: Format
EGLint attrib_list[] = {
EGL_WIDTH, static_cast<EGLint>(width), EGL_HEIGHT, static_cast<EGLint>(height), EGL_NONE,
EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE,
};
m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list);
if (!m_surface) [[unlikely]]
m_pbuffer_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list);
if (!m_pbuffer_surface) [[unlikely]]
{
ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError());
return false;
if (error)
error->SetStringFmt("eglCreatePbufferSurface() failed: {}", eglGetError());
else
ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError());
return nullptr;
}
m_wi.surface_format = GetSurfaceTextureFormat();
DEV_LOG("Created {}x{} pbuffer surface", width, height);
return true;
DEV_LOG("Created pbuffer surface");
return m_pbuffer_surface;
}
bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format)
@ -425,8 +420,21 @@ bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Fo
}
}
GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const
void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const
{
// Some implementations may require the size to be queried at runtime.
EGLint surface_width, surface_height;
if (eglQuerySurface(m_display, surface, EGL_WIDTH, &surface_width) &&
eglQuerySurface(m_display, surface, EGL_HEIGHT, &surface_height))
{
wi.surface_width = static_cast<u16>(surface_width);
wi.surface_height = static_cast<u16>(surface_height);
}
else
{
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
}
int red_size = 0, green_size = 0, blue_size = 0, alpha_size = 0;
eglGetConfigAttrib(m_display, m_config, EGL_RED_SIZE, &red_size);
eglGetConfigAttrib(m_display, m_config, EGL_GREEN_SIZE, &green_size);
@ -435,48 +443,25 @@ GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const
if (red_size == 5 && green_size == 6 && blue_size == 5)
{
return GPUTexture::Format::RGB565;
wi.surface_format = GPUTexture::Format::RGB565;
}
else if (red_size == 5 && green_size == 5 && blue_size == 5 && alpha_size == 1)
{
return GPUTexture::Format::RGBA5551;
wi.surface_format = GPUTexture::Format::RGBA5551;
}
else if (red_size == 8 && green_size == 8 && blue_size == 8 && alpha_size == 8)
{
return GPUTexture::Format::RGBA8;
wi.surface_format = GPUTexture::Format::RGBA8;
}
else
{
ERROR_LOG("Unknown surface format: R={}, G={}, B={}, A={}", red_size, green_size, blue_size, alpha_size);
return GPUTexture::Format::RGBA8;
wi.surface_format = GPUTexture::Format::RGBA8;
}
}
void OpenGLContextEGL::DestroyContext()
{
if (eglGetCurrentContext() == m_context)
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_context != EGL_NO_CONTEXT)
{
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_CONTEXT;
}
}
void OpenGLContextEGL::DestroySurface()
{
if (eglGetCurrentSurface(EGL_DRAW) == m_surface)
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (m_surface != EGL_NO_SURFACE)
{
eglDestroySurface(m_display, m_surface);
m_surface = EGL_NO_SURFACE;
}
}
bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_context)
bool OpenGLContextEGL::CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version,
EGLContext share_context, Error* error)
{
DEV_LOG("Trying version {}.{} ({})", version.major_version, version.minor_version,
version.profile == OpenGLContext::Profile::ES ?
@ -489,18 +474,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) :
EGL_OPENGL_BIT,
EGL_SURFACE_TYPE,
(m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0,
surfaceless ? 0 : EGL_WINDOW_BIT,
};
int nsurface_attribs = 4;
const GPUTexture::Format format = m_wi.surface_format;
if (format == GPUTexture::Format::Unknown)
{
WARNING_LOG("Surface format not specified, assuming RGBA8.");
m_wi.surface_format = GPUTexture::Format::RGBA8;
}
if (surface_format == GPUTexture::Format::Unknown)
surface_format = GPUTexture::Format::RGBA8;
switch (m_wi.surface_format)
switch (surface_format)
{
case GPUTexture::Format::RGBA8:
surface_attribs[nsurface_attribs++] = EGL_RED_SIZE;
@ -522,11 +503,8 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
surface_attribs[nsurface_attribs++] = 5;
break;
case GPUTexture::Format::Unknown:
break;
default:
UnreachableCode();
Error::SetStringFmt(error, "Unsupported texture format {}", GPUTexture::GetFormatName(surface_format));
break;
}
@ -536,14 +514,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
EGLint num_configs;
if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0)
{
ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
return false;
}
std::vector<EGLConfig> configs(static_cast<u32>(num_configs));
if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs))
{
ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
return false;
}
configs.resize(static_cast<u32>(num_configs));
@ -551,7 +529,7 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
std::optional<EGLConfig> config;
for (EGLConfig check_config : configs)
{
if (CheckConfigSurfaceFormat(check_config, m_wi.surface_format))
if (CheckConfigSurfaceFormat(check_config, surface_format))
{
config = check_config;
break;
@ -578,14 +556,15 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API))
{
ERROR_LOG("eglBindAPI({}) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
Error::SetStringFmt(error, "eglBindAPI({}) failed",
(version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
return false;
}
m_context = eglCreateContext(m_display, config.value(), share_context, attribs);
if (!m_context)
{
ERROR_LOG("eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
Error::SetStringFmt(error, "eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
return false;
}
@ -611,30 +590,62 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
return true;
}
bool OpenGLContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current)
bool OpenGLContextEGL::CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version,
EGLContext share_context, bool make_current, Error* error)
{
if (!CreateContext(version, share_context))
if (!CreateContext(wi.IsSurfaceless(), wi.surface_format, version, share_context, error))
return false;
if (!CreateSurface())
// create actual surface, need to handle surfaceless here
EGLSurface esurface;
if (wi.IsSurfaceless())
{
ERROR_LOG("Failed to create surface for context");
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_CONTEXT;
return false;
if (!SupportsSurfaceless())
{
esurface = GetPBufferSurface(error);
if (esurface == EGL_NO_SURFACE)
{
ERROR_LOG("Failed to create pbuffer surface for context");
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_SURFACE;
return false;
}
}
else
{
esurface = EGL_NO_SURFACE;
}
*surface = nullptr;
}
else
{
esurface = CreatePlatformSurface(m_config, wi, error);
if (esurface == EGL_NO_SURFACE)
{
ERROR_LOG("Failed to create surface for context");
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_SURFACE;
return false;
}
UpdateWindowInfoSize(wi, esurface);
*surface = esurface;
}
if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
if (make_current)
{
ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
if (m_surface != EGL_NO_SURFACE)
if (!eglMakeCurrent(m_display, esurface, esurface, m_context))
{
eglDestroySurface(m_display, m_surface);
m_surface = EGL_NO_SURFACE;
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
if (esurface != EGL_NO_SURFACE && esurface != m_pbuffer_surface)
DestroyPlatformSurface(esurface);
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_CONTEXT;
return false;
}
eglDestroyContext(m_display, m_context);
m_context = EGL_NO_CONTEXT;
return false;
m_current_surface = esurface;
}
return true;

View File

@ -10,48 +10,54 @@
class OpenGLContextEGL : public OpenGLContext
{
public:
OpenGLContextEGL(const WindowInfo& wi);
OpenGLContextEGL();
~OpenGLContextEGL() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error);
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error);
void* GetProcAddress(const char* name) override;
virtual bool ChangeSurface(const WindowInfo& new_wi) override;
virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
void DestroySurface(SurfaceHandle handle) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
bool SwapBuffers() override;
bool IsCurrent() const override;
bool MakeCurrent() override;
bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
bool DoneCurrent() override;
bool SupportsNegativeSwapInterval() const override;
bool SetSwapInterval(s32 interval) override;
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
protected:
virtual EGLDisplay GetPlatformDisplay(Error* error);
virtual EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error);
virtual EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error);
virtual EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error);
virtual void DestroyPlatformSurface(EGLSurface surface);
EGLDisplay TryGetPlatformDisplay(EGLenum platform, const char* platform_ext);
bool SupportsSurfaceless() const;
EGLDisplay TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext);
EGLSurface TryCreatePlatformSurface(EGLConfig config, void* window, Error* error);
EGLDisplay GetFallbackDisplay(Error* error);
EGLDisplay GetFallbackDisplay(void* display, Error* error);
EGLSurface CreateFallbackSurface(EGLConfig config, void* window, Error* error);
bool Initialize(std::span<const Version> versions_to_try, Error* error);
bool CreateContext(const Version& version, EGLContext share_context);
bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current);
bool CreateSurface();
bool CreatePBufferSurface();
bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try, Error* error);
bool CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version,
EGLContext share_context, Error* error);
bool CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version, EGLContext share_context,
bool make_current, Error* error);
EGLSurface GetPBufferSurface(Error* error);
EGLSurface GetSurfacelessSurface();
bool CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format);
GPUTexture::Format GetSurfaceTextureFormat() const;
void DestroyContext();
void DestroySurface();
void UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const;
EGLDisplay m_display = EGL_NO_DISPLAY;
EGLSurface m_surface = EGL_NO_SURFACE;
EGLContext m_context = EGL_NO_CONTEXT;
EGLSurface m_current_surface = EGL_NO_SURFACE;
EGLConfig m_config = {};
EGLSurface m_pbuffer_surface = EGL_NO_SURFACE;
bool m_use_ext_platform_base = false;
bool m_supports_negative_swap_interval = false;
};

View File

@ -3,91 +3,22 @@
#include "opengl_context_egl_wayland.h"
#include "common/assert.h"
#include "common/error.h"
#include <dlfcn.h>
static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1";
OpenGLContextEGLWayland::OpenGLContextEGLWayland(const WindowInfo& wi) : OpenGLContextEGL(wi)
{
}
OpenGLContextEGLWayland::OpenGLContextEGLWayland() = default;
OpenGLContextEGLWayland::~OpenGLContextEGLWayland()
{
if (m_wl_window)
m_wl_egl_window_destroy(m_wl_window);
AssertMsg(m_wl_window_map.empty(), "WL window map should be empty on destructor.");
if (m_wl_module)
dlclose(m_wl_module);
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::Create(const WindowInfo& wi,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>(wi);
if (!context->LoadModule(error) || !context->Initialize(versions_to_try, error))
return nullptr;
return context;
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::CreateSharedContext(const WindowInfo& wi, Error* error)
{
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>(wi);
context->m_display = m_display;
if (!context->LoadModule(error) || !context->CreateContextAndSurface(m_version, m_context, false))
return nullptr;
return context;
}
void OpenGLContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height)
{
if (m_wl_window)
m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0);
OpenGLContextEGL::ResizeSurface(new_surface_width, new_surface_height);
}
EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(Error* error)
{
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland");
if (dpy == EGL_NO_DISPLAY)
dpy = GetFallbackDisplay(error);
return dpy;
}
EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
{
if (m_wl_window)
{
m_wl_egl_window_destroy(m_wl_window);
m_wl_window = nullptr;
}
m_wl_window = m_wl_egl_window_create(static_cast<wl_surface*>(win), m_wi.surface_width, m_wi.surface_height);
if (!m_wl_window)
{
Error::SetStringView(error, "wl_egl_window_create() failed");
return EGL_NO_SURFACE;
}
EGLSurface surface = TryCreatePlatformSurface(config, m_wl_window, error);
if (surface == EGL_NO_SURFACE)
{
surface = CreateFallbackSurface(config, m_wl_window, error);
if (surface == EGL_NO_SURFACE)
{
m_wl_egl_window_destroy(m_wl_window);
m_wl_window = nullptr;
}
}
return surface;
}
bool OpenGLContextEGLWayland::LoadModule(Error* error)
{
m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL);
@ -112,3 +43,78 @@ bool OpenGLContextEGLWayland::LoadModule(Error* error)
return true;
}
EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(const WindowInfo& wi, Error* error)
{
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland");
if (dpy == EGL_NO_DISPLAY)
dpy = GetFallbackDisplay(wi.display_connection, error);
return dpy;
}
EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
{
struct wl_egl_window* wl_window =
m_wl_egl_window_create(static_cast<wl_surface*>(wi.window_handle), wi.surface_width, wi.surface_height);
if (!wl_window)
{
Error::SetStringView(error, "wl_egl_window_create() failed");
return EGL_NO_SURFACE;
}
EGLSurface surface = TryCreatePlatformSurface(config, wl_window, error);
if (surface == EGL_NO_SURFACE)
{
surface = CreateFallbackSurface(config, wl_window, error);
if (surface == EGL_NO_SURFACE)
{
m_wl_egl_window_destroy(wl_window);
return nullptr;
}
}
m_wl_window_map.emplace(surface, wl_window);
return surface;
}
void OpenGLContextEGLWayland::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
{
const auto it = m_wl_window_map.find((EGLSurface)handle);
AssertMsg(it != m_wl_window_map.end(), "Missing WL window");
m_wl_egl_window_resize(it->second, wi.surface_width, wi.surface_height, 0, 0);
OpenGLContextEGL::ResizeSurface(wi, handle);
}
void OpenGLContextEGLWayland::DestroyPlatformSurface(EGLSurface surface)
{
const auto it = m_wl_window_map.find((EGLSurface)surface);
AssertMsg(it != m_wl_window_map.end(), "Missing WL window");
m_wl_egl_window_destroy(it->second);
m_wl_window_map.erase(it);
OpenGLContextEGL::DestroyPlatformSurface(surface);
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>();
if (!context->LoadModule(error) || !context->Initialize(wi, surface, versions_to_try, error))
return nullptr;
return context;
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error)
{
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>();
context->m_display = m_display;
if (!context->LoadModule(error) || !context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
return nullptr;
return context;
}

View File

@ -10,26 +10,32 @@
class OpenGLContextEGLWayland final : public OpenGLContextEGL
{
public:
OpenGLContextEGLWayland(const WindowInfo& wi);
OpenGLContextEGLWayland();
~OpenGLContextEGLWayland() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error);
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error);
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
protected:
EGLDisplay GetPlatformDisplay(Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override;
EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
void DestroyPlatformSurface(EGLSurface surface) override;
private:
bool LoadModule(Error* error);
// Truely awful, I hate this so much, and all this work for a bloody windowing system where
// we can't even position windows to begin with, which makes multi-window kinda pointless...
using WLWindowMap = std::unordered_map<EGLSurface, struct wl_egl_window*>;
wl_egl_window* m_wl_window = nullptr;
bool LoadModule(Error* error);
void* m_wl_module = nullptr;
wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height);
void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window);
void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy);
WLWindowMap m_wl_window_map;
};

View File

@ -5,49 +5,49 @@
#include "common/error.h"
OpenGLContextEGLX11::OpenGLContextEGLX11(const WindowInfo& wi) : OpenGLContextEGL(wi)
{
}
OpenGLContextEGLX11::OpenGLContextEGLX11() = default;
OpenGLContextEGLX11::~OpenGLContextEGLX11() = default;
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::Create(const WindowInfo& wi,
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(wi);
if (!context->Initialize(versions_to_try, error))
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>();
if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr;
return context;
}
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(const WindowInfo& wi, Error* error)
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error)
{
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(wi);
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>();
context->m_display = m_display;
if (!context->CreateContextAndSurface(m_version, m_context, false))
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
return nullptr;
return context;
}
EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(Error* error)
EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error* error)
{
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11");
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11");
if (dpy == EGL_NO_DISPLAY)
dpy = GetFallbackDisplay(error);
dpy = GetFallbackDisplay(wi.display_connection, error);
return dpy;
}
EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
{
// This is hideous.. the EXT version requires a pointer to the window, whereas the base
// version requires the window itself, casted to void*...
void* win = wi.window_handle;
EGLSurface surface = TryCreatePlatformSurface(config, &win, error);
if (surface == EGL_NO_SURFACE)
surface = CreateFallbackSurface(config, win, error);
surface = CreateFallbackSurface(config, wi.window_handle, error);
return surface;
}
}

View File

@ -8,15 +8,15 @@
class OpenGLContextEGLX11 final : public OpenGLContextEGL
{
public:
OpenGLContextEGLX11(const WindowInfo& wi);
OpenGLContextEGLX11();
~OpenGLContextEGLX11() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error);
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error);
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
protected:
EGLDisplay GetPlatformDisplay(Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override;
EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
};

View File

@ -5,77 +5,134 @@
#include "opengl_loader.h"
#include "common/assert.h"
#include "common/dynamic_library.h"
#include "common/error.h"
#include "common/log.h"
#include "common/scoped_guard.h"
LOG_CHANNEL(GL::OpenGLContext);
LOG_CHANNEL(GPUDevice);
#ifdef __clang__
#pragma clang diagnostic ignored "-Wmicrosoft-cast"
#endif
static void* GetProcAddressCallback(const char* name)
namespace dyn_libs {
#define OPENGL_FUNCTIONS(X) \
X(wglCreateContext) \
X(wglDeleteContext) \
X(wglGetCurrentContext) \
X(wglGetCurrentDC) \
X(wglGetProcAddress) \
X(wglMakeCurrent) \
X(wglShareLists)
static bool LoadOpenGLLibrary(Error* error);
static void CloseOpenGLLibrary();
static void* GetProcAddressCallback(const char* name);
static DynamicLibrary s_opengl_library;
#define DECLARE_OPENGL_FUNCTION(F) static decltype(&::F) F;
OPENGL_FUNCTIONS(DECLARE_OPENGL_FUNCTION)
#undef DECLARE_OPENGL_FUNCTION
} // namespace dyn_libs
bool dyn_libs::LoadOpenGLLibrary(Error* error)
{
void* addr = wglGetProcAddress(name);
if (s_opengl_library.IsOpen())
return true;
else if (!s_opengl_library.Open("opengl32.dll", error))
return false;
bool result = true;
#define RESOLVE_OPENGL_FUNCTION(F) result = result && s_opengl_library.GetSymbol(#F, &F);
OPENGL_FUNCTIONS(RESOLVE_OPENGL_FUNCTION);
#undef RESOLVE_OPENGL_FUNCTION
if (!result)
{
CloseOpenGLLibrary();
Error::SetStringView(error, "One or more required functions from opengl32.dll is missing.");
return false;
}
std::atexit(&CloseOpenGLLibrary);
return true;
}
void dyn_libs::CloseOpenGLLibrary()
{
#define CLOSE_OPENGL_FUNCTION(F) F = nullptr;
OPENGL_FUNCTIONS(CLOSE_OPENGL_FUNCTION);
#undef CLOSE_OPENGL_FUNCTION
s_opengl_library.Close();
}
#undef OPENGL_FUNCTIONS
void* dyn_libs::GetProcAddressCallback(const char* name)
{
void* addr = dyn_libs::wglGetProcAddress(name);
if (addr)
return addr;
// try opengl32.dll
return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
return s_opengl_library.GetSymbolAddress(name);
}
static bool ReloadWGL(HDC dc)
static bool ReloadWGL(HDC dc, Error* error)
{
if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)dyn_libs::wglGetProcAddress(name); }))
{
ERROR_LOG("Loading GLAD WGL functions failed");
Error::SetStringView(error, "Loading GLAD WGL functions failed");
return false;
}
return true;
}
OpenGLContextWGL::OpenGLContextWGL(const WindowInfo& wi) : OpenGLContext(wi)
{
}
OpenGLContextWGL::OpenGLContextWGL() = default;
OpenGLContextWGL::~OpenGLContextWGL()
{
if (wglGetCurrentContext() == m_rc)
wglMakeCurrent(m_dc, nullptr);
if (m_rc)
wglDeleteContext(m_rc);
{
if (dyn_libs::wglGetCurrentContext() == m_rc)
dyn_libs::wglMakeCurrent(nullptr, nullptr);
ReleaseDC();
dyn_libs::wglDeleteContext(m_rc);
}
if (m_pbuffer)
{
wglReleasePbufferDCARB(m_pbuffer, m_pbuffer_dc);
wglDestroyPbufferARB(m_pbuffer);
DeleteDC(m_dummy_dc);
DestroyWindow(m_dummy_window);
}
}
std::unique_ptr<OpenGLContext> OpenGLContextWGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error)
std::unique_ptr<OpenGLContext> OpenGLContextWGL::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
if (!context->Initialize(versions_to_try, error))
return nullptr;
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>();
if (!dyn_libs::LoadOpenGLLibrary(error) || !context->Initialize(wi, surface, versions_to_try, error))
context.reset();
return context;
}
bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Error* error)
bool OpenGLContextWGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try,
Error* error)
{
if (m_wi.type == WindowInfo::Type::Win32)
{
if (!InitializeDC(error))
return false;
}
else
{
if (!CreatePBuffer(error))
return false;
}
const HDC hdc = wi.IsSurfaceless() ? GetPBufferDC(error) : CreateDCAndSetPixelFormat(wi, error);
if (!hdc)
return false;
// Everything including core/ES requires a dummy profile to load the WGL extensions.
if (!CreateAnyContext(nullptr, true, error))
if (!CreateAnyContext(hdc, nullptr, true, error))
return false;
for (const Version& cv : versions_to_try)
@ -84,11 +141,13 @@ bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Erro
{
// we already have the dummy context, so just use that
m_version = cv;
*surface = hdc;
return true;
}
else if (CreateVersionContext(cv, nullptr, true, error))
else if (CreateVersionContext(cv, hdc, nullptr, true, error))
{
m_version = cv;
*surface = hdc;
return true;
}
}
@ -99,65 +158,76 @@ bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Erro
void* OpenGLContextWGL::GetProcAddress(const char* name)
{
return GetProcAddressCallback(name);
return dyn_libs::GetProcAddressCallback(name);
}
bool OpenGLContextWGL::ChangeSurface(const WindowInfo& new_wi)
OpenGLContext::SurfaceHandle OpenGLContextWGL::CreateSurface(WindowInfo& wi, Error* error /*= nullptr*/)
{
const bool was_current = (wglGetCurrentContext() == m_rc);
Error error;
ReleaseDC();
m_wi = new_wi;
if (!InitializeDC(&error))
if (wi.IsSurfaceless()) [[unlikely]]
{
ERROR_LOG("Failed to change surface: {}", error.GetDescription());
return false;
Error::SetStringView(error, "Trying to create a surfaceless surface.");
return nullptr;
}
if (was_current && !wglMakeCurrent(m_dc, m_rc))
{
error.SetWin32(GetLastError());
ERROR_LOG("Failed to make context current again after surface change: {}", error.GetDescription());
return false;
}
return true;
return CreateDCAndSetPixelFormat(wi, error);
}
void OpenGLContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
void OpenGLContextWGL::DestroySurface(SurfaceHandle handle)
{
// pbuffer/surfaceless?
if (!handle)
return;
// current buffer? switch to pbuffer first
if (dyn_libs::wglGetCurrentDC() == static_cast<HDC>(handle))
MakeCurrent(nullptr);
DeleteDC(static_cast<HDC>(handle));
}
void OpenGLContextWGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
{
RECT client_rc = {};
GetClientRect(GetHWND(), &client_rc);
m_wi.surface_width = static_cast<u32>(client_rc.right - client_rc.left);
m_wi.surface_height = static_cast<u32>(client_rc.bottom - client_rc.top);
GetClientRect(static_cast<HWND>(wi.window_handle), &client_rc);
wi.surface_width = static_cast<u16>(client_rc.right - client_rc.left);
wi.surface_height = static_cast<u16>(client_rc.bottom - client_rc.top);
}
bool OpenGLContextWGL::SwapBuffers()
{
return ::SwapBuffers(m_dc);
return ::SwapBuffers(m_current_dc);
}
bool OpenGLContextWGL::IsCurrent() const
{
return (m_rc && wglGetCurrentContext() == m_rc);
return (m_rc && dyn_libs::wglGetCurrentContext() == m_rc);
}
bool OpenGLContextWGL::MakeCurrent()
bool OpenGLContextWGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */)
{
if (!wglMakeCurrent(m_dc, m_rc))
const HDC new_dc = surface ? static_cast<HDC>(surface) : GetPBufferDC(error);
if (!new_dc)
return false;
else if (m_current_dc == new_dc)
return true;
if (!dyn_libs::wglMakeCurrent(new_dc, m_rc))
{
ERROR_LOG("wglMakeCurrent() failed: {}", GetLastError());
return false;
}
m_current_dc = new_dc;
return true;
}
bool OpenGLContextWGL::DoneCurrent()
{
return wglMakeCurrent(m_dc, nullptr);
if (!dyn_libs::wglMakeCurrent(m_current_dc, nullptr))
return false;
m_current_dc = nullptr;
return true;
}
bool OpenGLContextWGL::SupportsNegativeSwapInterval() const
@ -165,45 +235,55 @@ bool OpenGLContextWGL::SupportsNegativeSwapInterval() const
return GLAD_WGL_EXT_swap_control && GLAD_WGL_EXT_swap_control_tear;
}
bool OpenGLContextWGL::SetSwapInterval(s32 interval)
bool OpenGLContextWGL::SetSwapInterval(s32 interval, Error* error)
{
if (!GLAD_WGL_EXT_swap_control)
{
Error::SetStringView(error, "WGL_EXT_swap_control is not supported.");
return false;
}
return wglSwapIntervalEXT(interval);
if (!wglSwapIntervalEXT(interval))
{
Error::SetWin32(error, "wglSwapIntervalEXT() failed: ", GetLastError());
return false;
}
return true;
}
std::unique_ptr<OpenGLContext> OpenGLContextWGL::CreateSharedContext(const WindowInfo& wi, Error* error)
std::unique_ptr<OpenGLContext> OpenGLContextWGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
Error* error)
{
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
if (wi.type == WindowInfo::Type::Win32)
{
if (!context->InitializeDC(error))
return nullptr;
}
else
{
if (!context->CreatePBuffer(error))
return nullptr;
}
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>();
const HDC hdc = wi.IsSurfaceless() ? context->GetPBufferDC(error) : context->CreateDCAndSetPixelFormat(wi, error);
if (!hdc)
return nullptr;
if (m_version.profile == Profile::NoProfile)
{
if (!context->CreateAnyContext(m_rc, false, error))
if (!context->CreateAnyContext(hdc, m_rc, false, error))
return nullptr;
}
else
{
if (!context->CreateVersionContext(m_version, m_rc, false, error))
if (!context->CreateVersionContext(m_version, hdc, m_rc, false, error))
return nullptr;
}
context->m_version = m_version;
*surface = wi.IsSurfaceless() ? hdc : nullptr;
return context;
}
HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
HDC OpenGLContextWGL::CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error)
{
if (wi.type != WindowInfo::Type::Win32)
{
Error::SetStringFmt(error, "Unknown window info type {}", static_cast<unsigned>(wi.type));
return NULL;
}
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
@ -215,6 +295,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
pfd.cBlueBits = 8;
pfd.cColorBits = 24;
const HWND hwnd = static_cast<HWND>(wi.window_handle);
HDC hDC = ::GetDC(hwnd);
if (!hDC)
{
@ -228,7 +309,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
if (pf == 0)
{
Error::SetWin32(error, "ChoosePixelFormat() failed: ", GetLastError());
::ReleaseDC(hwnd, hDC);
DeleteDC(hDC);
return {};
}
@ -238,63 +319,22 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd))
{
Error::SetWin32(error, "SetPixelFormat() failed: ", GetLastError());
::ReleaseDC(hwnd, hDC);
DeleteDC(hDC);
return {};
}
m_wi.surface_format = GPUTexture::Format::RGBA8;
wi.surface_format = GPUTexture::Format::RGBA8;
return hDC;
}
bool OpenGLContextWGL::InitializeDC(Error* error)
{
if (m_wi.type == WindowInfo::Type::Win32)
{
m_dc = GetDCAndSetPixelFormat(GetHWND(), error);
if (!m_dc)
return false;
return true;
}
else if (m_wi.type == WindowInfo::Type::Surfaceless)
{
return CreatePBuffer(error);
}
else
{
Error::SetStringFmt(error, "Unknown window info type {}", static_cast<unsigned>(m_wi.type));
return false;
}
}
void OpenGLContextWGL::ReleaseDC()
{
if (m_pbuffer)
{
wglReleasePbufferDCARB(m_pbuffer, m_dc);
m_dc = {};
wglDestroyPbufferARB(m_pbuffer);
m_pbuffer = {};
::ReleaseDC(m_dummy_window, m_dummy_dc);
m_dummy_dc = {};
DestroyWindow(m_dummy_window);
m_dummy_window = {};
}
else if (m_dc)
{
::ReleaseDC(GetHWND(), m_dc);
m_dc = {};
}
}
bool OpenGLContextWGL::CreatePBuffer(Error* error)
HDC OpenGLContextWGL::GetPBufferDC(Error* error)
{
static bool window_class_registered = false;
static const wchar_t* window_class_name = L"ContextWGLPBuffer";
if (m_pbuffer_dc)
return m_pbuffer_dc;
if (!window_class_registered)
{
WNDCLASSEXW wc = {};
@ -314,24 +354,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
if (!RegisterClassExW(&wc))
{
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) RegisterClassExW() failed");
return false;
return NULL;
}
window_class_registered = true;
}
Assert(m_dummy_window == NULL);
Assert(m_pbuffer == NULL);
HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
if (!hwnd)
{
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) CreateWindowEx() failed");
return false;
return NULL;
}
ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); });
HDC hdc = GetDCAndSetPixelFormat(hwnd, error);
WindowInfo wi = {.type = WindowInfo::Type::Win32, .window_handle = hwnd};
HDC hdc = CreateDCAndSetPixelFormat(wi, error);
if (!hdc)
return false;
return NULL;
ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); });
@ -341,25 +385,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
ScopedGuard temp_rc_guard([&temp_rc, hdc]() {
if (temp_rc)
{
wglMakeCurrent(hdc, nullptr);
wglDeleteContext(temp_rc);
dyn_libs::wglMakeCurrent(hdc, nullptr);
dyn_libs::wglDeleteContext(temp_rc);
}
});
if (!GLAD_WGL_ARB_pbuffer)
{
// we're probably running completely surfaceless... need a temporary context.
temp_rc = wglCreateContext(hdc);
if (!temp_rc || !wglMakeCurrent(hdc, temp_rc))
temp_rc = dyn_libs::wglCreateContext(hdc);
if (!temp_rc || !dyn_libs::wglMakeCurrent(hdc, temp_rc))
{
Error::SetStringView(error, "Failed to create temporary context to load WGL for pbuffer.");
return false;
return NULL;
}
if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer)
if (!ReloadWGL(hdc, error))
return NULL;
if (!GLAD_WGL_ARB_pbuffer)
{
Error::SetStringView(error, "Missing WGL_ARB_pbuffer");
return false;
return NULL;
}
}
@ -368,32 +415,33 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
if (!pbuffer)
{
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed");
return false;
return NULL;
}
ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); });
m_dc = wglGetPbufferDCARB(pbuffer);
if (!m_dc)
HDC dc = wglGetPbufferDCARB(pbuffer);
if (!dc)
{
Error::SetStringView(error, "(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed");
return false;
return NULL;
}
m_dummy_window = hwnd;
m_dummy_dc = hdc;
m_pbuffer = pbuffer;
m_pbuffer_dc = dc;
temp_rc_guard.Run();
pbuffer_guard.Cancel();
hdc_guard.Cancel();
hwnd_guard.Cancel();
return true;
return dc;
}
bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, Error* error)
bool OpenGLContextWGL::CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error)
{
m_rc = wglCreateContext(m_dc);
m_rc = dyn_libs::wglCreateContext(hdc);
if (!m_rc)
{
Error::SetWin32(error, "wglCreateContext() failed: ", GetLastError());
@ -402,21 +450,23 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current,
if (make_current)
{
if (!wglMakeCurrent(m_dc, m_rc))
if (!dyn_libs::wglMakeCurrent(hdc, m_rc))
{
Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
return false;
}
m_current_dc = hdc;
// re-init glad-wgl
if (!gladLoadWGL(m_dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
if (!ReloadWGL(m_current_dc, error))
{
Error::SetStringView(error, "Loading GLAD WGL functions failed");
return false;
}
}
if (share_context && !wglShareLists(share_context, m_rc))
if (share_context && !dyn_libs::wglShareLists(share_context, m_rc))
{
Error::SetWin32(error, "wglShareLists() failed: ", GetLastError());
return false;
@ -425,7 +475,7 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current,
return true;
}
bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current,
bool OpenGLContextWGL::CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current,
Error* error)
{
// we need create context attribs
@ -454,7 +504,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
0,
0};
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs);
}
else if (version.profile == Profile::ES)
{
@ -475,7 +525,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
0,
0};
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs);
}
else
{
@ -489,18 +539,20 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
// destroy and swap contexts
if (m_rc)
{
if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr))
if (!dyn_libs::wglMakeCurrent(hdc, make_current ? new_rc : nullptr))
{
Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
wglDeleteContext(new_rc);
dyn_libs::wglDeleteContext(new_rc);
return false;
}
m_current_dc = hdc;
// re-init glad-wgl
if (make_current && !ReloadWGL(m_dc))
if (make_current && !ReloadWGL(hdc, error))
return false;
wglDeleteContext(m_rc);
dyn_libs::wglDeleteContext(m_rc);
}
m_rc = new_rc;

View File

@ -15,36 +15,34 @@
class OpenGLContextWGL final : public OpenGLContext
{
public:
OpenGLContextWGL(const WindowInfo& wi);
OpenGLContextWGL();
~OpenGLContextWGL() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
Error* error);
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error);
void* GetProcAddress(const char* name) override;
bool ChangeSurface(const WindowInfo& new_wi) override;
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
void DestroySurface(SurfaceHandle handle) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
bool SwapBuffers() override;
bool IsCurrent() const override;
bool MakeCurrent() override;
bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
bool DoneCurrent() override;
bool SupportsNegativeSwapInterval() const override;
bool SetSwapInterval(s32 interval) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
private:
ALWAYS_INLINE HWND GetHWND() const { return static_cast<HWND>(m_wi.window_handle); }
bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try, Error* error);
HDC GetDCAndSetPixelFormat(HWND hwnd, Error* error);
bool CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error);
bool CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current, Error* error);
bool Initialize(std::span<const Version> versions_to_try, Error* error);
bool InitializeDC(Error* error);
void ReleaseDC();
bool CreatePBuffer(Error* error);
bool CreateAnyContext(HGLRC share_context, bool make_current, Error* error);
bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current, Error* error);
HDC CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error);
HDC GetPBufferDC(Error* error);
HDC m_dc = {};
HDC m_current_dc = {};
HGLRC m_rc = {};
// Can't change pixel format once it's set for a RC.
@ -54,4 +52,5 @@ private:
HWND m_dummy_window = {};
HDC m_dummy_dc = {};
HPBUFFERARB m_pbuffer = {};
HDC m_pbuffer_dc = {};
};

View File

@ -19,13 +19,16 @@
#include <array>
#include <tuple>
LOG_CHANNEL(OpenGLDevice);
LOG_CHANNEL(GPUDevice);
static constexpr const std::array<GLenum, GPUDevice::MAX_RENDER_TARGETS> s_draw_buffers = {
{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}};
OpenGLDevice::OpenGLDevice()
{
// Could change to GLES later.
m_render_api = RenderAPI::OpenGL;
// Something which won't be matched..
std::memset(&m_last_rasterization_state, 0xFF, sizeof(m_last_rasterization_state));
std::memset(&m_last_depth_state, 0xFF, sizeof(m_last_depth_state));
@ -238,19 +241,6 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif
}
void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
// OpenGL does not support Mailbox.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode)
return;
m_vsync_mode = mode;
SetSwapInterval();
}
static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* userParam)
{
@ -271,15 +261,15 @@ static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id,
}
}
bool OpenGLDevice::HasSurface() const
bool OpenGLDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
return m_window_info.type != WindowInfo::Type::Surfaceless;
}
bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
{
m_gl_context = OpenGLContext::Create(m_window_info, error);
WindowInfo wi_copy(wi);
OpenGLContext::SurfaceHandle wi_surface;
m_gl_context = OpenGLContext::Create(wi_copy, &wi_surface, error);
if (!m_gl_context)
{
ERROR_LOG("Failed to create any GL context");
@ -287,9 +277,11 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
return false;
}
#if 0
// Is this needed?
m_window_info = m_gl_context->GetWindowInfo();
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
m_vsync_mode = ;
#endif
const bool opengl_is_available =
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
@ -303,10 +295,6 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
return false;
}
SetSwapInterval();
if (HasSurface())
RenderBlankFrame();
if (m_debug_device && GLAD_GL_KHR_debug)
{
if (m_gl_context->IsGLES())
@ -326,6 +314,21 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
glObjectLabel = nullptr;
}
// create main swap chain
if (!wi_copy.IsSurfaceless())
{
// OpenGL does not support mailbox.
m_main_swap_chain = std::make_unique<OpenGLSwapChain>(
wi_copy, (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode, allow_present_throttle,
wi_surface);
Error swap_interval_error;
if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), m_main_swap_chain->GetVSyncMode(), &swap_interval_error))
WARNING_LOG("Failed to set swap interval on main swap chain: {}", swap_interval_error.GetDescription());
RenderBlankFrame();
}
if (!CheckFeatures(disabled_features))
return false;
@ -532,50 +535,101 @@ void OpenGLDevice::DestroyDevice()
DestroyBuffers();
m_gl_context->DoneCurrent();
m_main_swap_chain.reset();
m_gl_context.reset();
}
bool OpenGLDevice::UpdateWindow()
OpenGLSwapChain::OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
OpenGLContext::SurfaceHandle surface_handle)
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_surface_handle(surface_handle)
{
Assert(m_gl_context);
}
DestroySurface();
OpenGLSwapChain::~OpenGLSwapChain()
{
OpenGLDevice::GetContext()->DestroySurface(m_surface_handle);
}
if (!AcquireWindow(false))
return false;
if (!m_gl_context->ChangeSurface(m_window_info))
{
ERROR_LOG("Failed to change surface");
return false;
}
m_window_info = m_gl_context->GetWindowInfo();
if (m_window_info.type != WindowInfo::Type::Surfaceless)
{
// reset vsync rate, since it (usually) gets lost
SetSwapInterval();
RenderBlankFrame();
}
bool OpenGLSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
m_window_info.surface_scale = new_scale;
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
OpenGLDevice::GetContext()->ResizeSurface(m_window_info, m_surface_handle);
return true;
}
void OpenGLDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
bool OpenGLSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
{
if (m_window_info.IsSurfaceless())
return;
// OpenGL does not support Mailbox.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
if (m_vsync_mode == mode)
return true;
const bool is_main_swap_chain = (g_gpu_device->GetMainSwapChain() == this);
OpenGLContext* ctx = OpenGLDevice::GetContext();
if (!is_main_swap_chain && !ctx->MakeCurrent(m_surface_handle))
return false;
const bool result = SetSwapInterval(ctx, mode, error);
if (!is_main_swap_chain)
ctx->MakeCurrent(static_cast<OpenGLSwapChain*>(g_gpu_device->GetMainSwapChain())->m_surface_handle);
if (!result)
return false;
m_vsync_mode = mode;
return true;
}
bool OpenGLSwapChain::SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error)
{
// Window framebuffer has to be bound to call SetSwapInterval.
const s32 interval = static_cast<s32>(mode == GPUVSyncMode::FIFO);
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
const bool result = ctx->SetSwapInterval(interval);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
return result;
}
std::unique_ptr<GPUSwapChain> OpenGLDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error)
{
if (wi.IsSurfaceless())
{
return;
Error::SetStringView(error, "Trying to create a surfaceless swap chain.");
return {};
}
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
m_window_info = m_gl_context->GetWindowInfo();
WindowInfo wi_copy(wi);
const OpenGLContext::SurfaceHandle surface_handle = m_gl_context->CreateSurface(wi_copy, error);
if (!surface_handle || !m_gl_context->MakeCurrent(surface_handle, error))
return {};
Error swap_interval_error;
if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), vsync_mode, &swap_interval_error))
WARNING_LOG("Failed to set swap interval on new swap chain: {}", swap_interval_error.GetDescription());
RenderBlankFrame();
// only bother switching back if we actually have a main swap chain, avoids a couple of
// SetCurrent() calls when we're switching to and from fullscreen.
if (m_main_swap_chain)
m_gl_context->MakeCurrent(static_cast<OpenGLSwapChain*>(m_main_swap_chain.get())->GetSurfaceHandle());
return std::make_unique<OpenGLSwapChain>(wi_copy, vsync_mode, allow_present_throttle, surface_handle);
}
std::string OpenGLDevice::GetDriverInfo() const
@ -588,29 +642,18 @@ std::string OpenGLDevice::GetDriverInfo() const
gl_shading_language_version);
}
void OpenGLDevice::ExecuteAndWaitForGPUIdle()
void OpenGLDevice::FlushCommands()
{
glFlush();
TrimTexturePool();
}
void OpenGLDevice::WaitForGPUIdle()
{
// Could be glFinish(), but I'm afraid for mobile drivers...
glFlush();
}
void OpenGLDevice::SetSwapInterval()
{
if (m_window_info.type == WindowInfo::Type::Surfaceless)
return;
// Window framebuffer has to be bound to call SetSwapInterval.
const s32 interval = static_cast<s32>(m_vsync_mode == GPUVSyncMode::FIFO);
GLint current_fbo = 0;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (!m_gl_context->SetSwapInterval(interval))
WARNING_LOG("Failed to set swap interval to {}", interval);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
}
void OpenGLDevice::RenderBlankFrame()
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
@ -675,16 +718,6 @@ void OpenGLDevice::DestroyFramebuffer(GLuint fbo)
glDeleteFramebuffers(1, &fbo);
}
void OpenGLDevice::DestroySurface()
{
if (!m_gl_context)
return;
m_window_info.SetSurfaceless();
if (!m_gl_context->ChangeSurface(m_window_info))
ERROR_LOG("Failed to switch to surfaceless");
}
bool OpenGLDevice::CreateBuffers()
{
if (!(m_vertex_buffer = OpenGLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE)) ||
@ -742,14 +775,9 @@ void OpenGLDevice::DestroyBuffers()
m_vertex_buffer.reset();
}
GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
GPUDevice::PresentResult OpenGLDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
if (m_window_info.type == WindowInfo::Type::Surfaceless)
{
glFlush();
TrimTexturePool();
return PresentResult::SkipPresent;
}
m_gl_context->MakeCurrent(static_cast<OpenGLSwapChain*>(swap_chain)->GetSurfaceHandle());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_SCISSOR_TEST);
@ -764,7 +792,8 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr;
const GSVector4i window_rc = GSVector4i(0, 0, m_window_info.surface_width, m_window_info.surface_height);
const GSVector4i window_rc =
GSVector4i(0, 0, static_cast<s32>(swap_chain->GetWidth()), static_cast<s32>(swap_chain->GetHeight()));
m_last_viewport = window_rc;
m_last_scissor = window_rc;
UpdateViewport();
@ -772,23 +801,23 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
return PresentResult::OK;
}
void OpenGLDevice::EndPresent(bool explicit_present, u64 present_time)
void OpenGLDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
{
DebugAssert(!explicit_present && present_time == 0);
DebugAssert(m_current_fbo == 0);
if (m_gpu_timing_enabled)
if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
PopTimestampQuery();
m_gl_context->SwapBuffers();
if (m_gpu_timing_enabled)
if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
KickTimestampQuery();
TrimTexturePool();
}
void OpenGLDevice::SubmitPresent()
void OpenGLDevice::SubmitPresent(GPUSwapChain* swap_chain)
{
Panic("Not supported by this API.");
}

View File

@ -36,20 +36,21 @@ public:
return GetInstance().m_texture_stream_buffer.get();
}
ALWAYS_INLINE static bool IsGLES() { return GetInstance().m_gl_context->IsGLES(); }
ALWAYS_INLINE static OpenGLContext* GetContext() { return GetInstance().m_gl_context.get(); }
static void BindUpdateTextureUnit();
static bool ShouldUsePBOsForDownloads();
static void SetErrorObject(Error* errptr, std::string_view prefix, GLenum glerr);
bool HasSurface() const override;
void DestroySurface() override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
std::string GetDriverInfo() const override;
void ExecuteAndWaitForGPUIdle() override;
void FlushCommands() override;
void WaitForGPUIdle() override;
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
@ -100,11 +101,9 @@ public:
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override;
void SubmitPresent() override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent(GPUSwapChain* swap_chain) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
@ -131,9 +130,13 @@ public:
void UnbindSampler(GLuint id);
void UnbindPipeline(const OpenGLPipeline* pl);
void RenderBlankFrame();
protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) override;
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
bool OpenPipelineCache(const std::string& path, Error* error) override;
@ -154,9 +157,6 @@ private:
bool CreateBuffers();
void DestroyBuffers();
void SetSwapInterval();
void RenderBlankFrame();
s32 IsRenderTargetBound(const GPUTexture* tex) const;
static GLuint CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags);
static void DestroyFramebuffer(GLuint fbo);
@ -230,3 +230,21 @@ private:
bool m_disable_pbo = false;
bool m_disable_async_download = false;
};
class OpenGLSwapChain : public GPUSwapChain
{
public:
OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
OpenGLContext::SurfaceHandle surface_handle);
~OpenGLSwapChain() override;
ALWAYS_INLINE OpenGLContext::SurfaceHandle GetSurfaceHandle() const { return m_surface_handle; }
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
static bool SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error);
private:
OpenGLContext::SurfaceHandle m_surface_handle;
};

View File

@ -21,7 +21,7 @@
#include <cerrno>
LOG_CHANNEL(OpenGLDevice);
LOG_CHANNEL(GPUDevice);
struct PipelineDiskCacheFooter
{

View File

@ -15,7 +15,7 @@
#include <limits>
#include <tuple>
LOG_CHANNEL(OpenGLDevice);
LOG_CHANNEL(GPUDevice);
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems
// to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.

View File

@ -13,6 +13,7 @@
#include "platform_misc.h"
#include "window_info.h"
#include "common/error.h"
#include "common/log.h"
#include "common/small_string.h"
@ -87,49 +88,45 @@ bool PlatformMisc::PlaySoundAsync(const char* path)
return result;
}
bool CocoaTools::CreateMetalLayer(WindowInfo* wi)
void* CocoaTools::CreateMetalLayer(const WindowInfo& wi, Error* error)
{
// Punt off to main thread if we're not calling from it already.
if (![NSThread isMainThread])
{
bool ret;
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { ret = CreateMetalLayer(wi); });
void* ret;
dispatch_sync(dispatch_get_main_queue(), [&ret, &wi, error]() { ret = CreateMetalLayer(wi, error); });
return ret;
}
CAMetalLayer* layer = [CAMetalLayer layer];
if (layer == nil)
{
ERROR_LOG("Failed to create CAMetalLayer");
return false;
Error::SetStringView(error, "Failed to create CAMetalLayer");
return nullptr;
}
NSView* view = (__bridge NSView*)wi->window_handle;
NSView* view = (__bridge NSView*)wi.window_handle;
[view setWantsLayer:TRUE];
[view setLayer:layer];
[layer setContentsScale:[[[view window] screen] backingScaleFactor]];
wi->surface_handle = (__bridge void*)layer;
return true;
return (__bridge void*)layer;
}
void CocoaTools::DestroyMetalLayer(WindowInfo* wi)
void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer)
{
if (!wi->surface_handle)
return;
// Punt off to main thread if we're not calling from it already.
if (![NSThread isMainThread])
{
dispatch_sync(dispatch_get_main_queue(), [wi]() { DestroyMetalLayer(wi); });
dispatch_sync(dispatch_get_main_queue(), [&wi, layer]() { DestroyMetalLayer(wi, layer); });
return;
}
NSView* view = (__bridge NSView*)wi->window_handle;
CAMetalLayer* layer = (__bridge CAMetalLayer*)wi->surface_handle;
NSView* view = (__bridge NSView*)wi.window_handle;
CAMetalLayer* clayer = (__bridge CAMetalLayer*)layer;
[view setLayer:nil];
[view setWantsLayer:NO];
[layer release];
[clayer release];
}
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
@ -137,7 +134,7 @@ std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
if (![NSThread isMainThread])
{
std::optional<float> ret;
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = GetViewRefreshRate(wi); });
dispatch_sync(dispatch_get_main_queue(), [&ret, wi] { ret = GetViewRefreshRate(wi); });
return ret;
}

View File

@ -437,10 +437,10 @@ void PostProcessing::Chain::LoadStages()
DEV_LOG("Loaded {} post-processing stages.", stage_count);
// precompile shaders
if (!IsInternalChain() && g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown)
if (!IsInternalChain() && g_gpu_device && g_gpu_device->HasMainSwapChain())
{
CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(),
&progress);
CheckTargets(g_gpu_device->GetMainSwapChain()->GetFormat(), g_gpu_device->GetMainSwapChain()->GetWidth(),
g_gpu_device->GetMainSwapChain()->GetHeight(), &progress);
}
// must be down here, because we need to compile first, triggered by CheckTargets()

View File

@ -333,9 +333,9 @@ bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::stri
// TODO: This could use spv, it's probably fastest.
const auto& [cg, cg_language] = CreateRFXCodegen();
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(),
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), cg.get(), std::move(code),
error))
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetMainSwapChain()->GetWidth(),
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetMainSwapChain()->GetHeight(), cg.get(),
std::move(code), error))
{
return false;
}
@ -1762,7 +1762,8 @@ GPUDevice::PresentResult PostProcessing::ReShadeFXShader::Apply(GPUTexture* inpu
if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target)
{
// Special case: drawing to final buffer.
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
if (pres != GPUDevice::PresentResult::OK)
{
GL_POP();
return pres;

View File

@ -177,7 +177,8 @@ GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_col
// Assumes final stage has been cleared already.
if (!final_target)
{
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
if (pres != GPUDevice::PresentResult::OK)
return pres;
}
else

View File

@ -4,7 +4,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1</PreprocessorDefinitions>
<PreprocessorDefinitions>%(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1;ENABLE_SDL=1</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">%(PreprocessorDefinitions);ENABLE_OPENGL=1</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\ffmpeg\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include</AdditionalIncludeDirectories>
@ -14,7 +14,6 @@
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>

View File

@ -27,7 +27,7 @@
#include <limits>
#include <mutex>
LOG_CHANNEL(VulkanDevice);
LOG_CHANNEL(GPUDevice);
// TODO: VK_KHR_display.
@ -110,6 +110,8 @@ static std::mutex s_instance_mutex;
VulkanDevice::VulkanDevice()
{
m_render_api = RenderAPI::Vulkan;
#ifdef _DEBUG
s_debug_scope_depth = 0;
#endif
@ -637,14 +639,6 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
device_info.pEnabledFeatures = &enabled_features;
// Enable debug layer on debug builds
if (enable_validation_layer)
{
static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"};
device_info.enabledLayerCount = 1;
device_info.ppEnabledLayerNames = layer_names;
}
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr, VK_TRUE, VK_FALSE,
VK_FALSE};
@ -1266,11 +1260,6 @@ void VulkanDevice::WaitForFenceCounter(u64 fence_counter)
WaitForCommandBufferCompletion(index);
}
void VulkanDevice::WaitForGPUIdle()
{
vkDeviceWaitIdle(m_device);
}
float VulkanDevice::GetAndResetAccumulatedGPUTime()
{
const float time = m_accumulated_gpu_time;
@ -1445,9 +1434,15 @@ void VulkanDevice::QueuePresent(VulkanSwapChain* present_swap_chain)
{
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
if (res == VK_ERROR_OUT_OF_DATE_KHR)
ResizeWindow(0, 0, m_window_info.surface_scale);
{
Error error;
if (!present_swap_chain->ResizeBuffers(0, 0, present_swap_chain->GetScale(), &error)) [[unlikely]]
WARNING_LOG("Failed to reszie swap chain: {}", error.GetDescription());
}
else
{
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
}
return;
}
@ -1889,13 +1884,11 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
#endif
}
bool VulkanDevice::HasSurface() const
{
return static_cast<bool>(m_swap_chain);
}
bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
{
std::unique_lock lock(s_instance_mutex);
bool enable_debug_utils = m_debug_device;
@ -1908,7 +1901,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
return false;
}
m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
if (m_instance == VK_NULL_HANDLE)
{
if (enable_debug_utils || enable_validation_layer)
@ -1916,8 +1909,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
// Try again without the validation layer.
enable_debug_utils = false;
enable_validation_layer = false;
m_instance =
CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
if (m_instance == VK_NULL_HANDLE)
{
Error::SetStringView(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?");
@ -1983,21 +1975,21 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
if (enable_debug_utils)
EnableDebugUtils();
VkSurfaceKHR surface = VK_NULL_HANDLE;
ScopedGuard surface_cleanup = [this, &surface]() {
if (surface != VK_NULL_HANDLE)
vkDestroySurfaceKHR(m_instance, surface, nullptr);
};
if (m_window_info.type != WindowInfo::Type::Surfaceless)
std::unique_ptr<VulkanSwapChain> swap_chain;
if (!wi.IsSurfaceless())
{
surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info);
if (surface == VK_NULL_HANDLE)
swap_chain =
std::make_unique<VulkanSwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control);
if (!swap_chain->CreateSurface(m_instance, m_physical_device, error))
return false;
}
// Attempt to create the device.
if (!CreateDevice(surface, enable_validation_layer, disabled_features, error))
if (!CreateDevice(swap_chain ? swap_chain->GetSurface() : VK_NULL_HANDLE, enable_validation_layer, disabled_features,
error))
{
return false;
}
// And critical resources.
if (!CreateAllocator() || !CreatePersistentDescriptorPool() || !CreateCommandBuffers() || !CreatePipelineLayouts())
@ -2005,26 +1997,16 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
m_exclusive_fullscreen_control = exclusive_fullscreen_control;
if (surface != VK_NULL_HANDLE)
if (swap_chain)
{
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
{
Error::SetStringView(error, "Failed to create swap chain");
// Render a frame as soon as possible to clear out whatever was previously being displayed.
if (!swap_chain->CreateSwapChain(*this, error) || !swap_chain->CreateSwapChainImages(*this, error))
return false;
}
// NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal).
m_window_info = m_swap_chain->GetWindowInfo();
RenderBlankFrame(swap_chain.get());
m_main_swap_chain = std::move(swap_chain);
}
surface_cleanup.Cancel();
// Render a frame as soon as possible to clear out whatever was previously being displayed.
if (m_window_info.type != WindowInfo::Type::Surfaceless)
RenderBlankFrame();
if (!CreateNullTexture())
{
Error::SetStringView(error, "Failed to create dummy texture");
@ -2049,9 +2031,14 @@ void VulkanDevice::DestroyDevice()
// Don't both submitting the current command buffer, just toss it.
if (m_device != VK_NULL_HANDLE)
WaitForGPUIdle();
vkDeviceWaitIdle(m_device);
m_swap_chain.reset();
if (m_main_swap_chain)
{
// Explicit swap chain destroy, we don't want to execute the current cmdbuffer.
static_cast<VulkanSwapChain*>(m_main_swap_chain.get())->Destroy(*this, false);
m_main_swap_chain.reset();
}
if (m_null_texture)
{
@ -2208,73 +2195,27 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error
return true;
}
bool VulkanDevice::UpdateWindow()
std::unique_ptr<GPUSwapChain> VulkanDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error)
{
DestroySurface();
if (!AcquireWindow(false))
return false;
if (m_window_info.IsSurfaceless())
return true;
// make sure previous frames are presented
if (InRenderPass())
EndRenderPass();
SubmitCommandBuffer(false);
WaitForGPUIdle();
VkSurfaceKHR surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info);
if (surface == VK_NULL_HANDLE)
std::unique_ptr<VulkanSwapChain> swap_chain =
std::make_unique<VulkanSwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control);
if (swap_chain->CreateSurface(m_instance, m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) &&
swap_chain->CreateSwapChainImages(*this, error))
{
ERROR_LOG("Failed to create new surface for swap chain");
return false;
if (InRenderPass())
EndRenderPass();
RenderBlankFrame(swap_chain.get());
}
else
{
swap_chain.reset();
}
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
{
ERROR_LOG("Failed to create swap chain");
VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface);
return false;
}
m_window_info = m_swap_chain->GetWindowInfo();
RenderBlankFrame();
return true;
}
void VulkanDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
if (!m_swap_chain)
return;
if (m_swap_chain->GetWidth() == static_cast<u32>(new_window_width) &&
m_swap_chain->GetHeight() == static_cast<u32>(new_window_height))
{
// skip unnecessary resizes
m_window_info.surface_scale = new_window_scale;
return;
}
// make sure previous frames are presented
WaitForGPUIdle();
if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale))
{
// AcquireNextImage() will fail, and we'll recreate the surface.
ERROR_LOG("Failed to resize swap chain. Next present will fail.");
return;
}
m_window_info = m_swap_chain->GetWindowInfo();
}
void VulkanDevice::DestroySurface()
{
WaitForGPUIdle();
m_swap_chain.reset();
return swap_chain;
}
bool VulkanDevice::SupportsTextureFormat(GPUTexture::Format format) const
@ -2308,7 +2249,16 @@ std::string VulkanDevice::GetDriverInfo() const
return ret;
}
void VulkanDevice::ExecuteAndWaitForGPUIdle()
void VulkanDevice::FlushCommands()
{
if (InRenderPass())
EndRenderPass();
SubmitCommandBuffer(false);
TrimTexturePool();
}
void VulkanDevice::WaitForGPUIdle()
{
if (InRenderPass())
EndRenderPass();
@ -2316,39 +2266,7 @@ void VulkanDevice::ExecuteAndWaitForGPUIdle()
SubmitCommandBuffer(true);
}
void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
if (!m_swap_chain)
{
// For when it is re-created.
m_vsync_mode = mode;
return;
}
VkPresentModeKHR present_mode;
if (!VulkanSwapChain::SelectPresentMode(m_swap_chain->GetSurface(), &mode, &present_mode))
{
ERROR_LOG("Ignoring vsync mode change.");
return;
}
// Actually changed? If using a fallback, it might not have.
if (m_vsync_mode == mode)
return;
m_vsync_mode = mode;
// This swap chain should not be used by the current buffer, thus safe to destroy.
WaitForGPUIdle();
if (!m_swap_chain->SetPresentMode(present_mode))
{
Panic("Failed to update swap chain present mode.");
m_swap_chain.reset();
}
}
GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
GPUDevice::PresentResult VulkanDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
if (InRenderPass())
EndRenderPass();
@ -2356,37 +2274,30 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
if (!m_swap_chain)
{
SubmitCommandBuffer(false);
TrimTexturePool();
return PresentResult::SkipPresent;
}
VkResult res = m_swap_chain->AcquireNextImage();
VulkanSwapChain* const SC = static_cast<VulkanSwapChain*>(swap_chain);
VkResult res = SC->AcquireNextImage();
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
m_swap_chain->ReleaseCurrentImage();
SC->ReleaseCurrentImage();
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
{
ResizeWindow(0, 0, m_window_info.surface_scale);
res = m_swap_chain->AcquireNextImage();
Error error;
if (!SC->ResizeBuffers(0, 0, SC->GetScale(), &error)) [[unlikely]]
WARNING_LOG("Failed to resize buffers: {}", error.GetDescription());
else
res = SC->AcquireNextImage();
}
else if (res == VK_ERROR_SURFACE_LOST_KHR)
{
WARNING_LOG("Surface lost, attempting to recreate");
if (!m_swap_chain->RecreateSurface(m_window_info))
{
ERROR_LOG("Failed to recreate surface after loss");
SubmitCommandBuffer(false);
TrimTexturePool();
return PresentResult::SkipPresent;
}
res = m_swap_chain->AcquireNextImage();
Error error;
if (!SC->RecreateSurface(&error))
ERROR_LOG("Failed to recreate surface after loss: {}", error.GetDescription());
else
res = SC->AcquireNextImage();
}
// This can happen when multiple resize events happen in quick succession.
@ -2400,33 +2311,38 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
}
}
BeginSwapChainRenderPass(clear_color);
BeginSwapChainRenderPass(SC, clear_color);
return PresentResult::OK;
}
void VulkanDevice::EndPresent(bool explicit_present, u64 present_time)
void VulkanDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
{
VulkanSwapChain* const SC = static_cast<VulkanSwapChain*>(swap_chain);
DebugAssert(present_time == 0);
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass();
DebugAssert(SC == m_current_swap_chain);
m_current_swap_chain = nullptr;
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, m_swap_chain->GetCurrentImage(), GPUTexture::Type::RenderTarget,
0, 1, 0, 1, VulkanTexture::Layout::ColorAttachment,
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, SC->GetCurrentImage(), GPUTexture::Type::RenderTarget, 0, 1, 0,
1, VulkanTexture::Layout::ColorAttachment,
VulkanTexture::Layout::PresentSrc);
EndAndSubmitCommandBuffer(m_swap_chain.get(), explicit_present);
EndAndSubmitCommandBuffer(SC, explicit_present);
MoveToNextCommandBuffer();
InvalidateCachedState();
TrimTexturePool();
}
void VulkanDevice::SubmitPresent()
void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain)
{
DebugAssert(m_swap_chain);
DebugAssert(swap_chain);
if (m_device_was_lost) [[unlikely]]
return;
QueuePresent(m_swap_chain.get());
QueuePresent(static_cast<VulkanSwapChain*>(swap_chain));
}
#ifdef _DEBUG
@ -2515,7 +2431,6 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP
void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features)
{
const u32 store_api_version = std::min(m_device_properties.apiVersion, VK_API_VERSION_1_1);
m_render_api = RenderAPI::Vulkan;
m_render_api_version = (VK_API_VERSION_MAJOR(store_api_version) * 100u) +
(VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version));
m_max_texture_size =
@ -3076,9 +2991,9 @@ void VulkanDevice::DestroyPersistentDescriptorSets()
FreePersistentDescriptorSet(m_ubo_descriptor_set);
}
void VulkanDevice::RenderBlankFrame()
void VulkanDevice::RenderBlankFrame(VulkanSwapChain* swap_chain)
{
VkResult res = m_swap_chain->AcquireNextImage();
VkResult res = swap_chain->AcquireNextImage();
if (res != VK_SUCCESS)
{
ERROR_LOG("Failed to acquire image for blank frame present");
@ -3087,7 +3002,7 @@ void VulkanDevice::RenderBlankFrame()
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
const VkImage image = m_swap_chain->GetCurrentImage();
const VkImage image = swap_chain->GetCurrentImage();
static constexpr VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
static constexpr VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}};
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
@ -3096,7 +3011,7 @@ void VulkanDevice::RenderBlankFrame()
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
VulkanTexture::Layout::TransferDst, VulkanTexture::Layout::PresentSrc);
EndAndSubmitCommandBuffer(m_swap_chain.get(), false);
EndAndSubmitCommandBuffer(swap_chain, false);
MoveToNextCommandBuffer();
InvalidateCachedState();
@ -3363,7 +3278,7 @@ void VulkanDevice::BeginRenderPass()
VkRenderingAttachmentInfo& ai = attachments[0];
ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
ai.pNext = nullptr;
ai.imageView = m_swap_chain->GetCurrentImageView();
ai.imageView = m_current_swap_chain->GetCurrentImageView();
ai.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR;
ai.resolveImageView = VK_NULL_HANDLE;
@ -3373,7 +3288,7 @@ void VulkanDevice::BeginRenderPass()
ri.colorAttachmentCount = 1;
ri.pColorAttachments = attachments.data();
ri.renderArea = {{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}};
ri.renderArea = {{}, {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()}};
}
m_current_render_pass = DYNAMIC_RENDERING_RENDER_PASS;
@ -3434,10 +3349,10 @@ void VulkanDevice::BeginRenderPass()
else
{
// Re-rendering to swap chain.
bi.framebuffer = m_swap_chain->GetCurrentFramebuffer();
bi.framebuffer = m_current_swap_chain->GetCurrentFramebuffer();
bi.renderPass = m_current_render_pass =
GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_LOAD);
bi.renderArea.extent = {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()};
GetSwapChainRenderPass(m_current_swap_chain->GetFormat(), VK_ATTACHMENT_LOAD_OP_LOAD);
bi.renderArea.extent = {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()};
}
DebugAssert(m_current_render_pass);
@ -3451,12 +3366,12 @@ void VulkanDevice::BeginRenderPass()
SetInitialPipelineState();
}
void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color)
{
DebugAssert(!InRenderPass());
const VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
const VkImage swap_chain_image = m_swap_chain->GetCurrentImage();
const VkImage swap_chain_image = swap_chain->GetCurrentImage();
// Swap chain images start in undefined
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, swap_chain_image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
@ -3477,7 +3392,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
{
VkRenderingAttachmentInfo ai = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR,
nullptr,
m_swap_chain->GetCurrentImageView(),
swap_chain->GetCurrentImageView(),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_RESOLVE_MODE_NONE_KHR,
VK_NULL_HANDLE,
@ -3489,7 +3404,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
nullptr,
0u,
{{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
{{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
1u,
0u,
1u,
@ -3503,14 +3418,14 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
else
{
m_current_render_pass =
GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
GetSwapChainRenderPass(swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
DebugAssert(m_current_render_pass);
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_current_render_pass,
m_swap_chain->GetCurrentFramebuffer(),
{{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
swap_chain->GetCurrentFramebuffer(),
{{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
1u,
&clear_value};
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
@ -3526,6 +3441,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr;
m_current_framebuffer = VK_NULL_HANDLE;
m_current_swap_chain = swap_chain;
}
bool VulkanDevice::InRenderPass()

View File

@ -75,16 +75,16 @@ public:
static GPUList EnumerateGPUs();
static AdapterInfoList GetAdapterList();
bool HasSurface() const override;
bool UpdateWindow() override;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;
void ExecuteAndWaitForGPUIdle() override;
void FlushCommands() override;
void WaitForGPUIdle() override;
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
@ -138,11 +138,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
PresentResult BeginPresent(u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override;
void SubmitPresent() override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent(GPUSwapChain* swap_chain) override;
// Global state accessors
ALWAYS_INLINE static VulkanDevice& GetInstance() { return *static_cast<VulkanDevice*>(g_gpu_device.get()); }
@ -167,7 +165,7 @@ public:
return static_cast<u32>(m_device_properties.limits.optimalBufferCopyRowPitchAlignment);
}
void WaitForGPUIdle();
void WaitForAllFences();
// Creates a simple render pass.
VkRenderPass GetRenderPass(const GPUPipeline::GraphicsConfig& config);
@ -219,19 +217,26 @@ public:
// Also invokes callbacks for completion.
void WaitForFenceCounter(u64 fence_counter);
// Ends a render pass if we're currently in one.
// When Bind() is next called, the pass will be restarted.
void BeginRenderPass();
void EndRenderPass();
bool InRenderPass();
/// Ends any render pass, executes the command buffer, and invalidates cached state.
void SubmitCommandBuffer(bool wait_for_completion);
void SubmitCommandBuffer(bool wait_for_completion, const std::string_view reason);
void SubmitCommandBufferAndRestartRenderPass(const std::string_view reason);
void UnbindFramebuffer(VulkanTexture* tex);
void UnbindPipeline(VulkanPipeline* pl);
void UnbindTexture(VulkanTexture* tex);
void UnbindTextureBuffer(VulkanTextureBuffer* buf);
protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error) override;
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override;
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
@ -351,7 +356,7 @@ private:
VkSampler GetSampler(const GPUSampler::Config& config);
void DestroySamplers();
void RenderBlankFrame();
void RenderBlankFrame(VulkanSwapChain* swap_chain);
bool TryImportHostMemory(void* data, size_t data_size, VkBufferUsageFlags buffer_usage, VkDeviceMemory* out_memory,
VkBuffer* out_buffer, VkDeviceSize* out_offset);
@ -371,12 +376,7 @@ private:
bool UpdateDescriptorSetsForLayout(u32 dirty);
bool UpdateDescriptorSets(u32 dirty);
// Ends a render pass if we're currently in one.
// When Bind() is next called, the pass will be restarted.
void BeginRenderPass();
void BeginSwapChainRenderPass(u32 clear_color);
void EndRenderPass();
bool InRenderPass();
void BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color);
VkRenderPass CreateCachedRenderPass(RenderPassCacheKey key);
static VkFramebuffer CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags);
@ -427,7 +427,6 @@ private:
OptionalExtensions m_optional_extensions = {};
std::optional<bool> m_exclusive_fullscreen_control;
std::unique_ptr<VulkanSwapChain> m_swap_chain;
std::unique_ptr<VulkanTexture> m_null_texture;
VkDescriptorSetLayout m_ubo_ds_layout = VK_NULL_HANDLE;
@ -468,4 +467,5 @@ private:
VulkanTextureBuffer* m_current_texture_buffer = nullptr;
GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1);
GSVector4i m_current_scissor = GSVector4i::cxpr(0, 0, 1, 1);
VulkanSwapChain* m_current_swap_chain = nullptr;
};

View File

@ -16,7 +16,7 @@
#include <cstring>
#include <string>
LOG_CHANNEL(VulkanDevice);
LOG_CHANNEL(GPUDevice);
extern "C" {

View File

@ -11,7 +11,7 @@
#include "common/heap_array.h"
#include "common/log.h"
LOG_CHANNEL(VulkanDevice);
LOG_CHANNEL(GPUDevice);
VulkanShader::VulkanShader(GPUShaderStage stage, VkShaderModule mod) : GPUShader(stage), m_module(mod)
{

View File

@ -9,7 +9,8 @@
#include "common/assert.h"
#include "common/bitutils.h"
#include "common/log.h"
LOG_CHANNEL(VulkanDevice);
LOG_CHANNEL(GPUDevice);
VulkanStreamBuffer::VulkanStreamBuffer() = default;

View File

@ -6,6 +6,7 @@
#include "vulkan_device.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
#include <algorithm>
@ -20,9 +21,7 @@
#include "util/metal_layer.h"
#endif
LOG_CHANNEL(VulkanDevice);
static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
LOG_CHANNEL(GPUDevice);
static VkFormat GetLinearFormat(VkFormat format)
{
@ -72,170 +71,142 @@ static const char* PresentModeToString(VkPresentModeKHR mode)
}
}
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_present_mode(present_mode),
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{
static_assert(NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
}
VulkanSwapChain::~VulkanSwapChain()
{
DestroySwapChainImages();
DestroySwapChain();
DestroySurface();
Destroy(VulkanDevice::GetInstance(), true);
}
VkSurfaceKHR VulkanSwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error)
{
#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (wi->type == WindowInfo::Type::Win32)
if (m_window_info.type == WindowInfo::Type::Win32)
{
VkWin32SurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkWin32SurfaceCreateFlagsKHR flags
nullptr, // HINSTANCE hinstance
reinterpret_cast<HWND>(wi->window_handle) // HWND hwnd
};
VkSurfaceKHR surface;
VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface);
const VkWin32SurfaceCreateInfoKHR surface_create_info = {.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
.hwnd = static_cast<HWND>(m_window_info.window_handle)};
const VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: ");
return VK_NULL_HANDLE;
Vulkan::SetErrorObject(error, "vkCreateWin32SurfaceKHR() failed: ", res);
return false;
}
return surface;
return true;
}
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (wi->type == WindowInfo::Type::MacOS)
if (m_window_info.type == WindowInfo::Type::MacOS)
{
// TODO: FIXME
if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi))
return VK_NULL_HANDLE;
m_metal_layer = CocoaTools::CreateMetalLayer(m_window_info, error);
if (!m_metal_layer)
return false;
VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
static_cast<const CAMetalLayer*>(wi->surface_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
const VkMetalSurfaceCreateInfoEXT surface_create_info = {.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT,
.pLayer = static_cast<const CAMetalLayer*>(m_metal_layer)};
const VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: ");
return VK_NULL_HANDLE;
Vulkan::SetErrorObject(error, "vkCreateMetalSurfaceEXT failed: ", res);
return false;
}
return surface;
return true;
}
#endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
if (wi->type == WindowInfo::Type::Android)
if (m_window_info.type == WindowInfo::Type::Android)
{
VkAndroidSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkAndroidSurfaceCreateFlagsKHR flags
reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
};
VkSurfaceKHR surface;
VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
const VkAndroidSurfaceCreateInfoKHR surface_create_info = {
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
.window = static_cast<ANativeWindow*>(m_window_info.window_handle)};
const VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: ");
return VK_NULL_HANDLE;
Vulkan::SetErrorObject(error, "vkCreateAndroidSurfaceKHR failed: ", res);
return false;
}
return surface;
return true;
}
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (wi->type == WindowInfo::Type::X11)
if (m_window_info.type == WindowInfo::Type::X11)
{
VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkXlibSurfaceCreateFlagsKHR flags
static_cast<Display*>(wi->display_connection), // Display* dpy
reinterpret_cast<Window>(wi->window_handle) // Window window
};
VkSurfaceKHR surface;
VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
const VkXlibSurfaceCreateInfoKHR surface_create_info = {
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
.dpy = static_cast<Display*>(m_window_info.display_connection),
.window = reinterpret_cast<Window>(m_window_info.window_handle)};
const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: ");
return VK_NULL_HANDLE;
Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res);
return false;
}
return surface;
return true;
}
#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (wi->type == WindowInfo::Type::Wayland)
if (m_window_info.type == WindowInfo::Type::Wayland)
{
VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<struct wl_display*>(wi->display_connection),
static_cast<struct wl_surface*>(wi->window_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
const VkWaylandSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<struct wl_display*>(m_window_info.display_connection),
static_cast<struct wl_surface*>(m_window_info.window_handle)};
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: ");
return VK_NULL_HANDLE;
Vulkan::SetErrorObject(error, "vkCreateWaylandSurfaceEXT failed: ", res);
return false;
}
return surface;
return true;
}
#endif
return VK_NULL_HANDLE;
Error::SetStringFmt(error, "Unhandled window type: {}", static_cast<unsigned>(m_window_info.type));
return false;
}
void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface)
void VulkanSwapChain::DestroySurface()
{
vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), surface, nullptr);
if (m_surface != VK_NULL_HANDLE)
{
vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), m_surface, nullptr);
m_surface = VK_NULL_HANDLE;
}
#if defined(__APPLE__)
if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle)
CocoaTools::DestroyMetalLayer(wi);
if (m_metal_layer)
{
CocoaTools::DestroyMetalLayer(m_window_info, m_metal_layer);
m_metal_layer = nullptr;
}
#endif
}
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control)
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error)
{
std::unique_ptr<VulkanSwapChain> swap_chain =
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control));
if (!swap_chain->CreateSwapChain())
return nullptr;
return swap_chain;
}
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
u32 format_count;
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, nullptr);
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, nullptr);
if (res != VK_SUCCESS || format_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
return std::nullopt;
}
std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
res =
vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data());
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, surface_formats.data());
Assert(res == VK_SUCCESS);
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA
@ -255,28 +226,28 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:");
SmallString errormsg = "Failed to find a suitable format for swap chain buffers. Available formats were:";
for (const VkSurfaceFormatKHR& sf : surface_formats)
ERROR_LOG(" {}", static_cast<unsigned>(sf.format));
errormsg.append_format(" {}", static_cast<unsigned>(sf.format));
Error::SetStringView(error, errormsg);
return std::nullopt;
}
bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode)
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode,
Error* error)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
VkResult res;
u32 mode_count;
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, nullptr);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, nullptr);
if (res != VK_SUCCESS || mode_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return false;
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
return std::nullopt;
}
std::vector<VkPresentModeKHR> present_modes(mode_count);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count,
present_modes.data());
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, present_modes.data());
Assert(res == VK_SUCCESS);
// Checks if a particular mode is supported, if it is, returns that mode.
@ -286,25 +257,25 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
return it != present_modes.end();
};
switch (*vsync_mode)
switch (vsync_mode)
{
case GPUVSyncMode::Disabled:
{
// Prefer immediate > mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
{
*present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
return VK_PRESENT_MODE_IMMEDIATE_KHR;
}
else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
return VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
vsync_mode = GPUVSyncMode::FIFO;
return VK_PRESENT_MODE_FIFO_KHR;
}
}
break;
@ -312,7 +283,7 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
case GPUVSyncMode::FIFO:
{
// FIFO is always available.
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
return VK_PRESENT_MODE_FIFO_KHR;
}
break;
@ -321,48 +292,50 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
// Mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
return VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
vsync_mode = GPUVSyncMode::FIFO;
return VK_PRESENT_MODE_FIFO_KHR;
}
}
break;
DefaultCaseIsUnreachable()
}
return true;
}
bool VulkanSwapChain::CreateSwapChain()
bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
const VkPhysicalDevice physdev = dev.GetVulkanPhysicalDevice();
// Select swap chain format
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(physdev, error);
if (!surface_format.has_value())
return false;
const std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(physdev, m_vsync_mode, error);
if (!present_mode.has_value())
return false;
// Look up surface properties to determine image count and dimensions
VkSurfaceCapabilitiesKHR surface_capabilities;
VkResult res =
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities);
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physdev, m_surface, &surface_capabilities);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ");
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ", res);
return false;
}
// Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode.
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
u32 image_count = std::clamp<u32>(
(m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
(present_mode.value() == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
(surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode));
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count,
PresentModeToString(present_mode.value()));
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
// determines window size? Android sometimes lags updating currentExtent, so don't use it.
@ -396,7 +369,7 @@ bool VulkanSwapChain::CreateSwapChain()
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
{
ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment");
Error::SetStringView(error, "Swap chain does not support usage as color attachment");
return false;
}
@ -406,25 +379,21 @@ bool VulkanSwapChain::CreateSwapChain()
m_swap_chain = VK_NULL_HANDLE;
// Now we can actually create the swap chain
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr,
0,
m_surface,
image_count,
surface_format->format,
surface_format->colorSpace,
size,
1u,
image_usage,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr,
transform,
alpha,
m_present_mode,
VK_TRUE,
old_swap_chain};
std::array<uint32_t, 2> indices = {{
VkSwapchainCreateInfoKHR swap_chain_info = {.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = m_surface,
.minImageCount = image_count,
.imageFormat = surface_format->format,
.imageColorSpace = surface_format->colorSpace,
.imageExtent = size,
.imageArrayLayers = 1u,
.imageUsage = image_usage,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.preTransform = transform,
.compositeAlpha = alpha,
.presentMode = present_mode.value(),
.clipped = VK_TRUE,
.oldSwapchain = old_swap_chain};
const std::array<u32, 2> queue_indices = {{
dev.GetGraphicsQueueFamilyIndex(),
dev.GetPresentQueueFamilyIndex(),
}};
@ -432,7 +401,7 @@ bool VulkanSwapChain::CreateSwapChain()
{
swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swap_chain_info.queueFamilyIndexCount = 2;
swap_chain_info.pQueueFamilyIndices = indices.data();
swap_chain_info.pQueueFamilyIndices = queue_indices.data();
}
#ifdef _WIN32
@ -466,81 +435,101 @@ bool VulkanSwapChain::CreateSwapChain()
ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
#endif
res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain);
const VkDevice vkdev = dev.GetVulkanDevice();
res = vkCreateSwapchainKHR(vkdev, &swap_chain_info, nullptr, &m_swap_chain);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: ");
Vulkan::SetErrorObject(error, "vkCreateSwapchainKHR failed: ", res);
return false;
}
// Now destroy the old swap chain, since it's been recreated.
// We can do this immediately since all work should have been completed before calling resize.
if (old_swap_chain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(dev.GetVulkanDevice(), old_swap_chain, nullptr);
vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr);
m_format = surface_format->format;
m_window_info.surface_width = std::max(1u, size.width);
m_window_info.surface_height = std::max(1u, size.height);
if (size.width == 0 || size.width > std::numeric_limits<u16>::max() || size.height == 0 ||
size.height > std::numeric_limits<u16>::max())
{
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height);
return false;
}
m_present_mode = present_mode.value();
m_window_info.surface_width = static_cast<u16>(size.width);
m_window_info.surface_height = static_cast<u16>(size.height);
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
{
ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
Error::SetStringFmt(error, "Unknown surface format {}", static_cast<u32>(surface_format->format));
return false;
}
return true;
}
bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
{
const VkDevice vkdev = dev.GetVulkanDevice();
// Get and create images.
Assert(m_images.empty());
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, nullptr);
u32 image_count;
VkResult res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: ");
Vulkan::SetErrorObject(error, "vkGetSwapchainImagesKHR failed: ", res);
return false;
}
std::vector<VkImage> images(image_count);
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, images.data());
res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, images.data());
Assert(res == VK_SUCCESS);
VkRenderPass render_pass = dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
const VkRenderPass render_pass =
dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
if (render_pass == VK_NULL_HANDLE)
{
Error::SetStringFmt(error, "Failed to get render pass for format {}",
GPUTexture::GetFormatName(m_window_info.surface_format));
return false;
}
Vulkan::FramebufferBuilder fbb;
m_images.reserve(image_count);
m_current_image = 0;
for (u32 i = 0; i < image_count; i++)
{
Image image = {};
Image& image = m_images.emplace_back();
image.image = images[i];
const VkImageViewCreateInfo view_info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
nullptr,
0,
images[i],
VK_IMAGE_VIEW_TYPE_2D,
m_format,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY},
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = images[i],
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = VulkanDevice::TEXTURE_FORMAT_MAPPING[static_cast<u8>(m_window_info.surface_format)],
.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY},
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
};
if ((res = vkCreateImageView(dev.GetVulkanDevice(), &view_info, nullptr, &image.view)) != VK_SUCCESS)
if ((res = vkCreateImageView(vkdev, &view_info, nullptr, &image.view)) != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateImageView() failed: ");
Vulkan::SetErrorObject(error, "vkCreateImageView() failed: ", res);
m_images.pop_back();
return false;
}
fbb.AddAttachment(image.view);
fbb.SetRenderPass(render_pass);
fbb.SetSize(size.width, size.height, 1);
if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE)
fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1);
if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE)
{
vkDestroyImageView(dev.GetVulkanDevice(), image.view, nullptr);
Error::SetStringView(error, "Failed to create swap chain image framebuffer.");
vkDestroyImageView(vkdev, image.view, nullptr);
m_images.pop_back();
return false;
}
m_images.push_back(image);
}
m_current_semaphore = 0;
@ -549,18 +538,18 @@ bool VulkanSwapChain::CreateSwapChain()
ImageSemaphores& sema = m_semaphores[i];
const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.available_semaphore);
res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.available_semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
return false;
}
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr);
Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
vkDestroySemaphore(vkdev, sema.available_semaphore, nullptr);
sema.available_semaphore = VK_NULL_HANDLE;
return false;
}
@ -569,22 +558,39 @@ bool VulkanSwapChain::CreateSwapChain()
return true;
}
void VulkanSwapChain::Destroy(VulkanDevice& dev, bool wait_for_idle)
{
if (!m_swap_chain && !m_surface)
return;
if (wait_for_idle)
{
if (dev.InRenderPass())
dev.EndRenderPass();
dev.WaitForGPUIdle();
}
DestroySwapChain();
DestroySurface();
}
void VulkanSwapChain::DestroySwapChainImages()
{
VulkanDevice& dev = VulkanDevice::GetInstance();
const VkDevice vkdev = VulkanDevice::GetInstance().GetVulkanDevice();
for (const auto& it : m_images)
{
// don't defer view destruction, images are no longer valid
vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr);
vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr);
vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr);
vkDestroyImageView(vkdev, it.view, nullptr);
}
m_images.clear();
for (auto& it : m_semaphores)
for (const auto& it : m_semaphores)
{
if (it.rendering_finished_semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr);
vkDestroySemaphore(vkdev, it.rendering_finished_semaphore, nullptr);
if (it.available_semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr);
vkDestroySemaphore(vkdev, it.available_semaphore, nullptr);
}
m_semaphores = {};
@ -595,13 +601,11 @@ void VulkanSwapChain::DestroySwapChain()
{
DestroySwapChainImages();
if (m_swap_chain == VK_NULL_HANDLE)
return;
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
m_swap_chain = VK_NULL_HANDLE;
m_window_info.surface_width = 0;
m_window_info.surface_height = 0;
if (m_swap_chain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
m_swap_chain = VK_NULL_HANDLE;
}
}
VkResult VulkanSwapChain::AcquireNextImage()
@ -649,20 +653,27 @@ void VulkanSwapChain::ResetImageAcquireResult()
m_image_acquire_result.reset();
}
bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale)
bool VulkanSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{
m_window_info.surface_scale = new_scale;
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
VulkanDevice& dev = VulkanDevice::GetInstance();
if (dev.InRenderPass())
dev.EndRenderPass();
dev.SubmitCommandBuffer(true);
ReleaseCurrentImage();
DestroySwapChainImages();
if (new_width != 0 && new_height != 0)
{
m_window_info.surface_width = new_width;
m_window_info.surface_height = new_height;
m_window_info.surface_width = static_cast<u16>(new_width);
m_window_info.surface_height = static_cast<u16>(new_height);
}
m_window_info.surface_scale = new_scale;
if (!CreateSwapChain())
if (!CreateSwapChain(VulkanDevice::GetInstance(), error) || !CreateSwapChainImages(dev, error))
{
DestroySwapChain();
return false;
@ -671,18 +682,31 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
return true;
}
bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
bool VulkanSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
{
if (m_present_mode == present_mode)
m_allow_present_throttle = allow_present_throttle;
VulkanDevice& dev = VulkanDevice::GetInstance();
const std::optional<VkPresentModeKHR> new_present_mode =
SelectPresentMode(dev.GetVulkanPhysicalDevice(), mode, error);
if (!new_present_mode.has_value())
return false;
// High-level mode could change without the actual backend mode changing.
m_vsync_mode = mode;
if (m_present_mode == new_present_mode.value())
return true;
m_present_mode = present_mode;
if (dev.InRenderPass())
dev.EndRenderPass();
dev.SubmitCommandBuffer(true);
// TODO: Use the maintenance extension to change it without recreating...
// Recreate the swap chain with the new present mode.
VERBOSE_LOG("Recreating swap chain to change present mode.");
ReleaseCurrentImage();
DestroySwapChainImages();
if (!CreateSwapChain())
if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
{
DestroySwapChain();
return false;
@ -691,18 +715,19 @@ bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
return true;
}
bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
bool VulkanSwapChain::RecreateSurface(Error* error)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
if (dev.InRenderPass())
dev.EndRenderPass();
dev.SubmitCommandBuffer(true);
// Destroy the old swap chain, images, and surface.
DestroySwapChain();
DestroySurface();
// Re-create the surface with the new native handle
m_window_info = new_wi;
m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info);
if (m_surface == VK_NULL_HANDLE)
if (!CreateSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), error))
return false;
// The validation layers get angry at us if we don't call this before creating the swapchain.
@ -711,17 +736,13 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
m_surface, &present_supported);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
return false;
}
if (!present_supported)
{
Panic("Recreated surface does not support presenting.");
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ", res);
return false;
}
AssertMsg(present_supported, "Recreated surface does not support presenting.");
// Finally re-create the swap chain
if (!CreateSwapChain())
if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
{
DestroySwapChain();
return false;
@ -729,12 +750,3 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
return true;
}
void VulkanSwapChain::DestroySurface()
{
if (m_surface == VK_NULL_HANDLE)
return;
DestroyVulkanSurface(VulkanDevice::GetInstance().GetVulkanInstance(), &m_window_info, m_surface);
m_surface = VK_NULL_HANDLE;
}

View File

@ -3,6 +3,7 @@
#pragma once
#include "gpu_device.h"
#include "vulkan_loader.h"
#include "vulkan_texture.h"
#include "window_info.h"
@ -14,41 +15,19 @@
#include <optional>
#include <vector>
class VulkanSwapChain
class VulkanSwapChain final : public GPUSwapChain
{
public:
// We don't actually need +1 semaphores, or, more than one really.
// But, the validation layer gets cranky if we don't fence wait before the next image acquire.
// So, add an additional semaphore to ensure that we're never acquiring before fence waiting.
static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1
~VulkanSwapChain();
// Creates a vulkan-renderable surface for the specified window handle.
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi);
// Destroys a previously-created surface.
static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
// Create a new swap chain from a pre-existing surface.
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface,
VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control);
// Determines present mode to use.
static bool SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode);
VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
std::optional<bool> exclusive_fullscreen_control);
~VulkanSwapChain() override;
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() const { return &m_swap_chain; }
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
ALWAYS_INLINE u32 GetCurrentImageIndex() const { return m_current_image; }
ALWAYS_INLINE const u32* GetCurrentImageIndexPtr() const { return &m_current_image; }
ALWAYS_INLINE u32 GetImageCount() const { return static_cast<u32>(m_images.size()); }
ALWAYS_INLINE VkFormat GetImageFormat() const { return m_format; }
ALWAYS_INLINE VkImage GetCurrentImage() const { return m_images[m_current_image].image; }
ALWAYS_INLINE VkImageView GetCurrentImageView() const { return m_images[m_current_image].view; }
ALWAYS_INLINE VkFramebuffer GetCurrentFramebuffer() const { return m_images[m_current_image].framebuffer; }
@ -69,27 +48,31 @@ public:
return &m_semaphores[m_current_semaphore].rendering_finished_semaphore;
}
bool CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error);
bool CreateSwapChain(VulkanDevice& dev, Error* error);
bool CreateSwapChainImages(VulkanDevice& dev, Error* error);
void Destroy(VulkanDevice& dev, bool wait_for_idle);
VkResult AcquireNextImage();
void ReleaseCurrentImage();
void ResetImageAcquireResult();
bool RecreateSurface(const WindowInfo& new_wi);
bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f);
bool RecreateSurface(Error* error);
// Change vsync enabled state. This may fail as it causes a swapchain recreation.
bool SetPresentMode(VkPresentModeKHR present_mode);
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
private:
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control);
// We don't actually need +1 semaphores, or, more than one really.
// But, the validation layer gets cranky if we don't fence wait before the next image acquire.
// So, add an additional semaphore to ensure that we're never acquiring before fence waiting.
static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1
static std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkSurfaceKHR surface);
bool CreateSwapChain();
void DestroySwapChain();
std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error);
std::optional<VkPresentModeKHR> SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode, Error* error);
void DestroySwapChainImages();
void DestroySwapChain();
void DestroySurface();
struct Image
@ -105,9 +88,13 @@ private:
VkSemaphore rendering_finished_semaphore;
};
WindowInfo m_window_info;
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
#ifdef __APPLE__
// On MacOS, we need to store a pointer to the metal layer as well.
void* m_metal_layer = nullptr;
#endif
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
std::vector<Image> m_images;
@ -116,7 +103,6 @@ private:
u32 m_current_image = 0;
u32 m_current_semaphore = 0;
VkFormat m_format = VK_FORMAT_UNDEFINED;
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
std::optional<VkResult> m_image_acquire_result;

View File

@ -10,7 +10,7 @@
#include "common/bitutils.h"
#include "common/log.h"
LOG_CHANNEL(VulkanDevice);
LOG_CHANNEL(GPUDevice);
static constexpr const VkComponentMapping s_identity_swizzle{
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,

View File

@ -11,21 +11,6 @@
LOG_CHANNEL(WindowInfo);
void WindowInfo::SetSurfaceless()
{
type = Type::Surfaceless;
window_handle = nullptr;
surface_width = 0;
surface_height = 0;
surface_refresh_rate = 0.0f;
surface_scale = 1.0f;
surface_format = GPUTexture::Format::Unknown;
#ifdef __APPLE__
surface_handle = nullptr;
#endif
}
#if defined(_WIN32)
#include "common/windows_headers.h"
@ -318,4 +303,4 @@ std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
return std::nullopt;
}
#endif
#endif

View File

@ -3,15 +3,15 @@
#pragma once
#include "gpu_texture.h"
#include "common/types.h"
#include "gpu_texture.h"
#include <optional>
// Contains the information required to create a graphics context in a window.
struct WindowInfo
{
enum class Type
enum class Type : u8
{
Surfaceless,
Win32,
@ -19,27 +19,18 @@ struct WindowInfo
Wayland,
MacOS,
Android,
Display,
};
Type type = Type::Surfaceless;
void* display_connection = nullptr;
void* window_handle = nullptr;
u32 surface_width = 0;
u32 surface_height = 0;
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
u16 surface_width = 0;
u16 surface_height = 0;
float surface_refresh_rate = 0.0f;
float surface_scale = 1.0f;
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
// Needed for macOS.
#ifdef __APPLE__
void* surface_handle = nullptr;
#endif
void* display_connection = nullptr;
void* window_handle = nullptr;
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
// Changes the window to be surfaceless (i.e. no handle/size/etc).
void SetSurfaceless();
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi);
};