GPUDevice: Extract swap chain to separate class

This commit is contained in:
Stenzek 2024-10-12 22:18:48 +10:00
parent c6055affbf
commit eb46142ee7
No known key found for this signature in database
71 changed files with 2564 additions and 2314 deletions

View File

@ -4341,10 +4341,11 @@ void FullscreenUI::DrawDisplaySettingsPage()
options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty()); options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty());
if (selected_adapter) 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); const TinyString mode_str = mode.ToString();
options.emplace_back(mode, checked); 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, void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
float* display_y) const float* display_y) const
{ {
if (!g_gpu_device->HasMainSwapChain()) [[unlikely]]
{
*display_x = 0.0f;
*display_y = 0.0f;
return;
}
GSVector4i display_rc, draw_rc; 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 // convert coordinates to active display region, then to full display region
const float scaled_display_x = const float scaled_display_x =
@ -1644,7 +1652,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm
if (display) if (display)
{ {
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants; 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 vs = shadergen.GenerateDisplayVertexShader();
std::string fs; std::string fs;
@ -1839,10 +1848,13 @@ GPUDevice::PresentResult GPU::PresentDisplay()
{ {
FlushRender(); FlushRender();
if (!g_gpu_device->HasMainSwapChain())
return GPUDevice::PresentResult::SkipPresent;
GSVector4i display_rect; GSVector4i display_rect;
GSVector4i draw_rect; GSVector4i draw_rect;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), !g_settings.debugging.show_vram, CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
true, &display_rect, &draw_rect); !g_settings.debugging.show_vram, true, &display_rect, &draw_rect);
return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram); 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 GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetMainSwapChain()->GetFormat();
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth(); const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetMainSwapChain()->GetWidth();
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight(); const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetMainSwapChain()->GetHeight();
const bool really_postfx = const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
(postfx && PostProcessing::DisplayChain.IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() && hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
const GSVector4i real_draw_rect = const GSVector4i real_draw_rect =
g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect; g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect;
if (really_postfx) if (really_postfx)
@ -1904,9 +1915,15 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
else else
{ {
if (target) if (target)
{
g_gpu_device->SetRenderTarget(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) if (display_texture)
@ -2559,7 +2576,7 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
GPUTexture::Format* out_format) GPUTexture::Format* out_format)
{ {
const GPUTexture::Format hdformat = 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 = auto render_texture =
g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat); 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, void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const GSVector4i* draw_rect) const
{ {
*width = g_gpu_device->GetWindowWidth(); *width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1;
*height = g_gpu_device->GetWindowHeight(); *height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1;
CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect); 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); 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. // 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. // 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->PurgeTexturePool();
g_gpu_device->ExecuteAndWaitForGPUIdle(); g_gpu_device->WaitForGPUIdle();
if (!CreateBuffers()) if (!CreateBuffers())
Panic("Failed to recreate buffers."); Panic("Failed to recreate buffers.");
@ -680,7 +680,7 @@ u32 GPU_HW::CalculateResolutionScale() const
{ {
// Auto scaling. // Auto scaling.
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0 || m_crtc_state.display_vram_width == 0 || 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 // 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. // display_height therefore is also zero. Keep the existing resolution until it updates.
@ -689,8 +689,8 @@ u32 GPU_HW::CalculateResolutionScale() const
else else
{ {
GSVector4i display_rect, draw_rect; GSVector4i display_rect, draw_rect;
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rect, CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
&draw_rect); 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 // We use the draw rect to determine scaling. This way we match the resolution as best we can, regardless of the
// anamorphic aspect ratio. // anamorphic aspect ratio.

View File

@ -243,14 +243,14 @@ void GTE::UpdateAspectRatio()
{ {
case DisplayAspectRatio::MatchWindow: case DisplayAspectRatio::MatchWindow:
{ {
if (!g_gpu_device) if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
{ {
s_config.aspect_ratio = DisplayAspectRatio::R4_3; s_config.aspect_ratio = DisplayAspectRatio::R4_3;
return; return;
} }
num = g_gpu_device->GetWindowWidth(); num = g_gpu_device->GetMainSwapChain()->GetWidth();
denom = g_gpu_device->GetWindowHeight(); denom = g_gpu_device->GetMainSwapChain()->GetHeight();
} }
break; 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); 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); DebugAssert(!g_gpu_device);
INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api)); INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
g_gpu_device = GPUDevice::CreateDeviceForAPI(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; std::optional<bool> exclusive_fullscreen_control;
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic) 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) if (g_settings.gpu_disable_raster_order_views)
disabled_features |= GPUDevice::FEATURE_MASK_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; Error create_error;
if (!g_gpu_device || !g_gpu_device->Create( std::optional<WindowInfo> wi;
g_settings.gpu_adapter, if (!g_gpu_device ||
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache), !(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() ||
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveVSyncMode(), !g_gpu_device->Create(
System::ShouldAllowPresentThrottle(), exclusive_fullscreen_control, g_settings.gpu_adapter, static_cast<GPUDevice::FeatureMask>(disabled_features), shader_dump_directory,
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error)) 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()); ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device) if (g_gpu_device)
g_gpu_device->Destroy(); g_gpu_device->Destroy();
g_gpu_device.reset(); g_gpu_device.reset();
if (wi.has_value())
Host::ReleaseRenderWindow();
Error::SetStringFmt( Error::SetStringFmt(
error, error,
@ -327,27 +345,52 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription()); Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
g_gpu_device->Destroy(); g_gpu_device->Destroy();
g_gpu_device.reset(); g_gpu_device.reset();
Host::ReleaseRenderWindow();
return false; return false;
} }
InputManager::SetDisplayWindowSize(static_cast<float>(g_gpu_device->GetWindowWidth()), InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight());
static_cast<float>(g_gpu_device->GetWindowHeight()));
return true; return true;
} }
void Host::UpdateDisplayWindow() void Host::UpdateDisplayWindow(bool fullscreen)
{ {
if (!g_gpu_device) if (!g_gpu_device)
return; 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; return;
} }
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth()); const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight()); const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height); ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height); InputManager::SetDisplayWindowSize(f_width, f_height);
System::HostDisplayResized(); System::HostDisplayResized();
@ -365,15 +408,21 @@ void Host::UpdateDisplayWindow()
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale) void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
{ {
if (!g_gpu_device) if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return; return;
DEV_LOG("Display window resized to {}x{}", width, height); 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_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight()); const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height); ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(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())); INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
g_gpu_device->Destroy(); g_gpu_device->Destroy();
g_gpu_device.reset(); 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. /// Safely executes a function on the VM thread.
void RunOnCPUThread(std::function<void()> function, bool block = false); 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. /// 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. /// Handles fullscreen transitions and such.
void UpdateDisplayWindow(); void UpdateDisplayWindow(bool fullscreen);
/// Called when the window is resized. /// Called when the window is resized.
void ResizeDisplayWindow(s32 width, s32 height, float scale); 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*/, void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/,
int progress_value /*= -1*/) int progress_value /*= -1*/)
{ {
if (!g_gpu_device) if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
{ {
INFO_LOG("{}: {}/{}", message, progress_value, progress_max); INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
return; return;
@ -160,10 +160,11 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
// TODO: Glass effect or something. // 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->RenderImGui(swap_chain);
g_gpu_device->EndPresent(false); g_gpu_device->EndPresent(swap_chain, false);
} }
ImGui::NewFrame(); ImGui::NewFrame();

View File

@ -158,8 +158,6 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f); turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f);
sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false); sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false);
inhibit_screensaver = si.GetBoolValue("Main", "InhibitScreensaver", true); 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_focus_loss = si.GetBoolValue("Main", "PauseOnFocusLoss", false);
pause_on_controller_disconnection = si.GetBoolValue("Main", "PauseOnControllerDisconnection", false); pause_on_controller_disconnection = si.GetBoolValue("Main", "PauseOnControllerDisconnection", false);
save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true); 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", "SyncToHostRefreshRate", sync_to_host_refresh_rate);
si.SetBoolValue("Main", "InhibitScreensaver", inhibit_screensaver); 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", "PauseOnFocusLoss", pause_on_focus_loss);
si.SetBoolValue("Main", "PauseOnControllerDisconnection", pause_on_controller_disconnection); si.SetBoolValue("Main", "PauseOnControllerDisconnection", pause_on_controller_disconnection);
si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit); si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit);
@ -1657,10 +1653,11 @@ float Settings::GetDisplayAspectRatioValue() const
{ {
case DisplayAspectRatio::MatchWindow: 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 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: case DisplayAspectRatio::Custom:
@ -2110,7 +2107,6 @@ std::string EmuFolders::Bios;
std::string EmuFolders::Cache; std::string EmuFolders::Cache;
std::string EmuFolders::Cheats; std::string EmuFolders::Cheats;
std::string EmuFolders::Covers; std::string EmuFolders::Covers;
std::string EmuFolders::Dumps;
std::string EmuFolders::GameIcons; std::string EmuFolders::GameIcons;
std::string EmuFolders::GameSettings; std::string EmuFolders::GameSettings;
std::string EmuFolders::InputProfiles; std::string EmuFolders::InputProfiles;
@ -2130,7 +2126,6 @@ void EmuFolders::SetDefaults()
Cache = Path::Combine(DataRoot, "cache"); Cache = Path::Combine(DataRoot, "cache");
Cheats = Path::Combine(DataRoot, "cheats"); Cheats = Path::Combine(DataRoot, "cheats");
Covers = Path::Combine(DataRoot, "covers"); Covers = Path::Combine(DataRoot, "covers");
Dumps = Path::Combine(DataRoot, "dump");
GameIcons = Path::Combine(DataRoot, "gameicons"); GameIcons = Path::Combine(DataRoot, "gameicons");
GameSettings = Path::Combine(DataRoot, "gamesettings"); GameSettings = Path::Combine(DataRoot, "gamesettings");
InputProfiles = Path::Combine(DataRoot, "inputprofiles"); InputProfiles = Path::Combine(DataRoot, "inputprofiles");
@ -2162,7 +2157,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache"); Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache");
Cheats = LoadPathFromSettings(si, DataRoot, "Folders", "Cheats", "cheats"); Cheats = LoadPathFromSettings(si, DataRoot, "Folders", "Cheats", "cheats");
Covers = LoadPathFromSettings(si, DataRoot, "Folders", "Covers", "covers"); Covers = LoadPathFromSettings(si, DataRoot, "Folders", "Covers", "covers");
Dumps = LoadPathFromSettings(si, DataRoot, "Folders", "Dumps", "dump");
GameIcons = LoadPathFromSettings(si, DataRoot, "Folders", "GameIcons", "gameicons"); GameIcons = LoadPathFromSettings(si, DataRoot, "Folders", "GameIcons", "gameicons");
GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings"); GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings");
InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles"); InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles");
@ -2179,7 +2173,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
DEV_LOG("Cache Directory: {}", Cache); DEV_LOG("Cache Directory: {}", Cache);
DEV_LOG("Cheats Directory: {}", Cheats); DEV_LOG("Cheats Directory: {}", Cheats);
DEV_LOG("Covers Directory: {}", Covers); DEV_LOG("Covers Directory: {}", Covers);
DEV_LOG("Dumps Directory: {}", Dumps);
DEV_LOG("Game Icons Directory: {}", GameIcons); DEV_LOG("Game Icons Directory: {}", GameIcons);
DEV_LOG("Game Settings Directory: {}", GameSettings); DEV_LOG("Game Settings Directory: {}", GameSettings);
DEV_LOG("Input Profile Directory: {}", InputProfiles); 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", "Cache", Path::MakeRelative(Cache, DataRoot).c_str());
si.SetStringValue("Folders", "Cheats", Path::MakeRelative(Cheats, 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", "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", "GameIcons", Path::MakeRelative(GameIcons, DataRoot).c_str());
si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str()); si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str());
si.SetStringValue("Folders", "InputProfiles", Path::MakeRelative(InputProfiles, 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(Path::Combine(Cache, "achievement_images").c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Cheats.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(Cheats.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(Covers.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(GameIcons.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result;
result = FileSystem::EnsureDirectoryExists(InputProfiles.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; float turbo_speed = 0.0f;
bool sync_to_host_refresh_rate : 1 = false; bool sync_to_host_refresh_rate : 1 = false;
bool inhibit_screensaver : 1 = true; 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_focus_loss : 1 = false;
bool pause_on_controller_disconnection : 1 = false; bool pause_on_controller_disconnection : 1 = false;
bool save_state_on_exit : 1 = true; bool save_state_on_exit : 1 = true;
@ -572,7 +570,6 @@ extern std::string Bios;
extern std::string Cache; extern std::string Cache;
extern std::string Cheats; extern std::string Cheats;
extern std::string Covers; extern std::string Covers;
extern std::string Dumps;
extern std::string GameIcons; extern std::string GameIcons;
extern std::string GameSettings; extern std::string GameSettings;
extern std::string InputProfiles; 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, static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length); 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. /// Checks for settings changes, std::move() the old settings away for comparing beforehand.
static void CheckForSettingsChanges(const Settings& old_settings); static void CheckForSettingsChanges(const Settings& old_settings);
static void WarnAboutUnsafeSettings(); static void WarnAboutUnsafeSettings();
static void LogUnsafeSettingsToConsole(const SmallStringBase& messages); 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 LoadBIOS(Error* error);
static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error); static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error);
static void InternalReset(); static void InternalReset();
static void ClearRunningGame(); static void ClearRunningGame();
static void DestroySystem(); 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 bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true);
static void HandleHostGPUDeviceLost(); static void HandleHostGPUDeviceLost();
static void HandleExclusiveFullscreenLost();
/// Returns true if boot is being fast forwarded. /// Returns true if boot is being fast forwarded.
static bool IsFastForwardingBoot(); static bool IsFastForwardingBoot();
@ -1201,10 +1206,11 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
{ {
PostProcessing::Shutdown(); PostProcessing::Shutdown();
Host::ReleaseGPUDevice(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
} }
Error error; Error error;
if (!CreateGPU(renderer, true, &error)) if (!CreateGPU(renderer, true, Host::IsFullscreen(), &error))
{ {
if (!IsStartupCancelled()) if (!IsStartupCancelled())
Host::ReportErrorAsync("Error", error.GetDescription()); Host::ReportErrorAsync("Error", error.GetDescription());
@ -1265,6 +1271,12 @@ void System::HandleHostGPUDeviceLost()
Host::OSD_CRITICAL_ERROR_DURATION); Host::OSD_CRITICAL_ERROR_DURATION);
} }
void System::HandleExclusiveFullscreenLost()
{
WARNING_LOG("Lost exclusive fullscreen.");
Host::SetFullscreen(false);
}
void System::LoadSettings(bool display_osd_messages) void System::LoadSettings(bool display_osd_messages)
{ {
std::unique_lock<std::mutex> lock = Host::GetSettingsLock(); std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
@ -1373,6 +1385,9 @@ void System::SetDefaultSettings(SettingsInterface& si)
temp.Save(si, false); temp.Save(si, false);
si.SetBoolValue("Main", "StartPaused", false);
si.SetBoolValue("Main", "StartFullscreen", false);
#if !defined(_WIN32) && !defined(__ANDROID__) #if !defined(_WIN32) && !defined(__ANDROID__)
// On Linux, default the console to whether standard input is currently available. // On Linux, default the console to whether standard input is currently available.
si.SetBoolValue("Logging", "LogToConsole", Log::IsConsoleOutputCurrentlyAvailable()); si.SetBoolValue("Logging", "LogToConsole", Log::IsConsoleOutputCurrentlyAvailable());
@ -1793,7 +1808,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
} }
// Component setup. // 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_boot_mode = System::BootMode::None;
s_state = State::Shutdown; s_state = State::Shutdown;
@ -1853,7 +1869,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
if (parameters.start_media_capture) if (parameters.start_media_capture)
StartMediaCapture({}); StartMediaCapture({});
if (g_settings.start_paused || parameters.override_start_paused.value_or(false)) if (ShouldStartPaused() || parameters.override_start_paused.value_or(false))
PauseSystem(true); PauseSystem(true);
UpdateSpeedLimiterState(); UpdateSpeedLimiterState();
@ -1861,7 +1877,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
return true; 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); g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10);
@ -1911,7 +1927,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
Bus::Initialize(); Bus::Initialize();
CPU::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(); CPU::Shutdown();
Bus::Shutdown(); Bus::Shutdown();
@ -1928,10 +1944,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
{ {
g_gpu.reset(); g_gpu.reset();
if (!s_keep_gpu_device_on_shutdown) if (!s_keep_gpu_device_on_shutdown)
{
Host::ReleaseGPUDevice(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
}
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Shutdown(); CPU::PGXP::Shutdown();
CPU::Shutdown(); CPU::Shutdown();
@ -2018,7 +2031,6 @@ void System::DestroySystem()
else else
{ {
Host::ReleaseGPUDevice(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
} }
s_bios_hash = {}; 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); 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; s_last_presented_internal_frame_number = s_internal_frame_number;
const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame && const bool skip_this_frame =
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) || (((s_skip_presenting_duplicate_frames && !is_unique_frame &&
(!s_optimal_frame_pacing && current_time > s_next_frame_time && s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) || (!s_optimal_frame_pacing && current_time > s_next_frame_time &&
g_gpu_device->ShouldSkipPresentingFrame()) && s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted()); (g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->ShouldSkipPresentingFrame())) &&
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
if (!skip_this_frame) if (!skip_this_frame)
{ {
s_skipped_frame_count = 0; s_skipped_frame_count = 0;
@ -2211,7 +2224,7 @@ void System::FrameDone()
const bool do_present = PresentDisplay(true, 0); const bool do_present = PresentDisplay(true, 0);
Throttle(current_time); Throttle(current_time);
if (do_present) if (do_present)
g_gpu_device->SubmitPresent(); g_gpu_device->SubmitPresent(g_gpu_device->GetMainSwapChain());
} }
else else
{ {
@ -2373,7 +2386,7 @@ void System::IncrementInternalFrameNumber()
s_internal_frame_number++; 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); const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
@ -2388,7 +2401,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
} }
Host::ReleaseGPUDevice(); Host::ReleaseGPUDevice();
if (!Host::CreateGPUDevice(api, error)) if (!Host::CreateGPUDevice(api, fullscreen, error))
{ {
Host::ReleaseRenderWindow(); Host::ReleaseRenderWindow();
return false; return false;
@ -3436,9 +3449,9 @@ void System::UpdateSpeedLimiterState()
s_syncing_to_host = false; s_syncing_to_host = false;
s_syncing_to_host_with_vsync = 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) if (host_refresh_rate > 0.0f)
{ {
const float ratio = host_refresh_rate / System::GetThrottleFrequency(); 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. // Avoid flipping vsync on and off by manually throttling when vsync is on.
const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode(); const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode();
const bool allow_present_throttle = ShouldAllowPresentThrottle(); 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; return;
}
VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast<size_t>(vsync_mode)], VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast<size_t>(vsync_mode)],
s_syncing_to_host_with_vsync ? " (for throttling)" : "", s_syncing_to_host_with_vsync ? " (for throttling)" : "",
allow_present_throttle ? " (present throttle allowed)" : ""); 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() GPUVSyncMode System::GetEffectiveVSyncMode()
@ -4230,6 +4252,16 @@ bool System::SwitchMediaSubImage(u32 index)
return true; 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) void System::CheckForSettingsChanges(const Settings& old_settings)
{ {
if (IsValid() && if (IsValid() &&
@ -5193,7 +5225,7 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur
u32 capture_height = u32 capture_height =
Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT); Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT);
const GPUTexture::Format capture_format = 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(); const float fps = System::GetThrottleFrequency();
if (capture_video) if (capture_video)
{ {
@ -5640,11 +5672,14 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
ImGuiManager::RenderOverlayWindows(); ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows(); 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) if (pres == GPUDevice::PresentResult::OK)
{ {
g_gpu_device->RenderImGui(); g_gpu_device->RenderImGui(g_gpu_device->GetMainSwapChain());
g_gpu_device->EndPresent(explicit_present, present_time); g_gpu_device->EndPresent(g_gpu_device->GetMainSwapChain(), explicit_present, present_time);
if (g_gpu_device->IsGPUTimingEnabled()) if (g_gpu_device->IsGPUTimingEnabled())
{ {
@ -5656,9 +5691,13 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
{ {
if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]] if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]]
HandleHostGPUDeviceLost(); HandleHostGPUDeviceLost();
else if (pres == GPUDevice::PresentResult::ExclusiveFullscreenLost)
HandleExclusiveFullscreenLost();
else
g_gpu_device->FlushCommands();
// Still need to kick ImGui or it gets cranky. // Still need to kick ImGui or it gets cranky.
ImGui::Render(); ImGui::EndFrame();
} }
ImGuiManager::NewFrame(); 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); 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()) if (ret.has_value())
{ {
m_last_window_width = ret->surface_width; m_last_window_width = ret->surface_width;

View File

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

View File

@ -852,9 +852,9 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_
m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"), QVariant(QString())); m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"), QVariant(QString()));
if (current_adapter) 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)); m_ui.fullscreenMode->addItem(qmodename, QVariant(qmodename));
} }
} }

View File

@ -83,6 +83,7 @@ static bool s_use_central_widget = false;
// UI thread VM validity. // UI thread VM validity.
static bool s_system_valid = false; static bool s_system_valid = false;
static bool s_system_paused = 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_title;
static QString s_current_game_serial; static QString s_current_game_serial;
static QString s_current_game_path; static QString s_current_game_path;
@ -220,30 +221,25 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
#endif #endif
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
bool surfaceless, bool use_main_window_pos) bool use_main_window_pos, Error* error)
{ {
DEV_LOG("acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}", DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false",
surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false"); use_main_window_pos ? "true" : "false");
QWidget* container = QWidget* container =
m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget); m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
const bool is_fullscreen = isRenderingFullscreen(); const bool is_fullscreen = isRenderingFullscreen();
const bool is_rendering_to_main = isRenderingToMain(); const bool is_rendering_to_main = isRenderingToMain();
const bool changing_surfaceless = (!m_display_widget != surfaceless); 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. // 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. // .. except on Wayland, where everything tends to break if you don't recreate.
const bool has_container = (m_display_container != nullptr); const bool has_container = (m_display_container != nullptr);
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main); const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && if (m_display_created && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
has_container == needs_container && !needs_container && !changing_surfaceless) !needs_container && !changing_surfaceless)
{ {
DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed")); DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
@ -257,12 +253,11 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
} }
else else
{ {
container->showNormal();
if (use_main_window_pos) if (use_main_window_pos)
container->setGeometry(geometry()); container->setGeometry(geometry());
else else
restoreDisplayWindowGeometryFromConfig(); restoreDisplayWindowGeometryFromConfig();
container->showNormal();
} }
updateDisplayWidgetCursor(); updateDisplayWidgetCursor();
@ -270,7 +265,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
updateWindowState(); updateWindowState();
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
return m_display_widget->getWindowInfo(); return m_display_widget->getWindowInfo(error);
} }
destroyDisplayWidget(surfaceless); destroyDisplayWidget(surfaceless);
@ -282,7 +277,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); 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()) if (!wi.has_value())
{ {
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); 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) 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) : 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) 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) : 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_dialog_parent = nullptr;
lock.m_was_paused = true; lock.m_was_paused = true;
lock.m_was_fullscreen = false; lock.m_was_fullscreen = false;
@ -2864,6 +2861,8 @@ MainWindow::SystemLock::SystemLock(SystemLock&& lock)
MainWindow::SystemLock::~SystemLock() 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) if (m_was_fullscreen)
g_emu_thread->setFullscreen(true, true); g_emu_thread->setFullscreen(true, true);
if (!m_was_paused) if (!m_was_paused)
@ -2874,4 +2873,9 @@ void MainWindow::SystemLock::cancelResume()
{ {
m_was_paused = true; m_was_paused = true;
m_was_fullscreen = false; 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); bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message); void onStatusMessage(const QString& message);
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
bool surfaceless, bool use_main_window_pos); bool use_main_window_pos, Error* error);
void displayResizeRequested(qint32 width, qint32 height); void displayResizeRequested(qint32 width, qint32 height);
void releaseRenderWindow(); void releaseRenderWindow();
void focusDisplayWidget(); 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) void EmuThread::checkForSettingsChanges(const Settings& old_settings)
{ {
if (g_main_window) if (g_main_window)
@ -642,12 +635,16 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
updatePerformanceCounters(); updatePerformanceCounters();
} }
const bool render_to_main = shouldRenderToMain(); // don't mess with fullscreen while locked
if (m_is_rendering_to_main != render_to_main) if (!QtHost::IsSystemLocked())
{ {
m_is_rendering_to_main = render_to_main; const bool render_to_main = shouldRenderToMain();
if (g_gpu_device) if (m_is_rendering_to_main != render_to_main && !m_is_fullscreen)
g_gpu_device->UpdateWindow(); {
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 // we want settings loaded so we choose the correct renderer
// this also sorts out input sources. // this also sorts out input sources.
System::LoadSettings(false); 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; m_run_fullscreen_ui = true;
Error error; 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()) !FullscreenUI::Initialize())
{ {
Host::ReportErrorAsync("Error", error.GetDescription()); Host::ReportErrorAsync("Error", error.GetDescription());
Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
m_run_fullscreen_ui = false; m_run_fullscreen_ui = false;
return; return;
} }
@ -825,7 +821,6 @@ void EmuThread::stopFullscreenUI()
return; return;
Host::ReleaseGPUDevice(); Host::ReleaseGPUDevice();
Host::ReleaseRenderWindow();
} }
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params) void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
@ -841,7 +836,7 @@ void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
if (System::IsValidOrInitializing()) if (System::IsValidOrInitializing())
return; return;
setInitialState(params->override_fullscreen); m_is_rendering_to_main = shouldRenderToMain();
Error error; Error error;
if (!System::BootSystem(std::move(*params), &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_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain(); m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
Host::UpdateDisplayWindow(); Host::UpdateDisplayWindow(fullscreen);
} }
bool Host::IsFullscreen() bool Host::IsFullscreen()
@ -984,6 +979,10 @@ bool Host::IsFullscreen()
void Host::SetFullscreen(bool enabled) void Host::SetFullscreen(bool enabled)
{ {
// don't mess with fullscreen while locked
if (QtHost::IsSystemLocked())
return;
g_emu_thread->setFullscreen(enabled, true); g_emu_thread->setFullscreen(enabled, true);
} }
@ -999,7 +998,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
return; return;
m_is_surfaceless = surfaceless; m_is_surfaceless = surfaceless;
Host::UpdateDisplayWindow(); Host::UpdateDisplayWindow(false);
} }
void EmuThread::requestDisplaySize(float scale) void EmuThread::requestDisplaySize(float scale)
@ -1016,20 +1015,18 @@ void EmuThread::requestDisplaySize(float scale)
System::RequestDisplaySize(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); 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; m_is_fullscreen = fullscreen;
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
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(); const bool use_main_window_pos = shouldRenderToMain();
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless, return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos,
use_main_window_pos); error);
} }
void EmuThread::releaseRenderWindow() void EmuThread::releaseRenderWindow()
@ -1811,11 +1808,11 @@ void EmuThread::run()
m_event_loop->processEvents(QEventLoop::AllEvents); m_event_loop->processEvents(QEventLoop::AllEvents);
System::Internal::IdlePollUpdate(); System::Internal::IdlePollUpdate();
if (g_gpu_device) if (g_gpu_device && g_gpu_device->HasMainSwapChain())
{ {
System::PresentDisplay(false, 0); System::PresentDisplay(false, 0);
if (!g_gpu_device->IsVSyncModeBlocking()) if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking())
g_gpu_device->ThrottlePresentation(); g_gpu_device->GetMainSwapChain()->ThrottlePresentation();
} }
} }
} }
@ -2005,9 +2002,10 @@ void Host::CommitBaseSettingChanges()
QtHost::QueueSettingsSave(); 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() void Host::ReleaseRenderWindow()

View File

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

View File

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

View File

@ -19,7 +19,7 @@
#include <initializer_list> #include <initializer_list>
#include <optional> #include <optional>
class ByteStream; class Error;
class QComboBox; class QComboBox;
class QFrame; class QFrame;
@ -116,7 +116,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
qreal GetDevicePixelRatioForWidget(const QWidget* widget); qreal GetDevicePixelRatioForWidget(const QWidget* widget);
/// Returns the common window info structure for a Qt 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. /// 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); 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.cpp
opengl_context_wgl.h opengl_context_wgl.h
) )
target_link_libraries(util PRIVATE "opengl32.lib")
endif() endif()
if(LINUX OR BSD OR ANDROID) if(LINUX OR BSD OR ANDROID)
@ -183,6 +182,9 @@ if(NOT ANDROID)
sdl_input_source.cpp sdl_input_source.cpp
sdl_input_source.h sdl_input_source.h
) )
target_compile_definitions(util PUBLIC
ENABLE_SDL
)
target_link_libraries(util PUBLIC target_link_libraries(util PUBLIC
cubeb cubeb
SDL2::SDL2 SDL2::SDL2

View File

@ -22,7 +22,7 @@
#include <d3dcompiler.h> #include <d3dcompiler.h>
#include <dxgi1_5.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. // We need to synchronize instance creation because of adapter enumeration from the UI thread.
static std::mutex s_instance_mutex; static std::mutex s_instance_mutex;
@ -45,7 +45,10 @@ void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name)
#endif #endif
} }
D3D11Device::D3D11Device() = default; D3D11Device::D3D11Device()
{
m_render_api = RenderAPI::D3D11;
}
D3D11Device::~D3D11Device() D3D11Device::~D3D11Device()
{ {
@ -53,13 +56,11 @@ D3D11Device::~D3D11Device()
Assert(!m_device); Assert(!m_device);
} }
bool D3D11Device::HasSurface() const bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
{ const WindowInfo& wi, GPUVSyncMode vsync_mode,
return static_cast<bool>(m_swap_chain); bool allow_present_throttle,
} const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
{ {
std::unique_lock lock(s_instance_mutex); 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: {}", INFO_LOG("Max device feature level: {}",
D3DCommon::GetFeatureLevelString(D3DCommon::GetRenderAPIVersionForFeatureLevel(m_max_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); SetFeatures(disabled_features);
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()) if (!wi.IsSurfaceless())
{ {
Error::SetStringView(error, "Failed to create swap chain"); m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
return false; exclusive_fullscreen_control, error);
if (!m_main_swap_chain)
return false;
} }
if (!CreateBuffers()) if (!CreateBuffers())
@ -152,6 +150,7 @@ void D3D11Device::DestroyDevice()
std::unique_lock lock(s_instance_mutex); std::unique_lock lock(s_instance_mutex);
DestroyBuffers(); DestroyBuffers();
m_main_swap_chain.reset();
m_context.Reset(); m_context.Reset();
m_device.Reset(); m_device.Reset();
} }
@ -160,7 +159,6 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
{ {
const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel(); const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel();
m_render_api = RenderAPI::D3D11;
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level); m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
m_max_multisamples = 1; 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. if (fullscreen_mode)
// With triple buffering, we need three. InitializeExclusiveFullscreenMode(fullscreen_mode);
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
} }
bool D3D11Device::CreateSwapChain() D3D11SwapChain::~D3D11SwapChain()
{ {
if (m_window_info.type != WindowInfo::Type::Win32) m_swap_chain_rtv.Reset();
return false; 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); const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{}; RECT client_rc{};
GetClientRect(window_hwnd, &client_rc); GetClientRect(window_hwnd, &client_rc);
DXGI_MODE_DESC fullscreen_mode = {}; m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
ComPtr<IDXGIOutput> fullscreen_output; D3D11Device::GetDXGIFactory(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
if (Host::IsFullscreen()) return m_fullscreen_mode.has_value();
{ }
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());
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. u32 D3D11SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) {
{ // With vsync off, we only need two buffers. Same for blocking vsync.
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); // With triple buffering, we need three.
m_vsync_mode = GPUVSyncMode::FIFO; return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
} }
}
else 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 = 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 = {}; DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left); 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.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.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.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; 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; HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen) if (IsExclusiveFullscreen())
{ {
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc; DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width; fs_sd_desc.Width = m_fullscreen_mode->Width;
fs_sd_desc.Height = fullscreen_mode.Height; fs_sd_desc.Height = m_fullscreen_mode->Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate; fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering; fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling; fs_desc.Scaling = m_fullscreen_mode->Scaling;
fs_desc.Windowed = FALSE; fs_desc.Windowed = FALSE;
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height); 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, hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &fs_sd_desc, &fs_desc, m_fullscreen_output.Get(),
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf()); m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr)) if (FAILED(hr))
{ {
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed."); WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false; m_fullscreen_output.Reset();
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain; 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, 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"); 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_using_allow_tearing = (m_using_flip_model_swap_chain && !IsExclusiveFullscreen() &&
m_swap_chain.ReleaseAndGetAddressOf()); 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) 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_flip_model_swap_chain = false;
m_using_allow_tearing = false; m_using_allow_tearing = false;
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr,
m_swap_chain.ReleaseAndGetAddressOf()); m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr)) [[unlikely]] if (FAILED(hr)) [[unlikely]]
{ {
ERROR_LOG("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast<unsigned>(hr)); Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr);
return false; return false;
} }
} }
@ -319,25 +328,29 @@ bool D3D11Device::CreateSwapChain()
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed"); 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; 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; ComPtr<ID3D11Texture2D> backbuffer;
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf())); HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf()));
if (FAILED(hr)) [[unlikely]] if (FAILED(hr)) [[unlikely]]
{ {
ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr)); Error::SetHResult(error, "GetBuffer() failed: ", hr);
return false; 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, CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0,
backbuffer_desc.ArraySize); 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]] 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(); m_swap_chain_rtv.Reset();
return false; return false;
} }
m_window_info.surface_width = backbuffer_desc.Width; m_window_info.surface_width = static_cast<u16>(backbuffer_desc.Width);
m_window_info.surface_height = backbuffer_desc.Height; m_window_info.surface_height = static_cast<u16>(backbuffer_desc.Height);
m_window_info.surface_format = s_swap_chain_format; 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); 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; return true;
} }
void D3D11Device::DestroySwapChain() bool D3D11SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
{ {
if (!m_swap_chain) m_window_info.surface_scale = new_scale;
return; if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
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_swap_chain_rtv.Reset(); m_swap_chain_rtv.Reset();
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
if (FAILED(hr)) [[unlikely]] if (FAILED(hr)) [[unlikely]]
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr)); {
Error::SetHResult(error, "ResizeBuffers() failed: ", hr);
return false;
}
if (!CreateSwapChainRTV()) return CreateRTV(error);
Panic("Failed to recreate swap chain RTV after resize"); }
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 bool D3D11Device::SupportsExclusiveFullscreen() const
@ -471,9 +497,16 @@ std::string D3D11Device::GetDriverInfo() const
return ret; return ret;
} }
void D3D11Device::ExecuteAndWaitForGPUIdle() void D3D11Device::FlushCommands()
{ {
m_context->Flush(); m_context->Flush();
TrimTexturePool();
}
void D3D11Device::WaitForGPUIdle()
{
m_context->Flush();
TrimTexturePool();
} }
bool D3D11Device::CreateBuffers() bool D3D11Device::CreateBuffers()
@ -610,63 +643,29 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t)
T->CommitClear(m_context.Get()); 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; D3D11SwapChain* const SC = static_cast<D3D11SwapChain*>(swap_chain);
// 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;
}
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode. // 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. // This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen; BOOL is_fullscreen;
if (m_is_exclusive_fullscreen && if (SC->IsExclusiveFullscreen() &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) (FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{ {
Host::SetFullscreen(false);
TrimTexturePool(); TrimTexturePool();
return PresentResult::SkipPresent; return PresentResult::ExclusiveFullscreenLost;
} }
// When using vsync, the time here seems to include the time for the buffer to become available. // 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 // 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 // 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. // 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(); PopTimestampQuery();
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), GSVector4::rgba32(clear_color).F32); m_context->ClearRenderTargetView(SC->GetRTV(), GSVector4::rgba32(clear_color).F32);
m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); m_context->OMSetRenderTargets(1, SC->GetRTVArray(), nullptr);
s_stats.num_render_passes++; s_stats.num_render_passes++;
m_num_current_render_targets = 0; m_num_current_render_targets = 0;
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags; m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
@ -675,17 +674,19 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
return PresentResult::OK; 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(!explicit_present && present_time == 0);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); 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(); PopTimestampQuery();
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO); const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; const UINT flags =
m_swap_chain->Present(sync_interval, flags); (SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
SC->GetSwapChain()->Present(sync_interval, flags);
if (m_gpu_timing_enabled) if (m_gpu_timing_enabled)
KickTimestampQuery(); KickTimestampQuery();
@ -693,7 +694,7 @@ void D3D11Device::EndPresent(bool explicit_present, u64 present_time)
TrimTexturePool(); TrimTexturePool();
} }
void D3D11Device::SubmitPresent() void D3D11Device::SubmitPresent(GPUSwapChain* swap_chain)
{ {
Panic("Not supported by this API."); 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) void D3D11Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type)
{ {
Panic("Barriers are not supported"); Panic("Barriers are not supported");
} }

View File

@ -32,21 +32,23 @@ public:
~D3D11Device(); ~D3D11Device();
ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast<D3D11Device*>(g_gpu_device.get()); } 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 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; } 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; bool SupportsExclusiveFullscreen() const override;
void DestroySurface() override;
std::string GetDriverInfo() const 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, std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override; 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 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 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; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) override; PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override; void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent() override; void SubmitPresent(GPUSwapChain* swap_chain) override;
void UnbindPipeline(D3D11Pipeline* pl); void UnbindPipeline(D3D11Pipeline* pl);
void UnbindTexture(D3D11Texture* tex); void UnbindTexture(D3D11Texture* tex);
protected: protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
FeatureMask disabled_features, Error* error) override; GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override; void DestroyDevice() override;
private: private:
@ -136,11 +138,6 @@ private:
void SetFeatures(FeatureMask disabled_features); void SetFeatures(FeatureMask disabled_features);
u32 GetSwapChainBufferCount() const;
bool CreateSwapChain();
bool CreateSwapChainRTV();
void DestroySwapChain();
bool CreateBuffers(); bool CreateBuffers();
void DestroyBuffers(); void DestroyBuffers();
@ -161,8 +158,6 @@ private:
ComPtr<ID3DUserDefinedAnnotation> m_annotation; ComPtr<ID3DUserDefinedAnnotation> m_annotation;
ComPtr<IDXGIFactory5> m_dxgi_factory; ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
RasterizationStateMap m_rasterization_states; RasterizationStateMap m_rasterization_states;
DepthStateMap m_depth_states; DepthStateMap m_depth_states;
@ -170,10 +165,6 @@ private:
InputLayoutMap m_input_layouts; InputLayoutMap m_input_layouts;
D3D_FEATURE_LEVEL m_max_feature_level = D3D_FEATURE_LEVEL_10_0; 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_vertex_buffer;
D3D11StreamBuffer m_index_buffer; D3D11StreamBuffer m_index_buffer;
@ -207,4 +198,45 @@ private:
float m_accumulated_gpu_time = 0.0f; 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); void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name);

View File

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

View File

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

View File

@ -8,8 +8,6 @@
#include "d3d12_texture.h" #include "d3d12_texture.h"
#include "d3d_common.h" #include "d3d_common.h"
#include "core/host.h"
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/bitutils.h" #include "common/bitutils.h"
@ -27,7 +25,7 @@
#include <limits> #include <limits>
#include <mutex> #include <mutex>
LOG_CHANNEL(D3D12Device); LOG_CHANNEL(GPUDevice);
// Tweakables // Tweakables
enum : u32 enum : u32
@ -69,6 +67,8 @@ static u32 s_debug_scope_depth = 0;
D3D12Device::D3D12Device() D3D12Device::D3D12Device()
{ {
m_render_api = RenderAPI::D3D12;
#ifdef _DEBUG #ifdef _DEBUG
s_debug_scope_depth = 0; s_debug_scope_depth = 0;
#endif #endif
@ -117,8 +117,11 @@ D3D12Device::ComPtr<ID3D12RootSignature> D3D12Device::CreateRootSignature(const
return rs; return rs;
} }
bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, bool D3D12Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
FeatureMask disabled_features, Error* error) 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); 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)) if (!CreateCommandLists(error) || !CreateDescriptorHeaps(error))
return false; return false;
if (!m_window_info.IsSurfaceless() && !CreateSwapChain(error)) if (!wi.IsSurfaceless())
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 (!CreateRootSignatures(error) || !CreateBuffers(error)) if (!CreateRootSignatures(error) || !CreateBuffers(error))
return false; return false;
@ -256,7 +264,9 @@ void D3D12Device::DestroyDevice()
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
WaitForGPUIdle(); WaitForAllFences();
m_main_swap_chain.reset();
DestroyDeferredObjects(m_current_fence_value); DestroyDeferredObjects(m_current_fence_value);
DestroySamplers(); DestroySamplers();
@ -264,7 +274,6 @@ void D3D12Device::DestroyDevice()
DestroyBuffers(); DestroyBuffers();
DestroyDescriptorHeaps(); DestroyDescriptorHeaps();
DestroyRootSignatures(); DestroyRootSignatures();
DestroySwapChain();
DestroyCommandLists(); DestroyCommandLists();
m_pipeline_library.Reset(); m_pipeline_library.Reset();
@ -656,7 +665,7 @@ void D3D12Device::WaitForFence(u64 fence)
DestroyDeferredObjects(m_completed_fence_value); DestroyDeferredObjects(m_completed_fence_value);
} }
void D3D12Device::WaitForGPUIdle() void D3D12Device::WaitForAllFences()
{ {
u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS; u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
for (u32 i = 0; i < (NUM_COMMAND_LISTS - 1); i++) 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()) if (InRenderPass())
EndRenderPass(); 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. DestroyRTVs();
// With triple buffering, we need three. DestroySwapChain();
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
} }
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 D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle); const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{}; RECT client_rc{};
GetClientRect(window_hwnd, &client_rc); GetClientRect(window_hwnd, &client_rc);
DXGI_MODE_DESC fullscreen_mode = {}; m_fullscreen_mode =
ComPtr<IDXGIOutput> fullscreen_output; D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode,
if (Host::IsFullscreen()) fm.resource_format, m_fullscreen_output.GetAddressOf());
{ return m_fullscreen_mode.has_value();
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());
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. u32 D3D12SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) {
{ // With vsync off, we only need two buffers. Same for blocking vsync.
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); // With triple buffering, we need three.
m_vsync_mode = GPUVSyncMode::FIFO; return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
} }
}
else 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 = {}; 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.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = fm.resource_format; swap_chain_desc.Format = fm.resource_format;
swap_chain_desc.SampleDesc.Count = 1; 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.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 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; HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen) if (IsExclusiveFullscreen())
{ {
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc; DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width; fs_sd_desc.Width = m_fullscreen_mode->Width;
fs_sd_desc.Height = fullscreen_mode.Height; fs_sd_desc.Height = m_fullscreen_mode->Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate; fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering; fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling; fs_desc.Scaling = m_fullscreen_mode->Scaling;
fs_desc.Windowed = FALSE; fs_desc.Windowed = FALSE;
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height); 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, hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &fs_sd_desc, &fs_desc,
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf()); m_fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
if (FAILED(hr)) if (FAILED(hr))
{ {
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed."); WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false; m_fullscreen_output.Reset();
m_using_allow_tearing = m_allow_tearing_supported; 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); 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_using_allow_tearing = D3DCommon::SupportsAllowTearing(dev.GetDXGIFactory());
m_swap_chain.ReleaseAndGetAddressOf()); 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)) if (FAILED(hr))
{ {
Error::SetHResult(error, "CreateSwapChainForHwnd() 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)) if (FAILED(hr))
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed"); 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; return true;
} }
bool D3D12Device::CreateSwapChainRTV(Error* error) bool D3D12SwapChain::CreateRTV(D3D12Device& dev, Error* error)
{ {
DXGI_SWAP_CHAIN_DESC swap_chain_desc; DXGI_SWAP_CHAIN_DESC swap_chain_desc;
HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc); HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc);
@ -925,148 +934,162 @@ bool D3D12Device::CreateSwapChainRTV(Error* error)
if (FAILED(hr)) if (FAILED(hr))
{ {
Error::SetHResult(error, "GetBuffer for RTV failed: ", hr); Error::SetHResult(error, "GetBuffer for RTV failed: ", hr);
DestroySwapChainRTVs(); DestroyRTVs();
return false; return false;
} }
D3D12::SetObjectName(backbuffer.Get(), TinyString::from_format("Swap Chain Buffer #{}", i)); D3D12::SetObjectName(backbuffer.Get(), TinyString::from_format("Swap Chain Buffer #{}", i));
D3D12DescriptorHandle rtv; D3D12DescriptorHandle rtv;
if (!m_rtv_heap_manager.Allocate(&rtv)) if (!dev.GetRTVHeapManager().Allocate(&rtv))
{ {
Error::SetStringView(error, "Failed to allocate RTV handle."); Error::SetStringView(error, "Failed to allocate RTV handle.");
DestroySwapChainRTVs(); DestroyRTVs();
return false; 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_swap_chain_buffers.emplace_back(std::move(backbuffer), rtv);
} }
m_window_info.surface_width = swap_chain_desc.BufferDesc.Width; m_window_info.surface_width = static_cast<u16>(swap_chain_desc.BufferDesc.Width);
m_window_info.surface_height = swap_chain_desc.BufferDesc.Height; m_window_info.surface_height = static_cast<u16>(swap_chain_desc.BufferDesc.Height);
m_window_info.surface_format = s_swap_chain_format; 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); 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; m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
DXGI_SWAP_CHAIN_DESC desc; static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
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_current_swap_chain_buffer = 0; m_current_swap_chain_buffer = 0;
return true; 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... // Runtime gets cranky if we don't submit the current buffer...
if (InRenderPass()) if (dev.InRenderPass())
EndRenderPass(); dev.EndRenderPass();
SubmitCommandList(true); dev.SubmitCommandList(true);
for (auto it = m_swap_chain_buffers.rbegin(); it != m_swap_chain_buffers.rend(); ++it) 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(); it->first.Reset();
} }
m_swap_chain_buffers.clear(); m_swap_chain_buffers.clear();
m_current_swap_chain_buffer = 0; m_current_swap_chain_buffer = 0;
} }
void D3D12Device::DestroySwapChain() void D3D12SwapChain::DestroySwapChain()
{ {
if (!m_swap_chain) if (!m_swap_chain)
return; return;
DestroySwapChainRTVs();
// switch out of fullscreen before destroying // switch out of fullscreen before destroying
BOOL is_fullscreen; BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen) if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr); m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.Reset(); 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()) m_allow_present_throttle = allow_present_throttle;
EndRenderPass();
auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
ID3D12GraphicsCommandList4* cmdlist = GetCommandList(); if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen())
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, WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
D3D12_RESOURCE_STATE_RENDER_TARGET); mode = GPUVSyncMode::FIFO;
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);
}
bool D3D12Device::UpdateWindow() if (m_vsync_mode == mode)
{
WaitForGPUIdle();
DestroySwapChain();
if (!AcquireWindow(false))
return false;
if (m_window_info.IsSurfaceless())
return true; return true;
Error error; const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode);
if (!CreateSwapChain(&error)) const u32 new_buffer_count = GetNewBufferCount(mode);
{ m_vsync_mode = mode;
ERROR_LOG("Failed to create swap chain on updated window: {}", error.GetDescription()); if (old_buffer_count == new_buffer_count)
return false; return true;
}
RenderBlankFrame(); // Buffer count change => needs recreation.
return true; 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) m_window_info.surface_scale = new_scale;
return; 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; DestroyRTVs();
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();
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
if (FAILED(hr)) if (FAILED(hr))
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr)); ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
Error error; return CreateRTV(D3D12Device::GetInstance(), 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");
}
} }
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(); std::unique_ptr<D3D12SwapChain> ret;
DestroySwapChain(); 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 bool D3D12Device::SupportsTextureFormat(GPUTexture::Format format) const
@ -1105,79 +1128,77 @@ std::string D3D12Device::GetDriverInfo() const
return ret; return ret;
} }
void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) GPUDevice::PresentResult D3D12Device::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();
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)
{ {
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
if (m_device_was_lost) [[unlikely]] if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost; 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. // 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. // 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. // This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen; BOOL is_fullscreen;
if (m_is_exclusive_fullscreen && if (SC->IsExclusiveFullscreen() &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) (FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{ {
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); }); FlushCommands();
TrimTexturePool(); 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; 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(present_time == 0);
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass(); EndRenderPass();
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; DebugAssert(SC == m_current_swap_chain);
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size())); m_current_swap_chain = nullptr;
const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer();
SC->AdvanceBuffer();
ID3D12GraphicsCommandList* cmdlist = GetCommandList(); ID3D12GraphicsCommandList* cmdlist = GetCommandList();
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET, 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(); TrimTexturePool();
if (!explicit_present) 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]] if (m_device_was_lost) [[unlikely]]
return; return;
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO); const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; const UINT flags =
m_swap_chain->Present(sync_interval, flags); (SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
SC->GetSwapChain()->Present(sync_interval, flags);
} }
#ifdef _DEBUG #ifdef _DEBUG
@ -1251,7 +1273,6 @@ void D3D12Device::InsertDebugMessage(const char* msg)
void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features) 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_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
m_max_multisamples = 1; 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.pipeline_cache = true;
m_features.prefer_unused_textures = 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; m_features.raster_order_views = false;
if (!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS)) if (!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS))
{ {
@ -1841,7 +1857,7 @@ void D3D12Device::BeginRenderPass()
else else
{ {
// Re-rendering to swap chain. // 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, rt_desc[0] = {swap_chain_buf.second,
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, {}}, {D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, {}},
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}}; {D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
@ -1869,43 +1885,6 @@ void D3D12Device::BeginRenderPass()
SetInitialPipelineState(); 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() bool D3D12Device::InRenderPass()
{ {
return m_in_render_pass; return m_in_render_pass;

View File

@ -37,6 +37,8 @@ namespace D3D12MA {
class Allocator; class Allocator;
} }
class D3D12SwapChain;
class D3D12Device final : public GPUDevice class D3D12Device final : public GPUDevice
{ {
public: public:
@ -58,17 +60,16 @@ public:
D3D12Device(); D3D12Device();
~D3D12Device() override; ~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; 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, std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override; 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 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 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; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
PresentResult BeginPresent(u32 clear_color) override; PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(bool explicit_present, u64 present_time) override; void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
void SubmitPresent() override; void SubmitPresent(GPUSwapChain* swap_chain) override;
// Global state accessors // Global state accessors
ALWAYS_INLINE static D3D12Device& GetInstance() { return *static_cast<D3D12Device*>(g_gpu_device.get()); } ALWAYS_INLINE static D3D12Device& GetInstance() { return *static_cast<D3D12Device*>(g_gpu_device.get()); }
ALWAYS_INLINE IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); } ALWAYS_INLINE IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); }
ALWAYS_INLINE ID3D12Device1* GetDevice() const { return m_device.Get(); } ALWAYS_INLINE ID3D12Device1* GetDevice() const { return m_device.Get(); }
ALWAYS_INLINE ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.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(); } ALWAYS_INLINE D3D12MA::Allocator* GetAllocator() const { return m_allocator.Get(); }
void WaitForGPUIdle(); void WaitForAllFences();
// Descriptor manager access. // Descriptor manager access.
D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; } D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; }
@ -173,6 +173,12 @@ public:
// Also invokes callbacks for completion. // Also invokes callbacks for completion.
void WaitForFence(u64 fence_counter); 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. /// Ends any render pass, executes the command buffer, and invalidates cached state.
void SubmitCommandList(bool wait_for_completion); void SubmitCommandList(bool wait_for_completion);
void SubmitCommandList(bool wait_for_completion, const std::string_view reason); void SubmitCommandList(bool wait_for_completion, const std::string_view reason);
@ -183,8 +189,10 @@ public:
void UnbindTextureBuffer(D3D12TextureBuffer* buf); void UnbindTextureBuffer(D3D12TextureBuffer* buf);
protected: protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
FeatureMask disabled_features, Error* error) override; GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override; void DestroyDevice() override;
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override; bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
@ -232,12 +240,6 @@ private:
void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr); void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr);
void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features); 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); bool CreateCommandLists(Error* error);
void DestroyCommandLists(); void DestroyCommandLists();
bool CreateRootSignatures(Error* error); bool CreateRootSignatures(Error* error);
@ -252,7 +254,7 @@ private:
void DestroySamplers(); void DestroySamplers();
void DestroyDeferredObjects(u64 fence_value); void DestroyDeferredObjects(u64 fence_value);
void RenderBlankFrame(); void RenderBlankFrame(D3D12SwapChain* swap_chain);
void MoveToNextCommandList(); void MoveToNextCommandList();
bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format, bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
@ -280,13 +282,6 @@ private:
bool UpdateParametersForLayout(u32 dirty); bool UpdateParametersForLayout(u32 dirty);
bool UpdateRootParameters(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<IDXGIAdapter1> m_adapter;
ComPtr<ID3D12Device1> m_device; ComPtr<ID3D12Device1> m_device;
ComPtr<ID3D12CommandQueue> m_command_queue; ComPtr<ID3D12CommandQueue> m_command_queue;
@ -299,15 +294,9 @@ private:
std::array<CommandList, NUM_COMMAND_LISTS> m_command_lists; std::array<CommandList, NUM_COMMAND_LISTS> m_command_lists;
u32 m_current_command_list = NUM_COMMAND_LISTS - 1; u32 m_current_command_list = NUM_COMMAND_LISTS - 1;
bool m_device_was_lost = false;
ComPtr<IDXGIFactory5> m_dxgi_factory; 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_descriptor_heap_manager;
D3D12DescriptorHeapManager m_rtv_heap_manager; D3D12DescriptorHeapManager m_rtv_heap_manager;
@ -358,4 +347,52 @@ private:
D3D12TextureBuffer* m_current_texture_buffer = nullptr; D3D12TextureBuffer* m_current_texture_buffer = nullptr;
GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1); GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1);
GSVector4i m_current_scissor = {}; 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> #include <d3dcompiler.h>
LOG_CHANNEL(D3D12Device); LOG_CHANNEL(GPUDevice);
D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(stage), m_bytecode(std::move(bytecode)) D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(stage), m_bytecode(std::move(bytecode))
{ {

View File

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

View File

@ -15,7 +15,7 @@
#include "D3D12MemAlloc.h" #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, D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource, DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,

View File

@ -19,7 +19,7 @@
#include <dxcapi.h> #include <dxcapi.h>
#include <dxgi1_5.h> #include <dxgi1_5.h>
LOG_CHANNEL(D3DCommon); LOG_CHANNEL(GPUDevice);
namespace D3DCommon { namespace D3DCommon {
namespace { namespace {
@ -123,6 +123,14 @@ Microsoft::WRL::ComPtr<IDXGIFactory5> D3DCommon::CreateFactory(bool debug, Error
return factory; 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) static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList& adapter_names, std::string adapter_name)
{ {
if (std::any_of(adapter_names.begin(), adapter_names.end(), 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) for (const DXGI_MODE_DESC& mode : dmodes)
{ {
ai.fullscreen_modes.push_back(GPUDevice::GetFullscreenModeString( ai.fullscreen_modes.push_back(
mode.Width, mode.Height, GPUDevice::ExclusiveFullscreenMode{.width = mode.Width,
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator))); .height = mode.Height,
.refresh_rate = static_cast<float>(mode.RefreshRate.Numerator) /
static_cast<float>(mode.RefreshRate.Denominator)});
} }
} }
else else
@ -211,10 +221,13 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
return adapters; return adapters;
} }
bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, std::optional<DXGI_MODE_DESC>
u32 height, float refresh_rate, DXGI_FORMAT format, D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output) 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. // 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); 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) if (!first_output)
{ {
ERROR_LOG("No DXGI output found. Can't use exclusive fullscreen."); 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."); WARNING_LOG("No DXGI output found for window, using first.");
@ -268,22 +281,25 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
} }
DXGI_MODE_DESC request_mode = {}; DXGI_MODE_DESC request_mode = {};
request_mode.Width = width; request_mode.Width = requested_fullscreen_mode->width;
request_mode.Height = height; request_mode.Height = requested_fullscreen_mode->height;
request_mode.Format = format; 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; 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) request_mode.Format != format)
{ {
ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr)); ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
return false; ret.reset();
return ret;
} }
*output = intersecting_output.Get(); *output = intersecting_output.Get();
intersecting_output->AddRef(); intersecting_output->AddRef();
return true; return ret;
} }
Microsoft::WRL::ComPtr<IDXGIAdapter1> D3DCommon::GetAdapterByName(IDXGIFactory5* factory, std::string_view name) 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 // create a dxgi factory
Microsoft::WRL::ComPtr<IDXGIFactory5> CreateFactory(bool debug, Error* error); Microsoft::WRL::ComPtr<IDXGIFactory5> CreateFactory(bool debug, Error* error);
bool SupportsAllowTearing(IDXGIFactory5* factory);
// returns a list of all adapter names // returns a list of all adapter names
GPUDevice::AdapterInfoList GetAdapterInfoList(); GPUDevice::AdapterInfoList GetAdapterInfoList();
// returns the fullscreen mode to use for the specified dimensions // returns the fullscreen mode to use for the specified dimensions
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height, std::optional<DXGI_MODE_DESC>
float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
IDXGIOutput** output); const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
DXGI_FORMAT format, IDXGIOutput** output);
// get an adapter based on name // get an adapter based on name
Microsoft::WRL::ComPtr<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, std::string_view name); Microsoft::WRL::ComPtr<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, std::string_view name);

View File

@ -3,8 +3,6 @@
#include "gpu_device.h" #include "gpu_device.h"
#include "compress_helpers.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 "gpu_framebuffer_manager.h"
#include "shadergen.h" #include "shadergen.h"
@ -44,6 +42,7 @@ LOG_CHANNEL(GPUDevice);
std::unique_ptr<GPUDevice> g_gpu_device; std::unique_ptr<GPUDevice> g_gpu_device;
static std::string s_shader_dump_path;
static std::string s_pipeline_cache_path; static std::string s_pipeline_cache_path;
static size_t s_pipeline_cache_size; static size_t s_pipeline_cache_size;
static std::array<u8, SHA1Digest::DIGEST_SIZE> s_pipeline_cache_hash; 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); 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() GPUDevice::GPUDevice()
{ {
ResetStatistics(); ResetStatistics();
@ -346,21 +388,18 @@ GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api)
return ret; return ret;
} }
bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool GPUDevice::Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) 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; m_debug_device = debug_device;
s_shader_dump_path = shader_dump_path;
if (!AcquireWindow(true)) INFO_LOG("Main render window is {}x{}.", wi.surface_width, wi.surface_height);
{ if (!CreateDeviceAndMainSwapChain(adapter, disabled_features, wi, vsync, allow_present_throttle,
Error::SetStringView(error, "Failed to acquire window from host."); exclusive_fullscreen_mode, exclusive_fullscreen_control, error))
return false;
}
if (!CreateDevice(adapter, exclusive_fullscreen_control, disabled_features, error))
{ {
if (error && !error->IsValid()) if (error && !error->IsValid())
error->SetStringView("Failed to create device."); 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() void GPUDevice::Destroy()
{ {
s_shader_dump_path = {};
PurgeTexturePool(); PurgeTexturePool();
if (HasSurface())
DestroySurface();
DestroyResources(); DestroyResources();
CloseShaderCache(); CloseShaderCache();
DestroyDevice(); 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 bool GPUDevice::SupportsExclusiveFullscreen() const
{ {
return false; return false;
@ -533,17 +588,6 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
return false; 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) bool GPUDevice::CreateResources(Error* error)
{ {
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) || if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) ||
@ -587,7 +631,7 @@ bool GPUDevice::CreateResources(Error* error)
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
plconfig.blend.write_mask = 0x7; 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.samples = 1;
plconfig.per_sample_shading = false; plconfig.per_sample_shading = false;
plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags;
@ -619,23 +663,23 @@ void GPUDevice::DestroyResources()
m_shader_cache.Close(); m_shader_cache.Close();
} }
void GPUDevice::RenderImGui() void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
{ {
GL_SCOPE("RenderImGui"); GL_SCOPE("RenderImGui");
ImGui::Render(); ImGui::Render();
const ImDrawData* draw_data = ImGui::GetDrawData(); const ImDrawData* draw_data = ImGui::GetDrawData();
if (draw_data->CmdListsCount == 0) if (draw_data->CmdListsCount == 0 || !swap_chain)
return; return;
SetPipeline(m_imgui_pipeline.get()); 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 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 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] = { const float ortho_projection[4][4] = {
{2.0f / (R - L), 0.0f, 0.0f, 0.0f}, {2.0f / (R - L), 0.0f, 0.0f, 0.0f},
{0.0f, 2.0f / (T - B), 0.0f, 0.0f}, {0.0f, 2.0f / (T - B), 0.0f, 0.0f},
@ -666,8 +710,7 @@ void GPUDevice::RenderImGui()
if (flip) if (flip)
{ {
const s32 height = static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y); const s32 height = static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y);
const s32 flipped_y = const s32 flipped_y = static_cast<s32>(swap_chain->GetHeight()) - static_cast<s32>(pcmd->ClipRect.y) - height;
static_cast<s32>(m_window_info.surface_height) - 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), SetScissor(static_cast<s32>(pcmd->ClipRect.x), flipped_y, static_cast<s32>(pcmd->ClipRect.z - pcmd->ClipRect.x),
height); height);
} }
@ -789,69 +832,59 @@ std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, GPUShad
return shader; 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", ""); std::optional<ExclusiveFullscreenMode> ret;
if (!mode.empty()) std::string_view::size_type sep1 = str.find('x');
if (sep1 != std::string_view::npos)
{ {
const std::string_view mode_view = mode; std::optional<u32> owidth = StringUtil::FromChars<u32>(str.substr(0, sep1));
std::string_view::size_type sep1 = mode.find('x'); sep1++;
if (sep1 != std::string_view::npos)
{ while (sep1 < str.length() && std::isspace(str[sep1]))
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
sep1++; sep1++;
while (sep1 < mode.length() && std::isspace(mode[sep1])) if (owidth.has_value() && sep1 < str.length())
sep1++; {
std::string_view::size_type sep2 = str.find('@', sep1);
if (owidth.has_value() && sep1 < mode.length()) if (sep2 != std::string_view::npos)
{ {
std::string_view::size_type sep2 = mode.find('@', sep1); std::optional<u32> oheight = StringUtil::FromChars<u32>(str.substr(sep1, sep2 - sep1));
if (sep2 != std::string_view::npos) sep2++;
{
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1)); while (sep2 < str.length() && std::isspace(str[sep2]))
sep2++; sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2])) if (oheight.has_value() && sep2 < str.length())
sep2++; {
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(str.substr(sep2));
if (oheight.has_value() && sep2 < mode.length()) if (orefresh_rate.has_value())
{ {
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2)); ret = ExclusiveFullscreenMode{
if (orefresh_rate.has_value()) .width = owidth.value(), .height = oheight.value(), .refresh_rate = orefresh_rate.value()};
{
*width = owidth.value();
*height = oheight.value();
*refresh_rate = orefresh_rate.value();
return true;
}
} }
} }
} }
} }
} }
*width = 0; return ret;
*height = 0;
*refresh_rate = 0;
return false;
} }
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); return TinyString::from_format("{} x {} @ {} hz", width, height, refresh_rate);
}
std::string GPUDevice::GetShaderDumpPath(std::string_view name)
{
return Path::Combine(EmuFolders::Dumps, name);
} }
void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors) void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors)
{ {
static u32 next_bad_shader_id = 0; 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"); auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
if (fp) if (fp)
{ {
@ -1124,42 +1157,6 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
return true; 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) bool GPUDevice::SetGPUTimingEnabled(bool enabled)
{ {
return false; return false;

View File

@ -453,6 +453,38 @@ protected:
u32 m_current_position = 0; 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 class GPUDevice
{ {
public: public:
@ -485,6 +517,7 @@ public:
{ {
OK, OK,
SkipPresent, SkipPresent,
ExclusiveFullscreenLost,
DeviceLost, DeviceLost,
}; };
@ -521,10 +554,22 @@ public:
u32 num_uploads; 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 struct AdapterInfo
{ {
std::string name; std::string name;
std::vector<std::string> fullscreen_modes; std::vector<ExclusiveFullscreenMode> fullscreen_modes;
u32 max_texture_size; u32 max_texture_size;
u32 max_multisamples; u32 max_multisamples;
bool supports_sample_shading; bool supports_sample_shading;
@ -565,15 +610,6 @@ public:
/// Returns a list of adapters for the given API. /// Returns a list of adapters for the given API.
static AdapterInfoList GetAdapterListForAPI(RenderAPI 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. /// Dumps out a shader that failed compilation.
static void DumpBadShader(std::string_view code, std::string_view errors); 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 GetMaxTextureSize() const { return m_max_texture_size; }
ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; } ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; }
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); }
ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); } ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); }
ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); } // ALWAYS_INLINE u32 GetMainSwapChainWidth() const { return m_main_swap_chain->GetWidth(); }
ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; } // ALWAYS_INLINE u32 GetMainSwapChainHeight() const { return m_main_swap_chain->GetHeight(); }
ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; } // 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* GetLinearSampler() const { return m_linear_sampler.get(); }
ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); } ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); }
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; } 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, bool Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
GPUVSyncMode vsync, bool allow_present_throttle, std::optional<bool> exclusive_fullscreen_control, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, const WindowInfo& wi,
FeatureMask disabled_features, Error* error); GPUVSyncMode vsync, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error);
void Destroy(); void Destroy();
virtual bool HasSurface() const = 0; virtual std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
virtual void DestroySurface() = 0; bool allow_present_throttle,
virtual bool UpdateWindow() = 0; 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; 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; 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. // 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, virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, 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; virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0;
/// Returns false if the window was completely occluded. /// Returns false if the window was completely occluded.
virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; virtual PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
virtual void EndPresent(bool explicit_submit, u64 submit_time = 0) = 0; virtual void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 submit_time = 0) = 0;
virtual void SubmitPresent() = 0; virtual void SubmitPresent(GPUSwapChain* swap_chain) = 0;
/// Renders ImGui screen elements. Call before EndPresent(). /// Renders ImGui screen elements. Call before EndPresent().
void RenderImGui(); void RenderImGui(GPUSwapChain* swap_chain);
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;
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
@ -730,8 +770,6 @@ public:
static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height); static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height);
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type, bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve = true); GPUTexture::Format format, bool preserve = true);
bool ShouldSkipPresentingFrame();
void ThrottlePresentation();
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0; virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
@ -745,8 +783,10 @@ public:
static void ResetStatistics(); static void ResetStatistics();
protected: protected:
virtual bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, virtual bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
FeatureMask disabled_features, Error* error) = 0; 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; virtual void DestroyDevice() = 0;
std::string GetShaderCacheBaseName(std::string_view type) const; std::string GetShaderCacheBaseName(std::string_view type) const;
@ -762,8 +802,6 @@ protected:
std::string_view source, const char* entry_point, std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) = 0; DynamicHeapArray<u8>* out_binary, Error* error) = 0;
bool AcquireWindow(bool recreate_window);
void TrimTexturePool(); void TrimTexturePool();
bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source, 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_texture_size = 0;
u32 m_max_multisamples = 0; u32 m_max_multisamples = 0;
WindowInfo m_window_info; std::unique_ptr<GPUSwapChain> m_main_swap_chain;
u64 m_last_frame_displayed_time = 0;
GPUShaderCache m_shader_cache; GPUShaderCache m_shader_cache;
@ -852,8 +889,6 @@ private:
protected: protected:
static Statistics s_stats; static Statistics s_stats;
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
bool m_allow_present_throttle = false;
bool m_gpu_timing_enabled = false; bool m_gpu_timing_enabled = false;
bool m_debug_device = 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)); 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. // Macros for debug messages.
#ifdef _DEBUG #ifdef _DEBUG
struct GLAutoPop struct GLAutoPop

View File

@ -231,7 +231,8 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
} }
s_global_prescale = global_scale; 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; s_scale_changed = false;
ImGui::CreateContext(); ImGui::CreateContext();
@ -250,8 +251,10 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
#endif #endif
s_window_width = static_cast<float>(g_gpu_device->GetWindowWidth()); s_window_width =
s_window_height = static_cast<float>(g_gpu_device->GetWindowHeight()); 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.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
io.DisplaySize = ImVec2(s_window_width, s_window_height); io.DisplaySize = ImVec2(s_window_width, s_window_height);
@ -316,7 +319,8 @@ void ImGuiManager::RequestScaleUpdate()
void ImGuiManager::UpdateScale() 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); const float scale = std::max(window_scale * s_global_prescale, 1.0f);
if ((!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()) && scale == s_global_scale) 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 #ifdef _WIN32
"DInput", "DInput",
"XInput", "XInput",
#endif
#ifndef __ANDROID__
"SDL",
"RawInput", "RawInput",
#else #endif
#ifdef ENABLE_SDL
"SDL",
#endif
#ifdef __ANDROID__
"Android", "Android",
#endif #endif
}}; }};
@ -640,14 +641,17 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
case InputSourceType::XInput: case InputSourceType::XInput:
return false; return false;
#endif
#ifndef __ANDROID__
case InputSourceType::SDL:
return true;
case InputSourceType::RawInput: case InputSourceType::RawInput:
return false; return false;
#else #endif
#ifdef ENABLE_SDL
case InputSourceType::SDL:
return true;
#endif
#ifdef __ANDROID__
case InputSourceType::Android: case InputSourceType::Android:
return true; return true;
#endif #endif
@ -1229,7 +1233,7 @@ void InputManager::UpdatePointerCount()
return; return;
} }
#ifndef __ANDROID__ #ifdef _WIN32
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput); InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
DebugAssert(ris); 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::XInput, &InputSource::CreateXInputSource);
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource); UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
#endif #endif
#ifndef __ANDROID__ #ifdef ENABLE_SDL
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource); UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
#else #endif
#ifdef __ANDROID__
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource); UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
#endif #endif

View File

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

View File

@ -184,6 +184,23 @@ private:
MetalStreamBuffer m_buffer; 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 class MetalDevice final : public GPUDevice
{ {
friend MetalTexture; friend MetalTexture;
@ -198,16 +215,16 @@ public:
MetalDevice(); MetalDevice();
~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; 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, std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override; const void* data = nullptr, u32 data_stride = 0) override;
@ -261,11 +278,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 present_time) override;
PresentResult BeginPresent(u32 clear_color) override; void SubmitPresent(GPUSwapChain* swap_chain) override;
void EndPresent(bool explicit_submit, u64 present_time) override;
void SubmitPresent() override;
void WaitForFenceCounter(u64 counter); void WaitForFenceCounter(u64 counter);
@ -285,8 +300,10 @@ public:
static void DeferRelease(u64 fence_counter, id obj); static void DeferRelease(u64 fence_counter, id obj);
protected: protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
FeatureMask disabled_features, Error* error) override; GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override; void DestroyDevice() override;
bool OpenPipelineCache(const std::string& path, Error* error) override; bool OpenPipelineCache(const std::string& path, Error* error) override;
bool CreatePipelineCache(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); static_assert(sizeof(ClearPipelineConfig) == 8);
ALWAYS_INLINE NSView* GetWindowView() const { return (__bridge NSView*)m_window_info.window_handle; }
void SetFeatures(FeatureMask disabled_features); void SetFeatures(FeatureMask disabled_features);
bool LoadShaders(); bool LoadShaders();
@ -340,15 +355,12 @@ private:
void EndInlineUploading(); void EndInlineUploading();
void EndAnyEncoding(); void EndAnyEncoding();
GSVector4i ClampToFramebufferSize(const GSVector4i rc) const;
void PreDrawCheck(); void PreDrawCheck();
void SetInitialEncoderState(); void SetInitialEncoderState();
void SetViewportInRenderEncoder(); void SetViewportInRenderEncoder();
void SetScissorInRenderEncoder(); void SetScissorInRenderEncoder();
bool CreateLayer(); void RenderBlankFrame(MetalSwapChain* swap_chain);
void DestroyLayer();
void RenderBlankFrame();
bool CreateBuffers(); bool CreateBuffers();
void DestroyBuffers(); void DestroyBuffers();
@ -358,10 +370,6 @@ private:
id<MTLDevice> m_device; id<MTLDevice> m_device;
id<MTLCommandQueue> m_queue; 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; std::mutex m_fence_mutex;
u64 m_current_fence_counter = 0; u64 m_current_fence_counter = 0;
std::atomic<u64> m_completed_fence_counter{0}; std::atomic<u64> m_completed_fence_counter{0};
@ -402,10 +410,11 @@ private:
id<MTLBuffer> m_current_ssbo = nil; id<MTLBuffer> m_current_ssbo = nil;
GSVector4i m_current_viewport = {}; GSVector4i m_current_viewport = {};
GSVector4i m_current_scissor = {}; GSVector4i m_current_scissor = {};
GSVector4i m_current_framebuffer_size = {};
bool m_vsync_enabled = false;
bool m_pipeline_cache_modified = false;
double m_accumulated_gpu_time = 0; double m_accumulated_gpu_time = 0;
double m_last_gpu_time_end = 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 <mach/mach_time.h>
#include <pthread.h> #include <pthread.h>
LOG_CHANNEL(MetalDevice); LOG_CHANNEL(GPUDevice);
// TODO: Disable hazard tracking and issue barriers explicitly. // 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) MetalDevice::MetalDevice() : m_current_viewport(0, 0, 1, 1), m_current_scissor(0, 0, 1, 1)
{ {
m_render_api = RenderAPI::Metal;
} }
MetalDevice::~MetalDevice() 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. // Metal does not support mailbox mode.
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle; m_allow_present_throttle = allow_present_throttle;
if (m_vsync_mode == mode) if (m_vsync_mode == mode)
return; return true;
m_vsync_mode = mode; m_vsync_mode = mode;
if (m_layer != nil) if (m_layer != nil)
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO]; [m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
return true;
} }
bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, std::unique_ptr<GPUSwapChain> MetalDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
FeatureMask disabled_features, Error* error) 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 @autoreleasepool
{ {
@ -199,15 +325,20 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]); INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]);
SetFeatures(disabled_features); SetFeatures(disabled_features);
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
{
Error::SetStringView(error, "Failed to create layer.");
return false;
}
CreateCommandBuffer(); 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()) if (!LoadShaders())
{ {
@ -228,7 +359,6 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
void MetalDevice::SetFeatures(FeatureMask disabled_features) void MetalDevice::SetFeatures(FeatureMask disabled_features)
{ {
// Set version to Metal 2.3, that's all we're using. Use SPIRV-Cross version encoding. // 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_render_api_version = 20300;
m_max_texture_size = GetMetalMaxTextureSize(m_device); m_max_texture_size = GetMetalMaxTextureSize(m_device);
m_max_multisamples = GetMetalMaxMultisamples(m_device); m_max_multisamples = GetMetalMaxMultisamples(m_device);
@ -416,6 +546,12 @@ void MetalDevice::DestroyDevice()
m_render_cmdbuf = nil; 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(); DestroyBuffers();
for (auto& it : m_cleanup_objects) 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 std::string MetalDevice::GetDriverInfo() const
{ {
@autoreleasepool @autoreleasepool
@ -2082,6 +2089,8 @@ void MetalDevice::BeginRenderPass()
// Rendering to view, but we got interrupted... // Rendering to view, but we got interrupted...
desc.colorAttachments[0].texture = [m_layer_drawable texture]; desc.colorAttachments[0].texture = [m_layer_drawable texture];
desc.colorAttachments[0].loadAction = MTLLoadActionLoad; desc.colorAttachments[0].loadAction = MTLLoadActionLoad;
desc.renderTargetWidth = m_current_framebuffer_size.width();
desc.renderTargetHeight = m_current_framebuffer_size.height();
} }
else else
{ {
@ -2157,6 +2166,10 @@ void MetalDevice::BeginRenderPass()
break; 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]; m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain];
@ -2218,7 +2231,7 @@ void MetalDevice::SetInitialEncoderState()
void MetalDevice::SetViewportInRenderEncoder() void MetalDevice::SetViewportInRenderEncoder()
{ {
const GSVector4i rc = ClampToFramebufferSize(m_current_viewport); const GSVector4i rc = m_current_viewport.rintersect(m_current_framebuffer_size);
[m_render_encoder [m_render_encoder
setViewport:(MTLViewport){static_cast<double>(rc.left), static_cast<double>(rc.top), 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}]; static_cast<double>(rc.width()), static_cast<double>(rc.height()), 0.0, 1.0}];
@ -2226,21 +2239,12 @@ void MetalDevice::SetViewportInRenderEncoder()
void MetalDevice::SetScissorInRenderEncoder() void MetalDevice::SetScissorInRenderEncoder()
{ {
const GSVector4i rc = ClampToFramebufferSize(m_current_scissor); const GSVector4i rc = m_current_scissor.rintersect(m_current_framebuffer_size);
[m_render_encoder [m_render_encoder
setScissorRect:(MTLScissorRect){static_cast<NSUInteger>(rc.left), static_cast<NSUInteger>(rc.top), setScissorRect:(MTLScissorRect){static_cast<NSUInteger>(rc.left), static_cast<NSUInteger>(rc.top),
static_cast<NSUInteger>(rc.width()), static_cast<NSUInteger>(rc.height())}]; 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() void MetalDevice::PreDrawCheck()
{ {
if (!InRenderPass()) 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 @autoreleasepool
{ {
if (m_layer == nil)
{
TrimTexturePool();
return PresentResult::SkipPresent;
}
EndAnyEncoding(); EndAnyEncoding();
m_layer_drawable = [[m_layer nextDrawable] retain]; m_layer_drawable = [[static_cast<MetalSwapChain*>(swap_chain)->GetLayer() nextDrawable] retain];
if (m_layer_drawable == nil) if (m_layer_drawable == nil)
{ {
WARNING_LOG("Failed to get drawable from layer.");
SubmitCommandBuffer();
TrimTexturePool(); TrimTexturePool();
return PresentResult::SkipPresent; 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. // Set up rendering to layer.
const GSVector4 clear_color_v = GSVector4::rgba32(clear_color); const GSVector4 clear_color_v = GSVector4::rgba32(clear_color);
id<MTLTexture> layer_texture = [m_layer_drawable texture]; id<MTLTexture> layer_texture = [m_layer_drawable texture];
m_layer_pass_desc.colorAttachments[0].texture = layer_texture; MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor];
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear; desc.colorAttachments[0].texture = layer_texture;
m_layer_pass_desc.colorAttachments[0].clearColor = 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); 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++; s_stats.num_render_passes++;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_num_current_render_targets = 0; 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(!explicit_present);
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); 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(); TrimTexturePool();
} }
void MetalDevice::SubmitPresent() void MetalDevice::SubmitPresent(GPUSwapChain* swap_chainwel)
{ {
Panic("Not supported by this API."); Panic("Not supported by this API.");
} }
@ -2585,12 +2589,18 @@ void MetalDevice::WaitForPreviousCommandBuffers()
WaitForFenceCounter(m_current_fence_counter - 1); WaitForFenceCounter(m_current_fence_counter - 1);
} }
void MetalDevice::ExecuteAndWaitForGPUIdle() void MetalDevice::WaitForGPUIdle()
{ {
SubmitCommandBuffer(true); SubmitCommandBuffer(true);
CleanupObjects(); CleanupObjects();
} }
void MetalDevice::FlushCommands()
{
SubmitCommandBuffer();
TrimTexturePool();
}
void MetalDevice::CleanupObjects() void MetalDevice::CleanupObjects()
{ {
const u64 counter = m_completed_fence_counter.load(std::memory_order_acquire); 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-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
class Error;
struct WindowInfo; struct WindowInfo;
namespace CocoaTools { namespace CocoaTools {
/// Creates metal layer on specified window surface. /// 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. /// Destroys metal layer on specified window surface.
void DestroyMetalLayer(WindowInfo* wi); void DestroyMetalLayer(const WindowInfo& wi, void* layer);
} // namespace CocoaTools } // namespace CocoaTools

View File

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

View File

@ -32,7 +32,7 @@
#endif #endif
#endif #endif
LOG_CHANNEL(OpenGLContext); LOG_CHANNEL(GPUDevice);
static bool ShouldPreferESContext() 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; 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}, static constexpr std::array<Version, 14> vlist = {{{Profile::Core, 4, 6},
{Profile::Core, 4, 5}, {Profile::Core, 4, 5},
@ -149,22 +147,22 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error
std::unique_ptr<OpenGLContext> context; std::unique_ptr<OpenGLContext> context;
#if defined(_WIN32) && !defined(_M_ARM64) #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__) #elif defined(__APPLE__)
context = OpenGLContextAGL::Create(wi, versions_to_try, error); context = OpenGLContextAGL::Create(wi, surface, versions_to_try, error);
#elif defined(__ANDROID__) #elif defined(__ANDROID__)
context = OpenGLContextEGLAndroid::Create(wi, versions_to_try, error); context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error);
#else #else
#if defined(ENABLE_X11) #if defined(ENABLE_X11)
if (wi.type == WindowInfo::Type::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 #endif
#if defined(ENABLE_WAYLAND) #if defined(ENABLE_WAYLAND)
if (wi.type == WindowInfo::Type::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 #endif
if (wi.type == WindowInfo::Type::Surfaceless) if (wi.type == WindowInfo::Type::Surfaceless)
context = OpenGLContextEGL::Create(wi, versions_to_try, error); context = OpenGLContextEGL::Create(wi, surface, versions_to_try, error);
#endif #endif
if (!context) if (!context)

View File

@ -15,9 +15,12 @@ class Error;
class OpenGLContext class OpenGLContext
{ {
public: public:
OpenGLContext(const WindowInfo& wi); OpenGLContext();
virtual ~OpenGLContext(); virtual ~OpenGLContext();
using SurfaceHandle = void*;
static constexpr SurfaceHandle MAIN_SURFACE = nullptr;
enum class Profile enum class Profile
{ {
NoProfile, NoProfile,
@ -32,26 +35,22 @@ public:
int minor_version; 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 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 void* GetProcAddress(const char* name) = 0;
virtual bool ChangeSurface(const WindowInfo& new_wi) = 0; virtual SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) = 0;
virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0; virtual void DestroySurface(SurfaceHandle handle) = 0;
virtual void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) = 0;
virtual bool SwapBuffers() = 0; virtual bool SwapBuffers() = 0;
virtual bool IsCurrent() const = 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 DoneCurrent() = 0;
virtual bool SupportsNegativeSwapInterval() const = 0; virtual bool SupportsNegativeSwapInterval() const = 0;
virtual bool SetSwapInterval(s32 interval) = 0; virtual bool SetSwapInterval(s32 interval, Error* error = nullptr) = 0;
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) = 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: protected:
WindowInfo m_wi;
Version m_version = {}; Version m_version = {};
}; };

View File

@ -18,32 +18,30 @@ struct NSView;
class OpenGLContextAGL final : public OpenGLContext class OpenGLContextAGL final : public OpenGLContext
{ {
public: public:
OpenGLContextAGL(const WindowInfo& wi); OpenGLContextAGL();
~OpenGLContextAGL() override; ~OpenGLContextAGL() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try, static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
Error* error); std::span<const Version> versions_to_try, Error* error);
void* GetProcAddress(const char* name) override; void* GetProcAddress(const char* name) override;
bool ChangeSurface(const WindowInfo& new_wi) override; SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; void DestroySurface(SurfaceHandle handle) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
bool SwapBuffers() override; bool SwapBuffers() override;
bool IsCurrent() const override; bool IsCurrent() const override;
bool MakeCurrent() override; bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
bool DoneCurrent() override; bool DoneCurrent() override;
bool SupportsNegativeSwapInterval() const override; bool SupportsNegativeSwapInterval() const override;
bool SetSwapInterval(s32 interval) override; bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override; std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
private: private:
ALWAYS_INLINE NSView* GetView() const { return static_cast<NSView*>((__bridge NSView*)m_wi.window_handle); } bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try, Error* error);
bool CreateContext(NSOpenGLContext* share_context, int profile, Error* error);
bool Initialize(std::span<const Version> versions_to_try, Error* error); static void BindContextToView(WindowInfo& wi, NSOpenGLContext* context);
bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current, Error* error); static void UpdateSurfaceSize(WindowInfo& wi, NSOpenGLContext* context);
void BindContextToView();
// returns true if dimensions have changed
bool UpdateDimensions();
NSOpenGLContext* m_context = nullptr; NSOpenGLContext* m_context = nullptr;
NSOpenGLPixelFormat* m_pixel_format = nullptr; NSOpenGLPixelFormat* m_pixel_format = nullptr;

View File

@ -9,9 +9,9 @@
#include <dlfcn.h> #include <dlfcn.h>
LOG_CHANNEL(OpenGLContext); LOG_CHANNEL(GPUDevice);
OpenGLContextAGL::OpenGLContextAGL(const WindowInfo& wi) : OpenGLContext(wi) OpenGLContextAGL::OpenGLContextAGL() : OpenGLContext()
{ {
m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW); m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW);
if (!m_opengl_module_handle) if (!m_opengl_module_handle)
@ -33,24 +33,28 @@ OpenGLContextAGL::~OpenGLContextAGL()
dlclose(m_opengl_module_handle); dlclose(m_opengl_module_handle);
} }
std::unique_ptr<OpenGLContext> OpenGLContextAGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try, Error* error) std::unique_ptr<OpenGLContext> OpenGLContextAGL::Create(WindowInfo& wi, SurfaceHandle* surface,
std::span<const Version> versions_to_try, Error* error)
{ {
std::unique_ptr<OpenGLContextAGL> context = std::make_unique<OpenGLContextAGL>(wi); std::unique_ptr<OpenGLContextAGL> context = std::make_unique<OpenGLContextAGL>();
if (!context->Initialize(versions_to_try, error)) if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr; return nullptr;
return context; return context;
} }
bool OpenGLContextAGL::Initialize(const std::span<const Version> versions_to_try, Error* error) bool OpenGLContextAGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try,
Error* error)
{ {
for (const Version& cv : versions_to_try) for (const Version& cv : versions_to_try)
{ {
if (cv.profile == Profile::NoProfile && CreateContext(nullptr, NSOpenGLProfileVersionLegacy, true, error)) if (cv.profile == Profile::NoProfile && CreateContext(nullptr, NSOpenGLProfileVersionLegacy, error))
{ {
// we already have the dummy context, so just use that // we already have the dummy context, so just use that
BindContextToView(wi, m_context);
*surface = wi.window_handle;
m_version = cv; m_version = cv;
return true; return MakeCurrent(*surface, error);
} }
else if (cv.profile == Profile::Core) else if (cv.profile == Profile::Core)
{ {
@ -59,10 +63,12 @@ bool OpenGLContextAGL::Initialize(const std::span<const Version> versions_to_try
const NSOpenGLPixelFormatAttribute profile = const NSOpenGLPixelFormatAttribute profile =
(cv.major_version > 3 || cv.minor_version > 2) ? NSOpenGLProfileVersion4_1Core : NSOpenGLProfileVersion3_2Core; (cv.major_version > 3 || cv.minor_version > 2) ? NSOpenGLProfileVersion4_1Core : NSOpenGLProfileVersion3_2Core;
if (CreateContext(nullptr, static_cast<int>(profile), true, error)) if (CreateContext(nullptr, static_cast<int>(profile), error))
{ {
BindContextToView(wi, m_context);
*surface = wi.window_handle;
m_version = cv; m_version = cv;
return true; return MakeCurrent(*surface, error);
} }
} }
} }
@ -80,41 +86,31 @@ void* OpenGLContextAGL::GetProcAddress(const char* name)
return dlsym(RTLD_NEXT, name); return dlsym(RTLD_NEXT, name);
} }
bool OpenGLContextAGL::ChangeSurface(const WindowInfo& new_wi) OpenGLContext::SurfaceHandle OpenGLContextAGL::CreateSurface(WindowInfo& wi, Error* error)
{ {
m_wi = new_wi; if (m_context.view != nil)
BindContextToView(); {
return true; Error::SetStringView(error, "Multiple windows are not supported on this backend.");
return nullptr;
}
BindContextToView(wi, m_context);
return wi.window_handle;
} }
void OpenGLContextAGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) void OpenGLContextAGL::DestroySurface(SurfaceHandle handle)
{ {
UpdateDimensions(); if (!handle)
return;
DebugAssert(m_context.view == handle);
[m_context setView:nil];
} }
bool OpenGLContextAGL::UpdateDimensions() void OpenGLContextAGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
{ {
const NSSize window_size = [GetView() frame].size; DebugAssert(m_context.view == handle);
const CGFloat window_scale = [[GetView() window] backingScaleFactor]; UpdateSurfaceSize(wi, m_context);
const u32 new_width = static_cast<u32>(static_cast<CGFloat>(window_size.width) * window_scale);
const u32 new_height = static_cast<u32>(static_cast<CGFloat>(window_size.height) * window_scale);
if (m_wi.surface_width == new_width && m_wi.surface_height == new_height)
return false;
m_wi.surface_width = new_width;
m_wi.surface_height = new_height;
dispatch_block_t block = ^{
[m_context update];
};
if ([NSThread isMainThread])
block();
else
dispatch_sync(dispatch_get_main_queue(), block);
return true;
} }
bool OpenGLContextAGL::SwapBuffers() bool OpenGLContextAGL::SwapBuffers()
@ -128,8 +124,9 @@ bool OpenGLContextAGL::IsCurrent() const
return (m_context != nil && [NSOpenGLContext currentContext] == m_context); return (m_context != nil && [NSOpenGLContext currentContext] == m_context);
} }
bool OpenGLContextAGL::MakeCurrent() bool OpenGLContextAGL::MakeCurrent(SurfaceHandle surface, Error* error)
{ {
DebugAssert(surface == m_context.view);
[m_context makeCurrentContext]; [m_context makeCurrentContext];
return true; return true;
} }
@ -145,35 +142,21 @@ bool OpenGLContextAGL::SupportsNegativeSwapInterval() const
return false; return false;
} }
bool OpenGLContextAGL::SetSwapInterval(s32 interval) bool OpenGLContextAGL::SetSwapInterval(s32 interval, Error* error)
{ {
GLint gl_interval = static_cast<GLint>(interval); GLint gl_interval = static_cast<GLint>(interval);
[m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval]; [m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval];
return true; return true;
} }
std::unique_ptr<OpenGLContext> OpenGLContextAGL::CreateSharedContext(const WindowInfo& wi, Error* error) std::unique_ptr<OpenGLContext> OpenGLContextAGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* handle,
Error* error)
{ {
std::unique_ptr<OpenGLContextAGL> context = std::make_unique<OpenGLContextAGL>(wi); Error::SetStringView(error, "Not supported on this backend.");
return {};
context->m_context = [[NSOpenGLContext alloc] initWithFormat:m_pixel_format shareContext:m_context];
if (context->m_context == nil)
{
Error::SetStringView(error, "NSOpenGLContext initWithFormat failed");
return nullptr;
}
context->m_version = m_version;
context->m_pixel_format = m_pixel_format;
[context->m_pixel_format retain];
if (wi.type == WindowInfo::Type::MacOS)
context->BindContextToView();
return context;
} }
bool OpenGLContextAGL::CreateContext(NSOpenGLContext* share_context, int profile, bool make_current, Error* error) bool OpenGLContextAGL::CreateContext(NSOpenGLContext* share_context, int profile, Error* error)
{ {
if (m_context) if (m_context)
{ {
@ -201,26 +184,18 @@ bool OpenGLContextAGL::CreateContext(NSOpenGLContext* share_context, int profile
return false; return false;
} }
if (m_wi.type == WindowInfo::Type::MacOS)
BindContextToView();
if (make_current)
[m_context makeCurrentContext];
return true; return true;
} }
void OpenGLContextAGL::BindContextToView() void OpenGLContextAGL::BindContextToView(WindowInfo& wi, NSOpenGLContext* context)
{ {
NSView* const view = GetView(); NSView* const view = static_cast<NSView*>((__bridge NSView*)wi.window_handle);
NSWindow* const window = [view window]; NSWindow* const window = [view window];
[view setWantsBestResolutionOpenGLSurface:YES]; [view setWantsBestResolutionOpenGLSurface:YES];
UpdateDimensions();
dispatch_block_t block = ^{ dispatch_block_t block = ^{
[window makeFirstResponder:view]; [window makeFirstResponder:view];
[m_context setView:view]; [context setView:view];
[window makeKeyAndOrderFront:nil]; [window makeKeyAndOrderFront:nil];
}; };
@ -228,4 +203,32 @@ void OpenGLContextAGL::BindContextToView()
block(); block();
else else
dispatch_sync(dispatch_get_main_queue(), block); dispatch_sync(dispatch_get_main_queue(), block);
UpdateSurfaceSize(wi, context);
}
void OpenGLContextAGL::UpdateSurfaceSize(WindowInfo& wi, NSOpenGLContext* context)
{
NSView* const view = static_cast<NSView*>((__bridge NSView*)wi.window_handle);
const NSSize window_size = [view frame].size;
const CGFloat window_scale = [[view window] backingScaleFactor];
const u32 new_width = static_cast<u32>(static_cast<CGFloat>(window_size.width) * window_scale);
const u32 new_height = static_cast<u32>(static_cast<CGFloat>(window_size.height) * window_scale);
if (wi.surface_width == new_width && wi.surface_height == new_height)
return;
wi.surface_width = static_cast<u16>(new_width);
wi.surface_height = static_cast<u16>(new_height);
dispatch_block_t block = ^{
[context update];
};
if ([NSThread isMainThread])
block();
else
dispatch_sync(dispatch_get_main_queue(), block);
return true;
} }

View File

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

View File

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

View File

@ -3,91 +3,22 @@
#include "opengl_context_egl_wayland.h" #include "opengl_context_egl_wayland.h"
#include "common/assert.h"
#include "common/error.h" #include "common/error.h"
#include <dlfcn.h> #include <dlfcn.h>
static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1";
OpenGLContextEGLWayland::OpenGLContextEGLWayland(const WindowInfo& wi) : OpenGLContextEGL(wi) OpenGLContextEGLWayland::OpenGLContextEGLWayland() = default;
{
}
OpenGLContextEGLWayland::~OpenGLContextEGLWayland() OpenGLContextEGLWayland::~OpenGLContextEGLWayland()
{ {
if (m_wl_window) AssertMsg(m_wl_window_map.empty(), "WL window map should be empty on destructor.");
m_wl_egl_window_destroy(m_wl_window);
if (m_wl_module) if (m_wl_module)
dlclose(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) bool OpenGLContextEGLWayland::LoadModule(Error* error)
{ {
m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL); m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL);
@ -112,3 +43,78 @@ bool OpenGLContextEGLWayland::LoadModule(Error* error)
return true; 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 class OpenGLContextEGLWayland final : public OpenGLContextEGL
{ {
public: public:
OpenGLContextEGLWayland(const WindowInfo& wi); OpenGLContextEGLWayland();
~OpenGLContextEGLWayland() override; ~OpenGLContextEGLWayland() override;
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try, static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
Error* error); 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;
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
protected: protected:
EGLDisplay GetPlatformDisplay(Error* error) override; EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override; EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
void DestroyPlatformSurface(EGLSurface surface) override;
private: 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; void* m_wl_module = nullptr;
wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height); 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_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); 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" #include "common/error.h"
OpenGLContextEGLX11::OpenGLContextEGLX11(const WindowInfo& wi) : OpenGLContextEGL(wi) OpenGLContextEGLX11::OpenGLContextEGLX11() = default;
{
}
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::span<const Version> versions_to_try, Error* error)
{ {
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(wi); std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>();
if (!context->Initialize(versions_to_try, error)) if (!context->Initialize(wi, surface, versions_to_try, error))
return nullptr; return nullptr;
return context; 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; 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 nullptr;
return context; 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) if (dpy == EGL_NO_DISPLAY)
dpy = GetFallbackDisplay(error); dpy = GetFallbackDisplay(wi.display_connection, error);
return dpy; 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 // This is hideous.. the EXT version requires a pointer to the window, whereas the base
// version requires the window itself, casted to void*... // version requires the window itself, casted to void*...
void* win = wi.window_handle;
EGLSurface surface = TryCreatePlatformSurface(config, &win, error); EGLSurface surface = TryCreatePlatformSurface(config, &win, error);
if (surface == EGL_NO_SURFACE) if (surface == EGL_NO_SURFACE)
surface = CreateFallbackSurface(config, win, error); surface = CreateFallbackSurface(config, wi.window_handle, error);
return surface; return surface;
} }

View File

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

View File

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

View File

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

View File

@ -19,13 +19,16 @@
#include <array> #include <array>
#include <tuple> #include <tuple>
LOG_CHANNEL(OpenGLDevice); LOG_CHANNEL(GPUDevice);
static constexpr const std::array<GLenum, GPUDevice::MAX_RENDER_TARGETS> s_draw_buffers = { 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}}; {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}};
OpenGLDevice::OpenGLDevice() OpenGLDevice::OpenGLDevice()
{ {
// Could change to GLES later.
m_render_api = RenderAPI::OpenGL;
// Something which won't be matched.. // Something which won't be matched..
std::memset(&m_last_rasterization_state, 0xFF, sizeof(m_last_rasterization_state)); std::memset(&m_last_rasterization_state, 0xFF, sizeof(m_last_rasterization_state));
std::memset(&m_last_depth_state, 0xFF, sizeof(m_last_depth_state)); std::memset(&m_last_depth_state, 0xFF, sizeof(m_last_depth_state));
@ -238,19 +241,6 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
#endif #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, static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* userParam) 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; WindowInfo wi_copy(wi);
} OpenGLContext::SurfaceHandle wi_surface;
m_gl_context = OpenGLContext::Create(wi_copy, &wi_surface, error);
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);
if (!m_gl_context) if (!m_gl_context)
{ {
ERROR_LOG("Failed to create any 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; return false;
} }
#if 0
// Is this needed? // Is this needed?
m_window_info = m_gl_context->GetWindowInfo(); 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 = const bool opengl_is_available =
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || ((!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; return false;
} }
SetSwapInterval();
if (HasSurface())
RenderBlankFrame();
if (m_debug_device && GLAD_GL_KHR_debug) if (m_debug_device && GLAD_GL_KHR_debug)
{ {
if (m_gl_context->IsGLES()) if (m_gl_context->IsGLES())
@ -326,6 +314,21 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
glObjectLabel = nullptr; 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)) if (!CheckFeatures(disabled_features))
return false; return false;
@ -532,50 +535,104 @@ void OpenGLDevice::DestroyDevice()
DestroyBuffers(); DestroyBuffers();
m_gl_context->DoneCurrent(); m_gl_context->DoneCurrent();
m_main_swap_chain.reset();
m_gl_context.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)) bool OpenGLSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
return false; {
m_window_info.surface_scale = new_scale;
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
return true;
if (!m_gl_context->ChangeSurface(m_window_info)) m_window_info.surface_width = new_width;
{ m_window_info.surface_height = new_height;
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();
}
OpenGLDevice::GetContext()->ResizeSurface(m_window_info, m_surface_handle);
return true; 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()) // OpenGL does not support Mailbox.
return; mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
m_allow_present_throttle = allow_present_throttle;
m_window_info.surface_scale = new_window_scale; if (m_vsync_mode == mode)
if (m_window_info.surface_width == static_cast<u32>(new_window_width) && return true;
m_window_info.surface_height == static_cast<u32>(new_window_height))
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)); WindowInfo wi_copy(wi);
m_window_info = m_gl_context->GetWindowInfo(); 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 std::string OpenGLDevice::GetDriverInfo() const
@ -588,29 +645,18 @@ std::string OpenGLDevice::GetDriverInfo() const
gl_shading_language_version); 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... // Could be glFinish(), but I'm afraid for mobile drivers...
glFlush(); 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() void OpenGLDevice::RenderBlankFrame()
{ {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
@ -675,16 +721,6 @@ void OpenGLDevice::DestroyFramebuffer(GLuint fbo)
glDeleteFramebuffers(1, &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() bool OpenGLDevice::CreateBuffers()
{ {
if (!(m_vertex_buffer = OpenGLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE)) || if (!(m_vertex_buffer = OpenGLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE)) ||
@ -742,14 +778,9 @@ void OpenGLDevice::DestroyBuffers()
m_vertex_buffer.reset(); 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) m_gl_context->MakeCurrent(static_cast<OpenGLSwapChain*>(swap_chain)->GetSurfaceHandle());
{
glFlush();
TrimTexturePool();
return PresentResult::SkipPresent;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
@ -764,7 +795,8 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr; 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_viewport = window_rc;
m_last_scissor = window_rc; m_last_scissor = window_rc;
UpdateViewport(); UpdateViewport();
@ -772,23 +804,23 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
return PresentResult::OK; 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(!explicit_present && present_time == 0);
DebugAssert(m_current_fbo == 0); DebugAssert(m_current_fbo == 0);
if (m_gpu_timing_enabled) if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
PopTimestampQuery(); PopTimestampQuery();
m_gl_context->SwapBuffers(); m_gl_context->SwapBuffers();
if (m_gpu_timing_enabled) if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
KickTimestampQuery(); KickTimestampQuery();
TrimTexturePool(); TrimTexturePool();
} }
void OpenGLDevice::SubmitPresent() void OpenGLDevice::SubmitPresent(GPUSwapChain* swap_chain)
{ {
Panic("Not supported by this API."); Panic("Not supported by this API.");
} }

View File

@ -36,20 +36,21 @@ public:
return GetInstance().m_texture_stream_buffer.get(); return GetInstance().m_texture_stream_buffer.get();
} }
ALWAYS_INLINE static bool IsGLES() { return GetInstance().m_gl_context->IsGLES(); } 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 void BindUpdateTextureUnit();
static bool ShouldUsePBOsForDownloads(); static bool ShouldUsePBOsForDownloads();
static void SetErrorObject(Error* errptr, std::string_view prefix, GLenum glerr); 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; 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, std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override; 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 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 DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
PresentResult BeginPresent(u32 clear_color) override; void SubmitPresent(GPUSwapChain* swap_chain) override;
void EndPresent(bool explicit_present, u64 present_time) override;
void SubmitPresent() override;
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
@ -131,9 +130,13 @@ public:
void UnbindSampler(GLuint id); void UnbindSampler(GLuint id);
void UnbindPipeline(const OpenGLPipeline* pl); void UnbindPipeline(const OpenGLPipeline* pl);
void RenderBlankFrame();
protected: protected:
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control, bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
FeatureMask disabled_features, Error* error) override; GPUVSyncMode vsync_mode, bool allow_present_throttle,
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
void DestroyDevice() override; void DestroyDevice() override;
bool OpenPipelineCache(const std::string& path, Error* error) override; bool OpenPipelineCache(const std::string& path, Error* error) override;
@ -154,9 +157,6 @@ private:
bool CreateBuffers(); bool CreateBuffers();
void DestroyBuffers(); void DestroyBuffers();
void SetSwapInterval();
void RenderBlankFrame();
s32 IsRenderTargetBound(const GPUTexture* tex) const; s32 IsRenderTargetBound(const GPUTexture* tex) const;
static GLuint CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags); static GLuint CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags);
static void DestroyFramebuffer(GLuint fbo); static void DestroyFramebuffer(GLuint fbo);
@ -230,3 +230,21 @@ private:
bool m_disable_pbo = false; bool m_disable_pbo = false;
bool m_disable_async_download = 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> #include <cerrno>
LOG_CHANNEL(OpenGLDevice); LOG_CHANNEL(GPUDevice);
struct PipelineDiskCacheFooter struct PipelineDiskCacheFooter
{ {

View File

@ -15,7 +15,7 @@
#include <limits> #include <limits>
#include <tuple> #include <tuple>
LOG_CHANNEL(OpenGLDevice); LOG_CHANNEL(GPUDevice);
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems // 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. // to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.
@ -260,8 +260,7 @@ bool OpenGLTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data
const GLenum target = GetGLTarget(); const GLenum target = GetGLTarget();
const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_format, OpenGLDevice::IsGLES()); const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_format, OpenGLDevice::IsGLES());
const u32 pixel_size = GetPixelSize(); const u32 pixel_size = GetPixelSize();
const u32 preferred_pitch = const u32 preferred_pitch = Common::AlignUpPow2(static_cast<u32>(width) * pixel_size, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
Common::AlignUpPow2(static_cast<u32>(width) * pixel_size, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 map_size = preferred_pitch * static_cast<u32>(height); const u32 map_size = preferred_pitch * static_cast<u32>(height);
OpenGLStreamBuffer* sb = OpenGLDevice::GetTextureStreamBuffer(); OpenGLStreamBuffer* sb = OpenGLDevice::GetTextureStreamBuffer();
@ -551,7 +550,8 @@ void OpenGLDevice::CommitRTClearInFB(OpenGLTexture* tex, u32 idx)
case GPUTexture::State::Invalidated: case GPUTexture::State::Invalidated:
{ {
const GLenum attachment = GL_COLOR_ATTACHMENT0 + idx; const GLenum attachment = GL_COLOR_ATTACHMENT0 + idx;
glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &attachment); if (glInvalidateFramebuffer)
glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &attachment);
tex->SetState(GPUTexture::State::Dirty); tex->SetState(GPUTexture::State::Dirty);
} }
break; break;
@ -589,7 +589,8 @@ void OpenGLDevice::CommitDSClearInFB(OpenGLTexture* tex)
case GPUTexture::State::Invalidated: case GPUTexture::State::Invalidated:
{ {
const GLenum attachment = GL_DEPTH_ATTACHMENT; const GLenum attachment = GL_DEPTH_ATTACHMENT;
glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &attachment); if (glInvalidateFramebuffer)
glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &attachment);
tex->SetState(GPUTexture::State::Dirty); tex->SetState(GPUTexture::State::Dirty);
} }
break; break;

View File

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

View File

@ -437,10 +437,10 @@ void PostProcessing::Chain::LoadStages()
DEV_LOG("Loaded {} post-processing stages.", stage_count); DEV_LOG("Loaded {} post-processing stages.", stage_count);
// precompile shaders // 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(), CheckTargets(g_gpu_device->GetMainSwapChain()->GetFormat(), g_gpu_device->GetMainSwapChain()->GetWidth(),
&progress); g_gpu_device->GetMainSwapChain()->GetHeight(), &progress);
} }
// must be down here, because we need to compile first, triggered by CheckTargets() // 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. // TODO: This could use spv, it's probably fastest.
const auto& [cg, cg_language] = CreateRFXCodegen(); const auto& [cg, cg_language] = CreateRFXCodegen();
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(), if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetMainSwapChain()->GetWidth(),
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), cg.get(), std::move(code), only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetMainSwapChain()->GetHeight(), cg.get(),
error)) std::move(code), error))
{ {
return false; 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) if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target)
{ {
// Special case: drawing to final buffer. // 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(); GL_POP();
return pres; return pres;

View File

@ -177,7 +177,8 @@ GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_col
// Assumes final stage has been cleared already. // Assumes final stage has been cleared already.
if (!final_target) 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; return pres;
} }
else else

View File

@ -4,7 +4,7 @@
<ItemDefinitionGroup> <ItemDefinitionGroup>
<ClCompile> <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> <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>%(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> <AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include</AdditionalIncludeDirectories>
@ -14,7 +14,6 @@
<ItemDefinitionGroup> <ItemDefinitionGroup>
<Link> <Link>
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies> <AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>

View File

@ -27,7 +27,7 @@
#include <limits> #include <limits>
#include <mutex> #include <mutex>
LOG_CHANNEL(VulkanDevice); LOG_CHANNEL(GPUDevice);
// TODO: VK_KHR_display. // TODO: VK_KHR_display.
@ -110,6 +110,8 @@ static std::mutex s_instance_mutex;
VulkanDevice::VulkanDevice() VulkanDevice::VulkanDevice()
{ {
m_render_api = RenderAPI::Vulkan;
#ifdef _DEBUG #ifdef _DEBUG
s_debug_scope_depth = 0; s_debug_scope_depth = 0;
#endif #endif
@ -637,14 +639,6 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics; enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
device_info.pEnabledFeatures = &enabled_features; 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 = { VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr, VK_TRUE, VK_FALSE, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr, VK_TRUE, VK_FALSE,
VK_FALSE}; VK_FALSE};
@ -1266,11 +1260,6 @@ void VulkanDevice::WaitForFenceCounter(u64 fence_counter)
WaitForCommandBufferCompletion(index); WaitForCommandBufferCompletion(index);
} }
void VulkanDevice::WaitForGPUIdle()
{
vkDeviceWaitIdle(m_device);
}
float VulkanDevice::GetAndResetAccumulatedGPUTime() float VulkanDevice::GetAndResetAccumulatedGPUTime()
{ {
const float time = m_accumulated_gpu_time; 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. // 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) 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 else
{
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
}
return; return;
} }
@ -1889,13 +1884,11 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
#endif #endif
} }
bool VulkanDevice::HasSurface() const bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
{ const WindowInfo& wi, GPUVSyncMode vsync_mode,
return static_cast<bool>(m_swap_chain); bool allow_present_throttle,
} const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
std::optional<bool> exclusive_fullscreen_control, Error* error)
bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
FeatureMask disabled_features, Error* error)
{ {
std::unique_lock lock(s_instance_mutex); std::unique_lock lock(s_instance_mutex);
bool enable_debug_utils = m_debug_device; bool enable_debug_utils = m_debug_device;
@ -1908,7 +1901,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
return false; 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 (m_instance == VK_NULL_HANDLE)
{ {
if (enable_debug_utils || enable_validation_layer) 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. // Try again without the validation layer.
enable_debug_utils = false; enable_debug_utils = false;
enable_validation_layer = false; enable_validation_layer = false;
m_instance = m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
if (m_instance == VK_NULL_HANDLE) if (m_instance == VK_NULL_HANDLE)
{ {
Error::SetStringView(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?"); 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) if (enable_debug_utils)
EnableDebugUtils(); EnableDebugUtils();
VkSurfaceKHR surface = VK_NULL_HANDLE; std::unique_ptr<VulkanSwapChain> swap_chain;
ScopedGuard surface_cleanup = [this, &surface]() { if (!wi.IsSurfaceless())
if (surface != VK_NULL_HANDLE)
vkDestroySurfaceKHR(m_instance, surface, nullptr);
};
if (m_window_info.type != WindowInfo::Type::Surfaceless)
{ {
surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info); swap_chain =
if (surface == VK_NULL_HANDLE) 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; return false;
} }
// Attempt to create the device. // 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; return false;
}
// And critical resources. // And critical resources.
if (!CreateAllocator() || !CreatePersistentDescriptorPool() || !CreateCommandBuffers() || !CreatePipelineLayouts()) 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; m_exclusive_fullscreen_control = exclusive_fullscreen_control;
if (surface != VK_NULL_HANDLE) if (swap_chain)
{ {
VkPresentModeKHR present_mode; // Render a frame as soon as possible to clear out whatever was previously being displayed.
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) || if (!swap_chain->CreateSwapChain(*this, error) || !swap_chain->CreateSwapChainImages(*this, error))
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
{
Error::SetStringView(error, "Failed to create swap chain");
return false; return false;
}
// NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal). RenderBlankFrame(swap_chain.get());
m_window_info = m_swap_chain->GetWindowInfo(); 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()) if (!CreateNullTexture())
{ {
Error::SetStringView(error, "Failed to create dummy texture"); 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. // Don't both submitting the current command buffer, just toss it.
if (m_device != VK_NULL_HANDLE) 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) if (m_null_texture)
{ {
@ -2208,73 +2195,27 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error
return true; 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(); std::unique_ptr<VulkanSwapChain> swap_chain =
std::make_unique<VulkanSwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control);
if (!AcquireWindow(false)) if (swap_chain->CreateSurface(m_instance, m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) &&
return false; swap_chain->CreateSwapChainImages(*this, error))
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)
{ {
ERROR_LOG("Failed to create new surface for swap chain"); if (InRenderPass())
return false; EndRenderPass();
RenderBlankFrame(swap_chain.get());
}
else
{
swap_chain.reset();
} }
VkPresentModeKHR present_mode; return swap_chain;
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();
} }
bool VulkanDevice::SupportsTextureFormat(GPUTexture::Format format) const bool VulkanDevice::SupportsTextureFormat(GPUTexture::Format format) const
@ -2308,7 +2249,16 @@ std::string VulkanDevice::GetDriverInfo() const
return ret; return ret;
} }
void VulkanDevice::ExecuteAndWaitForGPUIdle() void VulkanDevice::FlushCommands()
{
if (InRenderPass())
EndRenderPass();
SubmitCommandBuffer(false);
TrimTexturePool();
}
void VulkanDevice::WaitForGPUIdle()
{ {
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
@ -2316,39 +2266,7 @@ void VulkanDevice::ExecuteAndWaitForGPUIdle()
SubmitCommandBuffer(true); SubmitCommandBuffer(true);
} }
void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) GPUDevice::PresentResult VulkanDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
{
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)
{ {
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
@ -2356,37 +2274,30 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
if (m_device_was_lost) [[unlikely]] if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost; return PresentResult::DeviceLost;
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors. VulkanSwapChain* const SC = static_cast<VulkanSwapChain*>(swap_chain);
if (!m_swap_chain) VkResult res = SC->AcquireNextImage();
{
SubmitCommandBuffer(false);
TrimTexturePool();
return PresentResult::SkipPresent;
}
VkResult res = m_swap_chain->AcquireNextImage();
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
m_swap_chain->ReleaseCurrentImage(); SC->ReleaseCurrentImage();
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
{ {
ResizeWindow(0, 0, m_window_info.surface_scale); Error error;
res = m_swap_chain->AcquireNextImage(); 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) else if (res == VK_ERROR_SURFACE_LOST_KHR)
{ {
WARNING_LOG("Surface lost, attempting to recreate"); 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. // 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; 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(present_time == 0);
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
EndRenderPass(); EndRenderPass();
DebugAssert(SC == m_current_swap_chain);
m_current_swap_chain = nullptr;
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, m_swap_chain->GetCurrentImage(), GPUTexture::Type::RenderTarget, VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, SC->GetCurrentImage(), GPUTexture::Type::RenderTarget, 0, 1, 0,
0, 1, 0, 1, VulkanTexture::Layout::ColorAttachment, 1, VulkanTexture::Layout::ColorAttachment,
VulkanTexture::Layout::PresentSrc); VulkanTexture::Layout::PresentSrc);
EndAndSubmitCommandBuffer(m_swap_chain.get(), explicit_present); EndAndSubmitCommandBuffer(SC, explicit_present);
MoveToNextCommandBuffer(); MoveToNextCommandBuffer();
InvalidateCachedState(); InvalidateCachedState();
TrimTexturePool(); TrimTexturePool();
} }
void VulkanDevice::SubmitPresent() void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain)
{ {
DebugAssert(m_swap_chain); DebugAssert(swap_chain);
if (m_device_was_lost) [[unlikely]] if (m_device_was_lost) [[unlikely]]
return; return;
QueuePresent(m_swap_chain.get()); QueuePresent(static_cast<VulkanSwapChain*>(swap_chain));
} }
#ifdef _DEBUG #ifdef _DEBUG
@ -2515,7 +2431,6 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP
void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features) 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); 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) + 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)); (VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version));
m_max_texture_size = m_max_texture_size =
@ -3076,9 +2991,9 @@ void VulkanDevice::DestroyPersistentDescriptorSets()
FreePersistentDescriptorSet(m_ubo_descriptor_set); 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) if (res != VK_SUCCESS)
{ {
ERROR_LOG("Failed to acquire image for blank frame present"); ERROR_LOG("Failed to acquire image for blank frame present");
@ -3087,7 +3002,7 @@ void VulkanDevice::RenderBlankFrame()
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); 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 VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
static constexpr VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}}; static constexpr VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}};
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1, 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::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
VulkanTexture::Layout::TransferDst, VulkanTexture::Layout::PresentSrc); VulkanTexture::Layout::TransferDst, VulkanTexture::Layout::PresentSrc);
EndAndSubmitCommandBuffer(m_swap_chain.get(), false); EndAndSubmitCommandBuffer(swap_chain, false);
MoveToNextCommandBuffer(); MoveToNextCommandBuffer();
InvalidateCachedState(); InvalidateCachedState();
@ -3363,7 +3278,7 @@ void VulkanDevice::BeginRenderPass()
VkRenderingAttachmentInfo& ai = attachments[0]; VkRenderingAttachmentInfo& ai = attachments[0];
ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
ai.pNext = nullptr; 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.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR; ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR;
ai.resolveImageView = VK_NULL_HANDLE; ai.resolveImageView = VK_NULL_HANDLE;
@ -3373,7 +3288,7 @@ void VulkanDevice::BeginRenderPass()
ri.colorAttachmentCount = 1; ri.colorAttachmentCount = 1;
ri.pColorAttachments = attachments.data(); 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; m_current_render_pass = DYNAMIC_RENDERING_RENDER_PASS;
@ -3434,10 +3349,10 @@ void VulkanDevice::BeginRenderPass()
else else
{ {
// Re-rendering to swap chain. // Re-rendering to swap chain.
bi.framebuffer = m_swap_chain->GetCurrentFramebuffer(); bi.framebuffer = m_current_swap_chain->GetCurrentFramebuffer();
bi.renderPass = m_current_render_pass = bi.renderPass = m_current_render_pass =
GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_LOAD); GetSwapChainRenderPass(m_current_swap_chain->GetFormat(), VK_ATTACHMENT_LOAD_OP_LOAD);
bi.renderArea.extent = {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}; bi.renderArea.extent = {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()};
} }
DebugAssert(m_current_render_pass); DebugAssert(m_current_render_pass);
@ -3451,12 +3366,12 @@ void VulkanDevice::BeginRenderPass()
SetInitialPipelineState(); SetInitialPipelineState();
} }
void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color)
{ {
DebugAssert(!InRenderPass()); DebugAssert(!InRenderPass());
const VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); 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 // Swap chain images start in undefined
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, swap_chain_image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1, 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, VkRenderingAttachmentInfo ai = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR,
nullptr, nullptr,
m_swap_chain->GetCurrentImageView(), swap_chain->GetCurrentImageView(),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_RESOLVE_MODE_NONE_KHR, VK_RESOLVE_MODE_NONE_KHR,
VK_NULL_HANDLE, VK_NULL_HANDLE,
@ -3489,7 +3404,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
nullptr, nullptr,
0u, 0u,
{{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}, {{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
1u, 1u,
0u, 0u,
1u, 1u,
@ -3503,14 +3418,14 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
else else
{ {
m_current_render_pass = 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); DebugAssert(m_current_render_pass);
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr, nullptr,
m_current_render_pass, m_current_render_pass,
m_swap_chain->GetCurrentFramebuffer(), swap_chain->GetCurrentFramebuffer(),
{{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}, {{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
1u, 1u,
&clear_value}; &clear_value};
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); 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)); std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr; m_current_depth_target = nullptr;
m_current_framebuffer = VK_NULL_HANDLE; m_current_framebuffer = VK_NULL_HANDLE;
m_current_swap_chain = swap_chain;
} }
bool VulkanDevice::InRenderPass() bool VulkanDevice::InRenderPass()

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
#include "vulkan_device.h" #include "vulkan_device.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/error.h"
#include "common/log.h" #include "common/log.h"
#include <algorithm> #include <algorithm>
@ -20,9 +21,7 @@
#include "util/metal_layer.h" #include "util/metal_layer.h"
#endif #endif
LOG_CHANNEL(VulkanDevice); LOG_CHANNEL(GPUDevice);
static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
static VkFormat GetLinearFormat(VkFormat format) 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) std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_present_mode(present_mode), : GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_exclusive_fullscreen_control(exclusive_fullscreen_control)
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{ {
static_assert(NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
} }
VulkanSwapChain::~VulkanSwapChain() VulkanSwapChain::~VulkanSwapChain()
{ {
DestroySwapChainImages(); Destroy(VulkanDevice::GetInstance(), true);
DestroySwapChain();
DestroySurface();
} }
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 defined(VK_USE_PLATFORM_WIN32_KHR)
if (wi->type == WindowInfo::Type::Win32) if (m_window_info.type == WindowInfo::Type::Win32)
{ {
VkWin32SurfaceCreateInfoKHR surface_create_info = { const VkWin32SurfaceCreateInfoKHR surface_create_info = {.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType .hwnd = static_cast<HWND>(m_window_info.window_handle)};
nullptr, // const void* pNext const VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
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);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); Vulkan::SetErrorObject(error, "vkCreateWin32SurfaceKHR() failed: ", res);
return VK_NULL_HANDLE; return false;
} }
return surface; return true;
} }
#endif #endif
#if defined(VK_USE_PLATFORM_METAL_EXT) #if defined(VK_USE_PLATFORM_METAL_EXT)
if (wi->type == WindowInfo::Type::MacOS) if (m_window_info.type == WindowInfo::Type::MacOS)
{ {
// TODO: FIXME m_metal_layer = CocoaTools::CreateMetalLayer(m_window_info, error);
if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi)) if (!m_metal_layer)
return VK_NULL_HANDLE; return false;
VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, const VkMetalSurfaceCreateInfoEXT surface_create_info = {.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT,
static_cast<const CAMetalLayer*>(wi->surface_handle)}; .pLayer = static_cast<const CAMetalLayer*>(m_metal_layer)};
const VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &m_surface);
VkSurfaceKHR surface;
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); Vulkan::SetErrorObject(error, "vkCreateMetalSurfaceEXT failed: ", res);
return VK_NULL_HANDLE; return false;
} }
return surface; return true;
} }
#endif #endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR) #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 = { const VkAndroidSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
nullptr, // const void* pNext .window = static_cast<ANativeWindow*>(m_window_info.window_handle)};
0, // VkAndroidSurfaceCreateFlagsKHR flags const VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
};
VkSurfaceKHR surface;
VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); Vulkan::SetErrorObject(error, "vkCreateAndroidSurfaceKHR failed: ", res);
return VK_NULL_HANDLE; return false;
} }
return surface; return true;
} }
#endif #endif
#if defined(VK_USE_PLATFORM_XLIB_KHR) #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 = { const VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
nullptr, // const void* pNext .dpy = static_cast<Display*>(m_window_info.display_connection),
0, // VkXlibSurfaceCreateFlagsKHR flags .window = reinterpret_cast<Window>(m_window_info.window_handle)};
static_cast<Display*>(wi->display_connection), // Display* dpy const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
reinterpret_cast<Window>(wi->window_handle) // Window window
};
VkSurfaceKHR surface;
VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res);
return VK_NULL_HANDLE; return false;
} }
return surface; return true;
} }
#endif #endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR) #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, const VkWaylandSurfaceCreateInfoKHR surface_create_info = {
static_cast<struct wl_display*>(wi->display_connection), VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<struct wl_surface*>(wi->window_handle)}; static_cast<struct wl_display*>(m_window_info.display_connection),
static_cast<struct wl_surface*>(m_window_info.window_handle)};
VkSurfaceKHR surface; VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: "); Vulkan::SetErrorObject(error, "vkCreateWaylandSurfaceEXT failed: ", res);
return VK_NULL_HANDLE; return false;
} }
return surface; return true;
} }
#endif #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 defined(__APPLE__)
if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle) if (m_metal_layer)
CocoaTools::DestroyMetalLayer(wi); {
CocoaTools::DestroyMetalLayer(m_window_info, m_metal_layer);
m_metal_layer = nullptr;
}
#endif #endif
} }
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error)
VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control)
{ {
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; 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) if (res != VK_SUCCESS || format_count == 0)
{ {
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
return std::nullopt; return std::nullopt;
} }
std::vector<VkSurfaceFormatKHR> surface_formats(format_count); std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
res = res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, surface_formats.data());
vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data());
Assert(res == VK_SUCCESS); Assert(res == VK_SUCCESS);
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA // 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}; 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) 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; 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; VkResult res;
u32 mode_count; 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) if (res != VK_SUCCESS || mode_count == 0)
{ {
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
return false; return std::nullopt;
} }
std::vector<VkPresentModeKHR> present_modes(mode_count); std::vector<VkPresentModeKHR> present_modes(mode_count);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, present_modes.data());
present_modes.data());
Assert(res == VK_SUCCESS); Assert(res == VK_SUCCESS);
// Checks if a particular mode is supported, if it is, returns that mode. // 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(); return it != present_modes.end();
}; };
switch (*vsync_mode) switch (vsync_mode)
{ {
case GPUVSyncMode::Disabled: case GPUVSyncMode::Disabled:
{ {
// Prefer immediate > mailbox > fifo. // Prefer immediate > mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) 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)) else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{ {
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox."); WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR; return VK_PRESENT_MODE_MAILBOX_KHR;
} }
else else
{ {
WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO."); 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; break;
@ -312,7 +283,7 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
case GPUVSyncMode::FIFO: case GPUVSyncMode::FIFO:
{ {
// FIFO is always available. // FIFO is always available.
*present_mode = VK_PRESENT_MODE_FIFO_KHR; return VK_PRESENT_MODE_FIFO_KHR;
} }
break; break;
@ -321,48 +292,50 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
// Mailbox > fifo. // Mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{ {
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR; return VK_PRESENT_MODE_MAILBOX_KHR;
} }
else else
{ {
WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO."); 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; break;
DefaultCaseIsUnreachable() 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 // 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()) if (!surface_format.has_value())
return false; 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 // Look up surface properties to determine image count and dimensions
VkSurfaceCapabilitiesKHR surface_capabilities; VkSurfaceCapabilitiesKHR surface_capabilities;
VkResult res = VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physdev, m_surface, &surface_capabilities);
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ", res);
return false; return false;
} }
// Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode. // 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. // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
u32 image_count = std::clamp<u32>( 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); (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 // 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. // 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; VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage) 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; return false;
} }
@ -406,25 +379,21 @@ bool VulkanSwapChain::CreateSwapChain()
m_swap_chain = VK_NULL_HANDLE; m_swap_chain = VK_NULL_HANDLE;
// Now we can actually create the swap chain // Now we can actually create the swap chain
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, VkSwapchainCreateInfoKHR swap_chain_info = {.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr, .surface = m_surface,
0, .minImageCount = image_count,
m_surface, .imageFormat = surface_format->format,
image_count, .imageColorSpace = surface_format->colorSpace,
surface_format->format, .imageExtent = size,
surface_format->colorSpace, .imageArrayLayers = 1u,
size, .imageUsage = image_usage,
1u, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
image_usage, .preTransform = transform,
VK_SHARING_MODE_EXCLUSIVE, .compositeAlpha = alpha,
0, .presentMode = present_mode.value(),
nullptr, .clipped = VK_TRUE,
transform, .oldSwapchain = old_swap_chain};
alpha, const std::array<u32, 2> queue_indices = {{
m_present_mode,
VK_TRUE,
old_swap_chain};
std::array<uint32_t, 2> indices = {{
dev.GetGraphicsQueueFamilyIndex(), dev.GetGraphicsQueueFamilyIndex(),
dev.GetPresentQueueFamilyIndex(), dev.GetPresentQueueFamilyIndex(),
}}; }};
@ -432,7 +401,7 @@ bool VulkanSwapChain::CreateSwapChain()
{ {
swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swap_chain_info.queueFamilyIndexCount = 2; swap_chain_info.queueFamilyIndexCount = 2;
swap_chain_info.pQueueFamilyIndices = indices.data(); swap_chain_info.pQueueFamilyIndices = queue_indices.data();
} }
#ifdef _WIN32 #ifdef _WIN32
@ -466,81 +435,101 @@ bool VulkanSwapChain::CreateSwapChain()
ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform."); ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
#endif #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) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); Vulkan::SetErrorObject(error, "vkCreateSwapchainKHR failed: ", res);
return false; return false;
} }
// Now destroy the old swap chain, since it's been recreated. // 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. // We can do this immediately since all work should have been completed before calling resize.
if (old_swap_chain != VK_NULL_HANDLE) 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; if (size.width == 0 || size.width > std::numeric_limits<u16>::max() || size.height == 0 ||
m_window_info.surface_width = std::max(1u, size.width); size.height > std::numeric_limits<u16>::max())
m_window_info.surface_height = std::max(1u, size.height); {
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); m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
if (m_window_info.surface_format == GPUTexture::Format::Unknown) 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 false;
} }
return true;
}
bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
{
const VkDevice vkdev = dev.GetVulkanDevice();
// Get and create images. // Get and create images.
Assert(m_images.empty()); 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) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); Vulkan::SetErrorObject(error, "vkGetSwapchainImagesKHR failed: ", res);
return false; return false;
} }
std::vector<VkImage> images(image_count); 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); 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) 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; return false;
}
Vulkan::FramebufferBuilder fbb; Vulkan::FramebufferBuilder fbb;
m_images.reserve(image_count); m_images.reserve(image_count);
m_current_image = 0; m_current_image = 0;
for (u32 i = 0; i < image_count; i++) for (u32 i = 0; i < image_count; i++)
{ {
Image image = {}; Image& image = m_images.emplace_back();
image.image = images[i]; image.image = images[i];
const VkImageViewCreateInfo view_info = { const VkImageViewCreateInfo view_info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
nullptr, .image = images[i],
0, .viewType = VK_IMAGE_VIEW_TYPE_2D,
images[i], .format = VulkanDevice::TEXTURE_FORMAT_MAPPING[static_cast<u8>(m_window_info.surface_format)],
VK_IMAGE_VIEW_TYPE_2D, .components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
m_format, 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},
VK_COMPONENT_SWIZZLE_IDENTITY},
{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; return false;
} }
fbb.AddAttachment(image.view); fbb.AddAttachment(image.view);
fbb.SetRenderPass(render_pass); fbb.SetRenderPass(render_pass);
fbb.SetSize(size.width, size.height, 1); fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1);
if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE) 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; return false;
} }
m_images.push_back(image);
} }
m_current_semaphore = 0; m_current_semaphore = 0;
@ -549,18 +538,18 @@ bool VulkanSwapChain::CreateSwapChain()
ImageSemaphores& sema = m_semaphores[i]; ImageSemaphores& sema = m_semaphores[i];
const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0}; 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) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
return false; 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) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr); vkDestroySemaphore(vkdev, sema.available_semaphore, nullptr);
sema.available_semaphore = VK_NULL_HANDLE; sema.available_semaphore = VK_NULL_HANDLE;
return false; return false;
} }
@ -569,22 +558,39 @@ bool VulkanSwapChain::CreateSwapChain()
return true; 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() void VulkanSwapChain::DestroySwapChainImages()
{ {
VulkanDevice& dev = VulkanDevice::GetInstance(); const VkDevice vkdev = VulkanDevice::GetInstance().GetVulkanDevice();
for (const auto& it : m_images) for (const auto& it : m_images)
{ {
// don't defer view destruction, images are no longer valid // don't defer view destruction, images are no longer valid
vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr); vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr);
vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr); vkDestroyImageView(vkdev, it.view, nullptr);
} }
m_images.clear(); m_images.clear();
for (auto& it : m_semaphores) for (const auto& it : m_semaphores)
{ {
if (it.rendering_finished_semaphore != VK_NULL_HANDLE) 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) if (it.available_semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr); vkDestroySemaphore(vkdev, it.available_semaphore, nullptr);
} }
m_semaphores = {}; m_semaphores = {};
@ -595,13 +601,11 @@ void VulkanSwapChain::DestroySwapChain()
{ {
DestroySwapChainImages(); DestroySwapChainImages();
if (m_swap_chain == VK_NULL_HANDLE) if (m_swap_chain != VK_NULL_HANDLE)
return; {
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr); m_swap_chain = VK_NULL_HANDLE;
m_swap_chain = VK_NULL_HANDLE; }
m_window_info.surface_width = 0;
m_window_info.surface_height = 0;
} }
VkResult VulkanSwapChain::AcquireNextImage() VkResult VulkanSwapChain::AcquireNextImage()
@ -649,20 +653,27 @@ void VulkanSwapChain::ResetImageAcquireResult()
m_image_acquire_result.reset(); 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(); ReleaseCurrentImage();
DestroySwapChainImages(); DestroySwapChainImages();
if (new_width != 0 && new_height != 0) if (new_width != 0 && new_height != 0)
{ {
m_window_info.surface_width = new_width; m_window_info.surface_width = static_cast<u16>(new_width);
m_window_info.surface_height = new_height; m_window_info.surface_height = static_cast<u16>(new_height);
} }
m_window_info.surface_scale = new_scale; if (!CreateSwapChain(VulkanDevice::GetInstance(), error) || !CreateSwapChainImages(dev, error))
if (!CreateSwapChain())
{ {
DestroySwapChain(); DestroySwapChain();
return false; return false;
@ -671,18 +682,31 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
return true; 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; 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. // Recreate the swap chain with the new present mode.
VERBOSE_LOG("Recreating swap chain to change present mode."); VERBOSE_LOG("Recreating swap chain to change present mode.");
ReleaseCurrentImage(); ReleaseCurrentImage();
DestroySwapChainImages(); DestroySwapChainImages();
if (!CreateSwapChain()) if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
{ {
DestroySwapChain(); DestroySwapChain();
return false; return false;
@ -691,18 +715,19 @@ bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
return true; return true;
} }
bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi) bool VulkanSwapChain::RecreateSurface(Error* error)
{ {
VulkanDevice& dev = VulkanDevice::GetInstance(); VulkanDevice& dev = VulkanDevice::GetInstance();
if (dev.InRenderPass())
dev.EndRenderPass();
dev.SubmitCommandBuffer(true);
// Destroy the old swap chain, images, and surface. // Destroy the old swap chain, images, and surface.
DestroySwapChain(); DestroySwapChain();
DestroySurface(); DestroySurface();
// Re-create the surface with the new native handle // Re-create the surface with the new native handle
m_window_info = new_wi; if (!CreateSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), error))
m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info);
if (m_surface == VK_NULL_HANDLE)
return false; return false;
// The validation layers get angry at us if we don't call this before creating the swapchain. // 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); m_surface, &present_supported);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ", res);
return false;
}
if (!present_supported)
{
Panic("Recreated surface does not support presenting.");
return false; return false;
} }
AssertMsg(present_supported, "Recreated surface does not support presenting.");
// Finally re-create the swap chain // Finally re-create the swap chain
if (!CreateSwapChain()) if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
{ {
DestroySwapChain(); DestroySwapChain();
return false; return false;
@ -729,12 +750,3 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
return true; 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 #pragma once
#include "gpu_device.h"
#include "vulkan_loader.h" #include "vulkan_loader.h"
#include "vulkan_texture.h" #include "vulkan_texture.h"
#include "window_info.h" #include "window_info.h"
@ -14,41 +15,19 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
class VulkanSwapChain class VulkanSwapChain final : public GPUSwapChain
{ {
public: public:
// We don't actually need +1 semaphores, or, more than one really. VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
// But, the validation layer gets cranky if we don't fence wait before the next image acquire. std::optional<bool> exclusive_fullscreen_control);
// So, add an additional semaphore to ensure that we're never acquiring before fence waiting. ~VulkanSwapChain() override;
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);
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() 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 u32 GetCurrentImageIndex() const { return m_current_image; }
ALWAYS_INLINE const u32* GetCurrentImageIndexPtr() 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 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 VkImage GetCurrentImage() const { return m_images[m_current_image].image; }
ALWAYS_INLINE VkImageView GetCurrentImageView() const { return m_images[m_current_image].view; } ALWAYS_INLINE VkImageView GetCurrentImageView() const { return m_images[m_current_image].view; }
ALWAYS_INLINE VkFramebuffer GetCurrentFramebuffer() const { return m_images[m_current_image].framebuffer; } 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; 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(); VkResult AcquireNextImage();
void ReleaseCurrentImage(); void ReleaseCurrentImage();
void ResetImageAcquireResult(); void ResetImageAcquireResult();
bool RecreateSurface(const WindowInfo& new_wi); bool RecreateSurface(Error* error);
bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f);
// Change vsync enabled state. This may fail as it causes a swapchain recreation. bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
bool SetPresentMode(VkPresentModeKHR present_mode); bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
private: private:
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, // We don't actually need +1 semaphores, or, more than one really.
std::optional<bool> exclusive_fullscreen_control); // 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); std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error);
std::optional<VkPresentModeKHR> SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode, Error* error);
bool CreateSwapChain();
void DestroySwapChain();
void DestroySwapChainImages(); void DestroySwapChainImages();
void DestroySwapChain();
void DestroySurface(); void DestroySurface();
struct Image struct Image
@ -105,9 +88,13 @@ private:
VkSemaphore rendering_finished_semaphore; VkSemaphore rendering_finished_semaphore;
}; };
WindowInfo m_window_info;
VkSurfaceKHR m_surface = VK_NULL_HANDLE; 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; VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
std::vector<Image> m_images; std::vector<Image> m_images;
@ -116,7 +103,6 @@ private:
u32 m_current_image = 0; u32 m_current_image = 0;
u32 m_current_semaphore = 0; u32 m_current_semaphore = 0;
VkFormat m_format = VK_FORMAT_UNDEFINED;
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
std::optional<VkResult> m_image_acquire_result; std::optional<VkResult> m_image_acquire_result;

View File

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

View File

@ -11,21 +11,6 @@
LOG_CHANNEL(WindowInfo); 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) #if defined(_WIN32)
#include "common/windows_headers.h" #include "common/windows_headers.h"
@ -318,4 +303,4 @@ std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
return std::nullopt; return std::nullopt;
} }
#endif #endif

View File

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