mirror of
https://github.com/stenzek/duckstation.git
synced 2024-11-23 05:49:43 +00:00
GPUDevice: Extract swap chain to separate class
This commit is contained in:
parent
c6055affbf
commit
eb46142ee7
@ -4341,10 +4341,11 @@ void FullscreenUI::DrawDisplaySettingsPage()
|
||||
options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty());
|
||||
if (selected_adapter)
|
||||
{
|
||||
for (const std::string& mode : selected_adapter->fullscreen_modes)
|
||||
for (const GPUDevice::ExclusiveFullscreenMode& mode : selected_adapter->fullscreen_modes)
|
||||
{
|
||||
const bool checked = (strvalue.has_value() && strvalue.value() == mode);
|
||||
options.emplace_back(mode, checked);
|
||||
const TinyString mode_str = mode.ToString();
|
||||
const bool checked = (strvalue.has_value() && strvalue.value() == mode_str);
|
||||
options.emplace_back(std::string(mode_str.view()), checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1145,8 +1145,16 @@ void GPU::UpdateCommandTickEvent()
|
||||
void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
|
||||
float* display_y) const
|
||||
{
|
||||
if (!g_gpu_device->HasMainSwapChain()) [[unlikely]]
|
||||
{
|
||||
*display_x = 0.0f;
|
||||
*display_y = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
GSVector4i display_rc, draw_rc;
|
||||
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rc, &draw_rc);
|
||||
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(), true,
|
||||
true, &display_rc, &draw_rc);
|
||||
|
||||
// convert coordinates to active display region, then to full display region
|
||||
const float scaled_display_x =
|
||||
@ -1644,7 +1652,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm
|
||||
if (display)
|
||||
{
|
||||
plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants;
|
||||
plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8);
|
||||
plconfig.SetTargetFormats(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() :
|
||||
GPUTexture::Format::RGBA8);
|
||||
|
||||
std::string vs = shadergen.GenerateDisplayVertexShader();
|
||||
std::string fs;
|
||||
@ -1839,10 +1848,13 @@ GPUDevice::PresentResult GPU::PresentDisplay()
|
||||
{
|
||||
FlushRender();
|
||||
|
||||
if (!g_gpu_device->HasMainSwapChain())
|
||||
return GPUDevice::PresentResult::SkipPresent;
|
||||
|
||||
GSVector4i display_rect;
|
||||
GSVector4i draw_rect;
|
||||
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), !g_settings.debugging.show_vram,
|
||||
true, &display_rect, &draw_rect);
|
||||
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
|
||||
!g_settings.debugging.show_vram, true, &display_rect, &draw_rect);
|
||||
return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram);
|
||||
}
|
||||
|
||||
@ -1887,13 +1899,12 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
||||
}
|
||||
}
|
||||
|
||||
const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetWindowFormat();
|
||||
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth();
|
||||
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight();
|
||||
const bool really_postfx =
|
||||
(postfx && PostProcessing::DisplayChain.IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() &&
|
||||
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
|
||||
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
|
||||
const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetMainSwapChain()->GetFormat();
|
||||
const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetMainSwapChain()->GetWidth();
|
||||
const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetMainSwapChain()->GetHeight();
|
||||
const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
|
||||
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
|
||||
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
|
||||
const GSVector4i real_draw_rect =
|
||||
g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect;
|
||||
if (really_postfx)
|
||||
@ -1904,9 +1915,15 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
||||
else
|
||||
{
|
||||
if (target)
|
||||
{
|
||||
g_gpu_device->SetRenderTarget(target);
|
||||
else if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
|
||||
return pres;
|
||||
}
|
||||
else
|
||||
{
|
||||
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
|
||||
if (pres != GPUDevice::PresentResult::OK)
|
||||
return pres;
|
||||
}
|
||||
}
|
||||
|
||||
if (display_texture)
|
||||
@ -2559,7 +2576,7 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
|
||||
GPUTexture::Format* out_format)
|
||||
{
|
||||
const GPUTexture::Format hdformat =
|
||||
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
|
||||
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8;
|
||||
|
||||
auto render_texture =
|
||||
g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat);
|
||||
@ -2605,8 +2622,8 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
|
||||
void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
|
||||
GSVector4i* draw_rect) const
|
||||
{
|
||||
*width = g_gpu_device->GetWindowWidth();
|
||||
*height = g_gpu_device->GetWindowHeight();
|
||||
*width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1;
|
||||
*height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1;
|
||||
CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect);
|
||||
|
||||
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram);
|
||||
|
@ -525,7 +525,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
|
||||
// When using very high upscaling, it's possible that we don't have enough VRAM for two sets of buffers.
|
||||
// Purge the pool, and idle the GPU so that all video memory is freed prior to creating the new buffers.
|
||||
g_gpu_device->PurgeTexturePool();
|
||||
g_gpu_device->ExecuteAndWaitForGPUIdle();
|
||||
g_gpu_device->WaitForGPUIdle();
|
||||
|
||||
if (!CreateBuffers())
|
||||
Panic("Failed to recreate buffers.");
|
||||
@ -680,7 +680,7 @@ u32 GPU_HW::CalculateResolutionScale() const
|
||||
{
|
||||
// Auto scaling.
|
||||
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0 || m_crtc_state.display_vram_width == 0 ||
|
||||
m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable)
|
||||
m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable || !g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
// When the system is starting and all borders crop is enabled, the registers are zero, and
|
||||
// display_height therefore is also zero. Keep the existing resolution until it updates.
|
||||
@ -689,8 +689,8 @@ u32 GPU_HW::CalculateResolutionScale() const
|
||||
else
|
||||
{
|
||||
GSVector4i display_rect, draw_rect;
|
||||
CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rect,
|
||||
&draw_rect);
|
||||
CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(),
|
||||
true, true, &display_rect, &draw_rect);
|
||||
|
||||
// We use the draw rect to determine scaling. This way we match the resolution as best we can, regardless of the
|
||||
// anamorphic aspect ratio.
|
||||
|
@ -243,14 +243,14 @@ void GTE::UpdateAspectRatio()
|
||||
{
|
||||
case DisplayAspectRatio::MatchWindow:
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
s_config.aspect_ratio = DisplayAspectRatio::R4_3;
|
||||
return;
|
||||
}
|
||||
|
||||
num = g_gpu_device->GetWindowWidth();
|
||||
denom = g_gpu_device->GetWindowHeight();
|
||||
num = g_gpu_device->GetMainSwapChain()->GetWidth();
|
||||
denom = g_gpu_device->GetMainSwapChain()->GetHeight();
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -274,13 +274,19 @@ std::string Host::GetHTTPUserAgent()
|
||||
return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str);
|
||||
}
|
||||
|
||||
bool Host::CreateGPUDevice(RenderAPI api, Error* error)
|
||||
bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error)
|
||||
{
|
||||
DebugAssert(!g_gpu_device);
|
||||
|
||||
INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
|
||||
g_gpu_device = GPUDevice::CreateDeviceForAPI(api);
|
||||
|
||||
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
|
||||
if (fullscreen && g_gpu_device && g_gpu_device->SupportsExclusiveFullscreen())
|
||||
{
|
||||
fullscreen_mode =
|
||||
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
|
||||
}
|
||||
std::optional<bool> exclusive_fullscreen_control;
|
||||
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
|
||||
{
|
||||
@ -300,18 +306,30 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
|
||||
if (g_settings.gpu_disable_raster_order_views)
|
||||
disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS;
|
||||
|
||||
// Don't dump shaders on debug builds for Android, users will complain about storage...
|
||||
#if !defined(__ANDROID__) || defined(_DEBUG)
|
||||
const std::string_view shader_dump_directory(EmuFolders::DataRoot);
|
||||
#else
|
||||
const std::string_view shader_dump_directory;
|
||||
#endif
|
||||
|
||||
Error create_error;
|
||||
if (!g_gpu_device || !g_gpu_device->Create(
|
||||
g_settings.gpu_adapter,
|
||||
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
|
||||
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveVSyncMode(),
|
||||
System::ShouldAllowPresentThrottle(), exclusive_fullscreen_control,
|
||||
static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
|
||||
std::optional<WindowInfo> wi;
|
||||
if (!g_gpu_device ||
|
||||
!(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() ||
|
||||
!g_gpu_device->Create(
|
||||
g_settings.gpu_adapter, static_cast<GPUDevice::FeatureMask>(disabled_features), shader_dump_directory,
|
||||
g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
|
||||
SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, wi.value(), System::GetEffectiveVSyncMode(),
|
||||
System::ShouldAllowPresentThrottle(), fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
|
||||
exclusive_fullscreen_control, &create_error))
|
||||
{
|
||||
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
|
||||
if (g_gpu_device)
|
||||
g_gpu_device->Destroy();
|
||||
g_gpu_device.reset();
|
||||
if (wi.has_value())
|
||||
Host::ReleaseRenderWindow();
|
||||
|
||||
Error::SetStringFmt(
|
||||
error,
|
||||
@ -327,27 +345,52 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
|
||||
Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
|
||||
g_gpu_device->Destroy();
|
||||
g_gpu_device.reset();
|
||||
Host::ReleaseRenderWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
InputManager::SetDisplayWindowSize(static_cast<float>(g_gpu_device->GetWindowWidth()),
|
||||
static_cast<float>(g_gpu_device->GetWindowHeight()));
|
||||
InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Host::UpdateDisplayWindow()
|
||||
void Host::UpdateDisplayWindow(bool fullscreen)
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
return;
|
||||
|
||||
if (!g_gpu_device->UpdateWindow())
|
||||
const GPUVSyncMode vsync_mode =
|
||||
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetVSyncMode() : GPUVSyncMode::Disabled;
|
||||
const bool allow_present_throttle =
|
||||
g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed();
|
||||
std::optional<GPUDevice::ExclusiveFullscreenMode> fullscreen_mode;
|
||||
if (fullscreen && g_gpu_device->SupportsExclusiveFullscreen())
|
||||
{
|
||||
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
|
||||
fullscreen_mode =
|
||||
GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", ""));
|
||||
}
|
||||
std::optional<bool> exclusive_fullscreen_control;
|
||||
if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic)
|
||||
{
|
||||
exclusive_fullscreen_control =
|
||||
(g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed);
|
||||
}
|
||||
|
||||
g_gpu_device->DestroyMainSwapChain();
|
||||
|
||||
Error error;
|
||||
std::optional<WindowInfo> wi;
|
||||
if (!(wi = Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error))
|
||||
.has_value() ||
|
||||
!g_gpu_device->RecreateMainSwapChain(wi.value(), vsync_mode, allow_present_throttle,
|
||||
fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
|
||||
exclusive_fullscreen_control, &error))
|
||||
{
|
||||
Host::ReportFatalError("Failed to change window after update", error.GetDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
|
||||
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
|
||||
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
|
||||
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
|
||||
ImGuiManager::WindowResized(f_width, f_height);
|
||||
InputManager::SetDisplayWindowSize(f_width, f_height);
|
||||
System::HostDisplayResized();
|
||||
@ -365,15 +408,21 @@ void Host::UpdateDisplayWindow()
|
||||
|
||||
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
||||
return;
|
||||
|
||||
DEV_LOG("Display window resized to {}x{}", width, height);
|
||||
|
||||
g_gpu_device->ResizeWindow(width, height, scale);
|
||||
Error error;
|
||||
if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error))
|
||||
{
|
||||
ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription());
|
||||
UpdateDisplayWindow(Host::IsFullscreen());
|
||||
return;
|
||||
}
|
||||
|
||||
const float f_width = static_cast<float>(g_gpu_device->GetWindowWidth());
|
||||
const float f_height = static_cast<float>(g_gpu_device->GetWindowHeight());
|
||||
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
|
||||
const float f_height = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
|
||||
ImGuiManager::WindowResized(f_width, f_height);
|
||||
InputManager::SetDisplayWindowSize(f_width, f_height);
|
||||
|
||||
@ -404,4 +453,6 @@ void Host::ReleaseGPUDevice()
|
||||
INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
|
||||
g_gpu_device->Destroy();
|
||||
g_gpu_device.reset();
|
||||
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
@ -82,11 +82,25 @@ void DisplayLoadingScreen(const char* message, int progress_min = -1, int progre
|
||||
/// Safely executes a function on the VM thread.
|
||||
void RunOnCPUThread(std::function<void()> function, bool block = false);
|
||||
|
||||
/// Called when the core is creating a render device.
|
||||
/// This could also be fullscreen transition.
|
||||
std::optional<WindowInfo> AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
|
||||
Error* error);
|
||||
|
||||
/// Called when the core is finished with a render window.
|
||||
void ReleaseRenderWindow();
|
||||
|
||||
/// Returns true if the hosting application is currently fullscreen.
|
||||
bool IsFullscreen();
|
||||
|
||||
/// Alters fullscreen state of hosting application.
|
||||
void SetFullscreen(bool enabled);
|
||||
|
||||
/// Attempts to create the rendering device backend.
|
||||
bool CreateGPUDevice(RenderAPI api, Error* error);
|
||||
bool CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error);
|
||||
|
||||
/// Handles fullscreen transitions and such.
|
||||
void UpdateDisplayWindow();
|
||||
void UpdateDisplayWindow(bool fullscreen);
|
||||
|
||||
/// Called when the window is resized.
|
||||
void ResizeDisplayWindow(s32 width, s32 height, float scale);
|
||||
|
@ -84,7 +84,7 @@ static std::tuple<float, float> GetMinMax(std::span<const float> values)
|
||||
void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/,
|
||||
int progress_value /*= -1*/)
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
|
||||
return;
|
||||
@ -160,10 +160,11 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
|
||||
|
||||
// TODO: Glass effect or something.
|
||||
|
||||
if (g_gpu_device->BeginPresent() == GPUDevice::PresentResult::OK)
|
||||
GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain();
|
||||
if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK)
|
||||
{
|
||||
g_gpu_device->RenderImGui();
|
||||
g_gpu_device->EndPresent(false);
|
||||
g_gpu_device->RenderImGui(swap_chain);
|
||||
g_gpu_device->EndPresent(swap_chain, false);
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
@ -158,8 +158,6 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si)
|
||||
turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f);
|
||||
sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false);
|
||||
inhibit_screensaver = si.GetBoolValue("Main", "InhibitScreensaver", true);
|
||||
start_paused = si.GetBoolValue("Main", "StartPaused", false);
|
||||
start_fullscreen = si.GetBoolValue("Main", "StartFullscreen", false);
|
||||
pause_on_focus_loss = si.GetBoolValue("Main", "PauseOnFocusLoss", false);
|
||||
pause_on_controller_disconnection = si.GetBoolValue("Main", "PauseOnControllerDisconnection", false);
|
||||
save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true);
|
||||
@ -505,8 +503,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
||||
{
|
||||
si.SetBoolValue("Main", "SyncToHostRefreshRate", sync_to_host_refresh_rate);
|
||||
si.SetBoolValue("Main", "InhibitScreensaver", inhibit_screensaver);
|
||||
si.SetBoolValue("Main", "StartPaused", start_paused);
|
||||
si.SetBoolValue("Main", "StartFullscreen", start_fullscreen);
|
||||
si.SetBoolValue("Main", "PauseOnFocusLoss", pause_on_focus_loss);
|
||||
si.SetBoolValue("Main", "PauseOnControllerDisconnection", pause_on_controller_disconnection);
|
||||
si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit);
|
||||
@ -1657,10 +1653,11 @@ float Settings::GetDisplayAspectRatioValue() const
|
||||
{
|
||||
case DisplayAspectRatio::MatchWindow:
|
||||
{
|
||||
if (!g_gpu_device)
|
||||
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
|
||||
return s_display_aspect_ratio_values[static_cast<size_t>(DEFAULT_DISPLAY_ASPECT_RATIO)];
|
||||
|
||||
return static_cast<float>(g_gpu_device->GetWindowWidth()) / static_cast<float>(g_gpu_device->GetWindowHeight());
|
||||
return static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
|
||||
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
|
||||
}
|
||||
|
||||
case DisplayAspectRatio::Custom:
|
||||
@ -2110,7 +2107,6 @@ std::string EmuFolders::Bios;
|
||||
std::string EmuFolders::Cache;
|
||||
std::string EmuFolders::Cheats;
|
||||
std::string EmuFolders::Covers;
|
||||
std::string EmuFolders::Dumps;
|
||||
std::string EmuFolders::GameIcons;
|
||||
std::string EmuFolders::GameSettings;
|
||||
std::string EmuFolders::InputProfiles;
|
||||
@ -2130,7 +2126,6 @@ void EmuFolders::SetDefaults()
|
||||
Cache = Path::Combine(DataRoot, "cache");
|
||||
Cheats = Path::Combine(DataRoot, "cheats");
|
||||
Covers = Path::Combine(DataRoot, "covers");
|
||||
Dumps = Path::Combine(DataRoot, "dump");
|
||||
GameIcons = Path::Combine(DataRoot, "gameicons");
|
||||
GameSettings = Path::Combine(DataRoot, "gamesettings");
|
||||
InputProfiles = Path::Combine(DataRoot, "inputprofiles");
|
||||
@ -2162,7 +2157,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
||||
Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache");
|
||||
Cheats = LoadPathFromSettings(si, DataRoot, "Folders", "Cheats", "cheats");
|
||||
Covers = LoadPathFromSettings(si, DataRoot, "Folders", "Covers", "covers");
|
||||
Dumps = LoadPathFromSettings(si, DataRoot, "Folders", "Dumps", "dump");
|
||||
GameIcons = LoadPathFromSettings(si, DataRoot, "Folders", "GameIcons", "gameicons");
|
||||
GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings");
|
||||
InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles");
|
||||
@ -2179,7 +2173,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
||||
DEV_LOG("Cache Directory: {}", Cache);
|
||||
DEV_LOG("Cheats Directory: {}", Cheats);
|
||||
DEV_LOG("Covers Directory: {}", Covers);
|
||||
DEV_LOG("Dumps Directory: {}", Dumps);
|
||||
DEV_LOG("Game Icons Directory: {}", GameIcons);
|
||||
DEV_LOG("Game Settings Directory: {}", GameSettings);
|
||||
DEV_LOG("Input Profile Directory: {}", InputProfiles);
|
||||
@ -2201,7 +2194,6 @@ void EmuFolders::Save(SettingsInterface& si)
|
||||
si.SetStringValue("Folders", "Cache", Path::MakeRelative(Cache, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "Cheats", Path::MakeRelative(Cheats, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "Covers", Path::MakeRelative(Covers, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "Dumps", Path::MakeRelative(Dumps, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "GameIcons", Path::MakeRelative(GameIcons, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "InputProfiles", Path::MakeRelative(InputProfiles, DataRoot).c_str());
|
||||
@ -2242,7 +2234,6 @@ bool EmuFolders::EnsureFoldersExist()
|
||||
result = FileSystem::EnsureDirectoryExists(Path::Combine(Cache, "achievement_images").c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Cheats.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Covers.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Dumps.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(GameIcons.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(InputProfiles.c_str(), false) && result;
|
||||
|
@ -78,8 +78,6 @@ struct Settings
|
||||
float turbo_speed = 0.0f;
|
||||
bool sync_to_host_refresh_rate : 1 = false;
|
||||
bool inhibit_screensaver : 1 = true;
|
||||
bool start_paused : 1 = false;
|
||||
bool start_fullscreen : 1 = false;
|
||||
bool pause_on_focus_loss : 1 = false;
|
||||
bool pause_on_controller_disconnection : 1 = false;
|
||||
bool save_state_on_exit : 1 = true;
|
||||
@ -572,7 +570,6 @@ extern std::string Bios;
|
||||
extern std::string Cache;
|
||||
extern std::string Cheats;
|
||||
extern std::string Covers;
|
||||
extern std::string Dumps;
|
||||
extern std::string GameIcons;
|
||||
extern std::string GameSettings;
|
||||
extern std::string InputProfiles;
|
||||
|
@ -145,21 +145,26 @@ static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_
|
||||
static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span<const u8> exe_buffer,
|
||||
const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length);
|
||||
|
||||
/// Settings that are looked up on demand.
|
||||
static bool ShouldStartFullscreen();
|
||||
static bool ShouldStartPaused();
|
||||
|
||||
/// Checks for settings changes, std::move() the old settings away for comparing beforehand.
|
||||
static void CheckForSettingsChanges(const Settings& old_settings);
|
||||
static void WarnAboutUnsafeSettings();
|
||||
static void LogUnsafeSettingsToConsole(const SmallStringBase& messages);
|
||||
|
||||
static bool Initialize(bool force_software_renderer, Error* error);
|
||||
static bool Initialize(bool force_software_renderer, bool fullscreen, Error* error);
|
||||
static bool LoadBIOS(Error* error);
|
||||
static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error);
|
||||
static void InternalReset();
|
||||
static void ClearRunningGame();
|
||||
static void DestroySystem();
|
||||
|
||||
static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error);
|
||||
static bool CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error);
|
||||
static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true);
|
||||
static void HandleHostGPUDeviceLost();
|
||||
static void HandleExclusiveFullscreenLost();
|
||||
|
||||
/// Returns true if boot is being fast forwarded.
|
||||
static bool IsFastForwardingBoot();
|
||||
@ -1201,10 +1206,11 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
|
||||
{
|
||||
PostProcessing::Shutdown();
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
||||
Error error;
|
||||
if (!CreateGPU(renderer, true, &error))
|
||||
if (!CreateGPU(renderer, true, Host::IsFullscreen(), &error))
|
||||
{
|
||||
if (!IsStartupCancelled())
|
||||
Host::ReportErrorAsync("Error", error.GetDescription());
|
||||
@ -1265,6 +1271,12 @@ void System::HandleHostGPUDeviceLost()
|
||||
Host::OSD_CRITICAL_ERROR_DURATION);
|
||||
}
|
||||
|
||||
void System::HandleExclusiveFullscreenLost()
|
||||
{
|
||||
WARNING_LOG("Lost exclusive fullscreen.");
|
||||
Host::SetFullscreen(false);
|
||||
}
|
||||
|
||||
void System::LoadSettings(bool display_osd_messages)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
|
||||
@ -1373,6 +1385,9 @@ void System::SetDefaultSettings(SettingsInterface& si)
|
||||
|
||||
temp.Save(si, false);
|
||||
|
||||
si.SetBoolValue("Main", "StartPaused", false);
|
||||
si.SetBoolValue("Main", "StartFullscreen", false);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__ANDROID__)
|
||||
// On Linux, default the console to whether standard input is currently available.
|
||||
si.SetBoolValue("Logging", "LogToConsole", Log::IsConsoleOutputCurrentlyAvailable());
|
||||
@ -1793,7 +1808,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
}
|
||||
|
||||
// Component setup.
|
||||
if (!Initialize(parameters.force_software_renderer, error))
|
||||
if (!Initialize(parameters.force_software_renderer, parameters.override_fullscreen.value_or(ShouldStartFullscreen()),
|
||||
error))
|
||||
{
|
||||
s_boot_mode = System::BootMode::None;
|
||||
s_state = State::Shutdown;
|
||||
@ -1853,7 +1869,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
if (parameters.start_media_capture)
|
||||
StartMediaCapture({});
|
||||
|
||||
if (g_settings.start_paused || parameters.override_start_paused.value_or(false))
|
||||
if (ShouldStartPaused() || parameters.override_start_paused.value_or(false))
|
||||
PauseSystem(true);
|
||||
|
||||
UpdateSpeedLimiterState();
|
||||
@ -1861,7 +1877,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool System::Initialize(bool force_software_renderer, Error* error)
|
||||
bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* error)
|
||||
{
|
||||
g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK);
|
||||
s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10);
|
||||
@ -1911,7 +1927,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
|
||||
Bus::Initialize();
|
||||
CPU::Initialize();
|
||||
|
||||
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error))
|
||||
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error))
|
||||
{
|
||||
CPU::Shutdown();
|
||||
Bus::Shutdown();
|
||||
@ -1928,10 +1944,7 @@ bool System::Initialize(bool force_software_renderer, Error* error)
|
||||
{
|
||||
g_gpu.reset();
|
||||
if (!s_keep_gpu_device_on_shutdown)
|
||||
{
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
if (g_settings.gpu_pgxp_enable)
|
||||
CPU::PGXP::Shutdown();
|
||||
CPU::Shutdown();
|
||||
@ -2018,7 +2031,6 @@ void System::DestroySystem()
|
||||
else
|
||||
{
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
||||
s_bios_hash = {};
|
||||
@ -2189,12 +2201,13 @@ void System::FrameDone()
|
||||
const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number);
|
||||
s_last_presented_internal_frame_number = s_internal_frame_number;
|
||||
|
||||
const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame &&
|
||||
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
|
||||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
|
||||
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
|
||||
g_gpu_device->ShouldSkipPresentingFrame()) &&
|
||||
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
|
||||
const bool skip_this_frame =
|
||||
(((s_skip_presenting_duplicate_frames && !is_unique_frame &&
|
||||
s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) ||
|
||||
(!s_optimal_frame_pacing && current_time > s_next_frame_time &&
|
||||
s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) ||
|
||||
(g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->ShouldSkipPresentingFrame())) &&
|
||||
!s_syncing_to_host_with_vsync && !IsExecutionInterrupted());
|
||||
if (!skip_this_frame)
|
||||
{
|
||||
s_skipped_frame_count = 0;
|
||||
@ -2211,7 +2224,7 @@ void System::FrameDone()
|
||||
const bool do_present = PresentDisplay(true, 0);
|
||||
Throttle(current_time);
|
||||
if (do_present)
|
||||
g_gpu_device->SubmitPresent();
|
||||
g_gpu_device->SubmitPresent(g_gpu_device->GetMainSwapChain());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2373,7 +2386,7 @@ void System::IncrementInternalFrameNumber()
|
||||
s_internal_frame_number++;
|
||||
}
|
||||
|
||||
bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
|
||||
bool System::CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error)
|
||||
{
|
||||
const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer);
|
||||
|
||||
@ -2388,7 +2401,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
|
||||
}
|
||||
|
||||
Host::ReleaseGPUDevice();
|
||||
if (!Host::CreateGPUDevice(api, error))
|
||||
if (!Host::CreateGPUDevice(api, fullscreen, error))
|
||||
{
|
||||
Host::ReleaseRenderWindow();
|
||||
return false;
|
||||
@ -3436,9 +3449,9 @@ void System::UpdateSpeedLimiterState()
|
||||
s_syncing_to_host = false;
|
||||
s_syncing_to_host_with_vsync = false;
|
||||
|
||||
if (g_settings.sync_to_host_refresh_rate)
|
||||
if (g_settings.sync_to_host_refresh_rate && g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate;
|
||||
const float host_refresh_rate = g_gpu_device->GetMainSwapChain()->GetWindowInfo().surface_refresh_rate;
|
||||
if (host_refresh_rate > 0.0f)
|
||||
{
|
||||
const float ratio = host_refresh_rate / System::GetThrottleFrequency();
|
||||
@ -3499,14 +3512,23 @@ void System::UpdateDisplayVSync()
|
||||
// Avoid flipping vsync on and off by manually throttling when vsync is on.
|
||||
const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode();
|
||||
const bool allow_present_throttle = ShouldAllowPresentThrottle();
|
||||
if (g_gpu_device->GetVSyncMode() == vsync_mode && g_gpu_device->IsPresentThrottleAllowed() == allow_present_throttle)
|
||||
if (!g_gpu_device->HasMainSwapChain() ||
|
||||
(g_gpu_device->GetMainSwapChain()->GetVSyncMode() == vsync_mode &&
|
||||
g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed() == allow_present_throttle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast<size_t>(vsync_mode)],
|
||||
s_syncing_to_host_with_vsync ? " (for throttling)" : "",
|
||||
allow_present_throttle ? " (present throttle allowed)" : "");
|
||||
|
||||
g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle);
|
||||
Error error;
|
||||
if (!g_gpu_device->GetMainSwapChain()->SetVSyncMode(vsync_mode, allow_present_throttle, &error))
|
||||
{
|
||||
ERROR_LOG("Failed to update vsync mode to {}: {}", vsync_modes[static_cast<size_t>(vsync_mode)],
|
||||
error.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
GPUVSyncMode System::GetEffectiveVSyncMode()
|
||||
@ -4230,6 +4252,16 @@ bool System::SwitchMediaSubImage(u32 index)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool System::ShouldStartFullscreen()
|
||||
{
|
||||
return Host::GetBoolSettingValue("Main", "StartFullscreen", false);
|
||||
}
|
||||
|
||||
bool System::ShouldStartPaused()
|
||||
{
|
||||
return Host::GetBoolSettingValue("Main", "StartPaused", false);
|
||||
}
|
||||
|
||||
void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||
{
|
||||
if (IsValid() &&
|
||||
@ -5193,7 +5225,7 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur
|
||||
u32 capture_height =
|
||||
Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT);
|
||||
const GPUTexture::Format capture_format =
|
||||
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
|
||||
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8;
|
||||
const float fps = System::GetThrottleFrequency();
|
||||
if (capture_video)
|
||||
{
|
||||
@ -5640,11 +5672,14 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
|
||||
ImGuiManager::RenderOverlayWindows();
|
||||
ImGuiManager::RenderDebugWindows();
|
||||
|
||||
const GPUDevice::PresentResult pres = g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent();
|
||||
const GPUDevice::PresentResult pres =
|
||||
g_gpu_device->HasMainSwapChain() ?
|
||||
(g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain())) :
|
||||
GPUDevice::PresentResult::SkipPresent;
|
||||
if (pres == GPUDevice::PresentResult::OK)
|
||||
{
|
||||
g_gpu_device->RenderImGui();
|
||||
g_gpu_device->EndPresent(explicit_present, present_time);
|
||||
g_gpu_device->RenderImGui(g_gpu_device->GetMainSwapChain());
|
||||
g_gpu_device->EndPresent(g_gpu_device->GetMainSwapChain(), explicit_present, present_time);
|
||||
|
||||
if (g_gpu_device->IsGPUTimingEnabled())
|
||||
{
|
||||
@ -5656,9 +5691,13 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time)
|
||||
{
|
||||
if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]]
|
||||
HandleHostGPUDeviceLost();
|
||||
else if (pres == GPUDevice::PresentResult::ExclusiveFullscreenLost)
|
||||
HandleExclusiveFullscreenLost();
|
||||
else
|
||||
g_gpu_device->FlushCommands();
|
||||
|
||||
// Still need to kick ImGui or it gets cranky.
|
||||
ImGui::Render();
|
||||
ImGui::EndFrame();
|
||||
}
|
||||
|
||||
ImGuiManager::NewFrame();
|
||||
|
@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const
|
||||
static_cast<int>(std::ceil(static_cast<qreal>(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo(Error* error)
|
||||
{
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this));
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this, error));
|
||||
if (ret.has_value())
|
||||
{
|
||||
m_last_window_width = ret->surface_width;
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <optional>
|
||||
|
||||
class Error;
|
||||
|
||||
class QCloseEvent;
|
||||
|
||||
class DisplayWidget final : public QWidget
|
||||
@ -26,7 +28,7 @@ public:
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
std::optional<WindowInfo> getWindowInfo(Error* error);
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
void updateCursor(bool hidden);
|
||||
|
@ -852,9 +852,9 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_
|
||||
m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"), QVariant(QString()));
|
||||
if (current_adapter)
|
||||
{
|
||||
for (const std::string& mode_name : current_adapter->fullscreen_modes)
|
||||
for (const GPUDevice::ExclusiveFullscreenMode& mode : current_adapter->fullscreen_modes)
|
||||
{
|
||||
const QString qmodename = QString::fromStdString(mode_name);
|
||||
const QString qmodename = QtUtils::StringViewToQString(mode.ToString());
|
||||
m_ui.fullscreenMode->addItem(qmodename, QVariant(qmodename));
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ static bool s_use_central_widget = false;
|
||||
// UI thread VM validity.
|
||||
static bool s_system_valid = false;
|
||||
static bool s_system_paused = false;
|
||||
static std::atomic_uint32_t s_system_locked{false};
|
||||
static QString s_current_game_title;
|
||||
static QString s_current_game_serial;
|
||||
static QString s_current_game_path;
|
||||
@ -220,30 +221,25 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
|
||||
|
||||
#endif
|
||||
|
||||
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
|
||||
bool surfaceless, bool use_main_window_pos)
|
||||
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error)
|
||||
{
|
||||
DEV_LOG("acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
|
||||
recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false",
|
||||
surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false");
|
||||
DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
|
||||
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false",
|
||||
use_main_window_pos ? "true" : "false");
|
||||
|
||||
QWidget* container =
|
||||
m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
const bool is_fullscreen = isRenderingFullscreen();
|
||||
const bool is_rendering_to_main = isRenderingToMain();
|
||||
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
||||
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
|
||||
!changing_surfaceless)
|
||||
{
|
||||
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
|
||||
}
|
||||
|
||||
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
||||
// .. except on Wayland, where everything tends to break if you don't recreate.
|
||||
const bool has_container = (m_display_container != nullptr);
|
||||
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main &&
|
||||
has_container == needs_container && !needs_container && !changing_surfaceless)
|
||||
if (m_display_created && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
|
||||
!needs_container && !changing_surfaceless)
|
||||
{
|
||||
DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||
|
||||
@ -257,12 +253,11 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
}
|
||||
else
|
||||
{
|
||||
container->showNormal();
|
||||
if (use_main_window_pos)
|
||||
container->setGeometry(geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
|
||||
container->showNormal();
|
||||
}
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
@ -270,7 +265,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
updateWindowState();
|
||||
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
return m_display_widget->getWindowInfo();
|
||||
return m_display_widget->getWindowInfo(error);
|
||||
}
|
||||
|
||||
destroyDisplayWidget(surfaceless);
|
||||
@ -282,7 +277,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
|
||||
createDisplayWidget(fullscreen, render_to_main, use_main_window_pos);
|
||||
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(error);
|
||||
if (!wi.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||
@ -2852,11 +2847,13 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem()
|
||||
MainWindow::SystemLock::SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
|
||||
: m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen)
|
||||
{
|
||||
s_system_locked.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
MainWindow::SystemLock::SystemLock(SystemLock&& lock)
|
||||
: m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen)
|
||||
{
|
||||
s_system_locked.fetch_add(1, std::memory_order_release);
|
||||
lock.m_dialog_parent = nullptr;
|
||||
lock.m_was_paused = true;
|
||||
lock.m_was_fullscreen = false;
|
||||
@ -2864,6 +2861,8 @@ MainWindow::SystemLock::SystemLock(SystemLock&& lock)
|
||||
|
||||
MainWindow::SystemLock::~SystemLock()
|
||||
{
|
||||
DebugAssert(s_system_locked.load(std::memory_order_relaxed) > 0);
|
||||
s_system_locked.fetch_sub(1, std::memory_order_release);
|
||||
if (m_was_fullscreen)
|
||||
g_emu_thread->setFullscreen(true, true);
|
||||
if (!m_was_paused)
|
||||
@ -2874,4 +2873,9 @@ void MainWindow::SystemLock::cancelResume()
|
||||
{
|
||||
m_was_paused = true;
|
||||
m_was_fullscreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool QtHost::IsSystemLocked()
|
||||
{
|
||||
return (s_system_locked.load(std::memory_order_acquire) > 0);
|
||||
}
|
||||
|
@ -126,8 +126,8 @@ private Q_SLOTS:
|
||||
bool confirmMessage(const QString& title, const QString& message);
|
||||
void onStatusMessage(const QString& message);
|
||||
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
|
||||
bool surfaceless, bool use_main_window_pos);
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error);
|
||||
void displayResizeRequested(qint32 width, qint32 height);
|
||||
void releaseRenderWindow();
|
||||
void focusDisplayWidget();
|
||||
|
@ -625,13 +625,6 @@ void EmuThread::loadSettings(SettingsInterface& si)
|
||||
//
|
||||
}
|
||||
|
||||
void EmuThread::setInitialState(std::optional<bool> override_fullscreen)
|
||||
{
|
||||
m_is_fullscreen = override_fullscreen.value_or(Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false));
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
m_is_surfaceless = false;
|
||||
}
|
||||
|
||||
void EmuThread::checkForSettingsChanges(const Settings& old_settings)
|
||||
{
|
||||
if (g_main_window)
|
||||
@ -642,12 +635,16 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings)
|
||||
updatePerformanceCounters();
|
||||
}
|
||||
|
||||
const bool render_to_main = shouldRenderToMain();
|
||||
if (m_is_rendering_to_main != render_to_main)
|
||||
// don't mess with fullscreen while locked
|
||||
if (!QtHost::IsSystemLocked())
|
||||
{
|
||||
m_is_rendering_to_main = render_to_main;
|
||||
if (g_gpu_device)
|
||||
g_gpu_device->UpdateWindow();
|
||||
const bool render_to_main = shouldRenderToMain();
|
||||
if (m_is_rendering_to_main != render_to_main && !m_is_fullscreen)
|
||||
{
|
||||
m_is_rendering_to_main = render_to_main;
|
||||
if (g_gpu_device)
|
||||
Host::UpdateDisplayWindow(m_is_fullscreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,16 +777,15 @@ void EmuThread::startFullscreenUI()
|
||||
// we want settings loaded so we choose the correct renderer
|
||||
// this also sorts out input sources.
|
||||
System::LoadSettings(false);
|
||||
setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional<bool>(true) : std::optional<bool>());
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
m_run_fullscreen_ui = true;
|
||||
|
||||
Error error;
|
||||
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), &error) ||
|
||||
if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer),
|
||||
s_start_fullscreen_ui_fullscreen, &error) ||
|
||||
!FullscreenUI::Initialize())
|
||||
{
|
||||
Host::ReportErrorAsync("Error", error.GetDescription());
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
m_run_fullscreen_ui = false;
|
||||
return;
|
||||
}
|
||||
@ -825,7 +821,6 @@ void EmuThread::stopFullscreenUI()
|
||||
return;
|
||||
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
}
|
||||
|
||||
void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
|
||||
@ -841,7 +836,7 @@ void EmuThread::bootSystem(std::shared_ptr<SystemBootParameters> params)
|
||||
if (System::IsValidOrInitializing())
|
||||
return;
|
||||
|
||||
setInitialState(params->override_fullscreen);
|
||||
m_is_rendering_to_main = shouldRenderToMain();
|
||||
|
||||
Error error;
|
||||
if (!System::BootSystem(std::move(*params), &error))
|
||||
@ -974,7 +969,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||
|
||||
m_is_fullscreen = fullscreen;
|
||||
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
|
||||
Host::UpdateDisplayWindow();
|
||||
Host::UpdateDisplayWindow(fullscreen);
|
||||
}
|
||||
|
||||
bool Host::IsFullscreen()
|
||||
@ -984,6 +979,10 @@ bool Host::IsFullscreen()
|
||||
|
||||
void Host::SetFullscreen(bool enabled)
|
||||
{
|
||||
// don't mess with fullscreen while locked
|
||||
if (QtHost::IsSystemLocked())
|
||||
return;
|
||||
|
||||
g_emu_thread->setFullscreen(enabled, true);
|
||||
}
|
||||
|
||||
@ -999,7 +998,7 @@ void EmuThread::setSurfaceless(bool surfaceless)
|
||||
return;
|
||||
|
||||
m_is_surfaceless = surfaceless;
|
||||
Host::UpdateDisplayWindow();
|
||||
Host::UpdateDisplayWindow(false);
|
||||
}
|
||||
|
||||
void EmuThread::requestDisplaySize(float scale)
|
||||
@ -1016,20 +1015,18 @@ void EmuThread::requestDisplaySize(float scale)
|
||||
System::RequestDisplaySize(scale);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
|
||||
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error)
|
||||
{
|
||||
DebugAssert(g_gpu_device);
|
||||
u32 fs_width, fs_height;
|
||||
float fs_refresh_rate;
|
||||
m_is_exclusive_fullscreen = (m_is_fullscreen && g_gpu_device->SupportsExclusiveFullscreen() &&
|
||||
GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate));
|
||||
|
||||
const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
|
||||
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
|
||||
m_is_fullscreen = fullscreen;
|
||||
|
||||
const bool window_fullscreen = m_is_fullscreen && !exclusive_fullscreen;
|
||||
const bool render_to_main = !fullscreen && m_is_rendering_to_main;
|
||||
const bool use_main_window_pos = shouldRenderToMain();
|
||||
|
||||
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless,
|
||||
use_main_window_pos);
|
||||
return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos,
|
||||
error);
|
||||
}
|
||||
|
||||
void EmuThread::releaseRenderWindow()
|
||||
@ -1811,11 +1808,11 @@ void EmuThread::run()
|
||||
|
||||
m_event_loop->processEvents(QEventLoop::AllEvents);
|
||||
System::Internal::IdlePollUpdate();
|
||||
if (g_gpu_device)
|
||||
if (g_gpu_device && g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
System::PresentDisplay(false, 0);
|
||||
if (!g_gpu_device->IsVSyncModeBlocking())
|
||||
g_gpu_device->ThrottlePresentation();
|
||||
if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking())
|
||||
g_gpu_device->GetMainSwapChain()->ThrottlePresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2005,9 +2002,10 @@ void Host::CommitBaseSettingChanges()
|
||||
QtHost::QueueSettingsSave();
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
|
||||
Error* error)
|
||||
{
|
||||
return g_emu_thread->acquireRenderWindow(recreate_window);
|
||||
return g_emu_thread->acquireRenderWindow(fullscreen, exclusive_fullscreen, error);
|
||||
}
|
||||
|
||||
void Host::ReleaseRenderWindow()
|
||||
|
@ -98,7 +98,7 @@ public:
|
||||
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
|
||||
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
|
||||
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error);
|
||||
void connectDisplaySignals(DisplayWidget* widget);
|
||||
void releaseRenderWindow();
|
||||
|
||||
@ -135,8 +135,8 @@ Q_SIGNALS:
|
||||
void systemPaused();
|
||||
void systemResumed();
|
||||
void gameListRefreshed();
|
||||
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main,
|
||||
bool surfaceless, bool use_main_window_pos);
|
||||
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless,
|
||||
bool use_main_window_pos, Error* error);
|
||||
void onResizeRenderWindowRequested(qint32 width, qint32 height);
|
||||
void onReleaseRenderWindowRequested();
|
||||
void focusDisplayWidgetRequested();
|
||||
@ -220,7 +220,6 @@ private:
|
||||
|
||||
void createBackgroundControllerPollTimer();
|
||||
void destroyBackgroundControllerPollTimer();
|
||||
void setInitialState(std::optional<bool> override_fullscreen);
|
||||
void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
|
||||
std::function<void(bool)> callback) const;
|
||||
|
||||
@ -233,8 +232,6 @@ private:
|
||||
bool m_run_fullscreen_ui = false;
|
||||
bool m_is_rendering_to_main = false;
|
||||
bool m_is_fullscreen = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_lost_exclusive_fullscreen = false;
|
||||
bool m_is_surfaceless = false;
|
||||
bool m_save_state_on_shutdown = false;
|
||||
|
||||
@ -320,6 +317,9 @@ bool ShouldShowDebugOptions();
|
||||
bool IsSystemValid();
|
||||
bool IsSystemPaused();
|
||||
|
||||
/// Returns true if any lock is in place.
|
||||
bool IsSystemLocked();
|
||||
|
||||
/// Accessors for game information.
|
||||
const QString& GetCurrentGameTitle();
|
||||
const QString& GetCurrentGameSerial();
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "core/game_list.h"
|
||||
#include "core/system.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
@ -316,7 +317,7 @@ qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
|
||||
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget)
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
@ -344,14 +345,14 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget)
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Unknown PNI platform " << platform_name;
|
||||
Error::SetStringFmt(error, "Unknown PNI platform {}", platform_name.toStdString());
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
const qreal dpr = GetDevicePixelRatioForWidget(widget);
|
||||
wi.surface_width = static_cast<u32>(static_cast<qreal>(widget->width()) * dpr);
|
||||
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
|
||||
wi.surface_width = static_cast<u16>(static_cast<qreal>(widget->width()) * dpr);
|
||||
wi.surface_height = static_cast<u16>(static_cast<qreal>(widget->height()) * dpr);
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
class ByteStream;
|
||||
class Error;
|
||||
|
||||
class QComboBox;
|
||||
class QFrame;
|
||||
@ -116,7 +116,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
|
||||
qreal GetDevicePixelRatioForWidget(const QWidget* widget);
|
||||
|
||||
/// Returns the common window info structure for a Qt widget.
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, Error* error = nullptr);
|
||||
|
||||
/// Saves a window's geometry to configuration. Returns false if the configuration was changed.
|
||||
bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true);
|
||||
|
@ -110,7 +110,6 @@ if(ENABLE_OPENGL)
|
||||
opengl_context_wgl.cpp
|
||||
opengl_context_wgl.h
|
||||
)
|
||||
target_link_libraries(util PRIVATE "opengl32.lib")
|
||||
endif()
|
||||
|
||||
if(LINUX OR BSD OR ANDROID)
|
||||
@ -183,6 +182,9 @@ if(NOT ANDROID)
|
||||
sdl_input_source.cpp
|
||||
sdl_input_source.h
|
||||
)
|
||||
target_compile_definitions(util PUBLIC
|
||||
ENABLE_SDL
|
||||
)
|
||||
target_link_libraries(util PUBLIC
|
||||
cubeb
|
||||
SDL2::SDL2
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include <d3dcompiler.h>
|
||||
#include <dxgi1_5.h>
|
||||
|
||||
LOG_CHANNEL(D3D11Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
// We need to synchronize instance creation because of adapter enumeration from the UI thread.
|
||||
static std::mutex s_instance_mutex;
|
||||
@ -45,7 +45,10 @@ void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name)
|
||||
#endif
|
||||
}
|
||||
|
||||
D3D11Device::D3D11Device() = default;
|
||||
D3D11Device::D3D11Device()
|
||||
{
|
||||
m_render_api = RenderAPI::D3D11;
|
||||
}
|
||||
|
||||
D3D11Device::~D3D11Device()
|
||||
{
|
||||
@ -53,13 +56,11 @@ D3D11Device::~D3D11Device()
|
||||
Assert(!m_device);
|
||||
}
|
||||
|
||||
bool D3D11Device::HasSurface() const
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
}
|
||||
|
||||
bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error)
|
||||
bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_instance_mutex);
|
||||
|
||||
@ -125,17 +126,14 @@ bool D3D11Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
||||
INFO_LOG("Max device feature level: {}",
|
||||
D3DCommon::GetFeatureLevelString(D3DCommon::GetRenderAPIVersionForFeatureLevel(m_max_feature_level)));
|
||||
|
||||
BOOL allow_tearing_supported = false;
|
||||
hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
|
||||
sizeof(allow_tearing_supported));
|
||||
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
||||
|
||||
SetFeatures(disabled_features);
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||
if (!wi.IsSurfaceless())
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create swap chain");
|
||||
return false;
|
||||
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
|
||||
exclusive_fullscreen_control, error);
|
||||
if (!m_main_swap_chain)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateBuffers())
|
||||
@ -152,6 +150,7 @@ void D3D11Device::DestroyDevice()
|
||||
std::unique_lock lock(s_instance_mutex);
|
||||
|
||||
DestroyBuffers();
|
||||
m_main_swap_chain.reset();
|
||||
m_context.Reset();
|
||||
m_device.Reset();
|
||||
}
|
||||
@ -160,7 +159,6 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
|
||||
{
|
||||
const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel();
|
||||
|
||||
m_render_api = RenderAPI::D3D11;
|
||||
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
|
||||
m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
m_max_multisamples = 1;
|
||||
@ -202,96 +200,107 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
|
||||
}
|
||||
}
|
||||
|
||||
u32 D3D11Device::GetSwapChainBufferCount() const
|
||||
D3D11SwapChain::D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode)
|
||||
: GPUSwapChain(wi, vsync_mode, allow_present_throttle)
|
||||
{
|
||||
// With vsync off, we only need two buffers. Same for blocking vsync.
|
||||
// With triple buffering, we need three.
|
||||
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
|
||||
if (fullscreen_mode)
|
||||
InitializeExclusiveFullscreenMode(fullscreen_mode);
|
||||
}
|
||||
|
||||
bool D3D11Device::CreateSwapChain()
|
||||
D3D11SwapChain::~D3D11SwapChain()
|
||||
{
|
||||
if (m_window_info.type != WindowInfo::Type::Win32)
|
||||
return false;
|
||||
m_swap_chain_rtv.Reset();
|
||||
DestroySwapChain();
|
||||
}
|
||||
|
||||
const DXGI_FORMAT dxgi_format = D3DCommon::GetFormatMapping(s_swap_chain_format).resource_format;
|
||||
bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode)
|
||||
{
|
||||
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
|
||||
|
||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||
RECT client_rc{};
|
||||
GetClientRect(window_hwnd, &client_rc);
|
||||
|
||||
DXGI_MODE_DESC fullscreen_mode = {};
|
||||
ComPtr<IDXGIOutput> fullscreen_output;
|
||||
if (Host::IsFullscreen())
|
||||
{
|
||||
u32 fullscreen_width, fullscreen_height;
|
||||
float fullscreen_refresh_rate;
|
||||
m_is_exclusive_fullscreen =
|
||||
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width,
|
||||
fullscreen_height, fullscreen_refresh_rate, dxgi_format,
|
||||
&fullscreen_mode, fullscreen_output.GetAddressOf());
|
||||
m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc(
|
||||
D3D11Device::GetDXGIFactory(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf());
|
||||
return m_fullscreen_mode.has_value();
|
||||
}
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
m_vsync_mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
}
|
||||
else
|
||||
u32 D3D11SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
|
||||
{
|
||||
// With vsync off, we only need two buffers. Same for blocking vsync.
|
||||
// With triple buffering, we need three.
|
||||
return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
|
||||
}
|
||||
|
||||
bool D3D11SwapChain::CreateSwapChain(Error* error)
|
||||
{
|
||||
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
|
||||
|
||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||
RECT client_rc{};
|
||||
GetClientRect(window_hwnd, &client_rc);
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox)
|
||||
{
|
||||
m_is_exclusive_fullscreen = false;
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
m_vsync_mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
m_using_flip_model_swap_chain =
|
||||
!Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || m_is_exclusive_fullscreen;
|
||||
!Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || IsExclusiveFullscreen();
|
||||
|
||||
IDXGIFactory5* const dxgi_factory = D3D11Device::GetDXGIFactory();
|
||||
ID3D11Device1* const d3d_device = D3D11Device::GetD3DDevice();
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||
swap_chain_desc.Format = dxgi_format;
|
||||
swap_chain_desc.Format = fm.resource_format;
|
||||
swap_chain_desc.SampleDesc.Count = 1;
|
||||
swap_chain_desc.BufferCount = GetSwapChainBufferCount();
|
||||
swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode);
|
||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (m_is_exclusive_fullscreen)
|
||||
if (IsExclusiveFullscreen())
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
|
||||
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||
fs_sd_desc.Width = m_fullscreen_mode->Width;
|
||||
fs_sd_desc.Height = m_fullscreen_mode->Height;
|
||||
fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
|
||||
fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
|
||||
fs_desc.Scaling = m_fullscreen_mode->Scaling;
|
||||
fs_desc.Windowed = FALSE;
|
||||
|
||||
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
|
||||
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
|
||||
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &fs_sd_desc, &fs_desc, m_fullscreen_output.Get(),
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
|
||||
m_is_exclusive_fullscreen = false;
|
||||
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
|
||||
m_fullscreen_output.Reset();
|
||||
m_fullscreen_mode.reset();
|
||||
m_using_allow_tearing = (m_using_flip_model_swap_chain && D3DCommon::SupportsAllowTearing(dxgi_factory));
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_is_exclusive_fullscreen)
|
||||
if (!IsExclusiveFullscreen())
|
||||
{
|
||||
VERBOSE_LOG("Creating a {}x{} {} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
||||
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
m_using_allow_tearing = (m_using_flip_model_swap_chain && !IsExclusiveFullscreen() &&
|
||||
D3DCommon::SupportsAllowTearing(D3D11Device::GetDXGIFactory()));
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr,
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
}
|
||||
|
||||
if (FAILED(hr) && m_using_flip_model_swap_chain)
|
||||
@ -302,11 +311,11 @@ bool D3D11Device::CreateSwapChain()
|
||||
m_using_flip_model_swap_chain = false;
|
||||
m_using_allow_tearing = false;
|
||||
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr,
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast<unsigned>(hr));
|
||||
Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -319,25 +328,29 @@ bool D3D11Device::CreateSwapChain()
|
||||
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||
}
|
||||
|
||||
if (!CreateSwapChainRTV())
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), s_clear_color.data());
|
||||
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D11Device::CreateSwapChainRTV()
|
||||
void D3D11SwapChain::DestroySwapChain()
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
// switch out of fullscreen before destroying
|
||||
BOOL is_fullscreen;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||
|
||||
m_swap_chain.Reset();
|
||||
}
|
||||
|
||||
bool D3D11SwapChain::CreateRTV(Error* error)
|
||||
{
|
||||
ComPtr<ID3D11Texture2D> backbuffer;
|
||||
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf()));
|
||||
if (FAILED(hr)) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
|
||||
Error::SetHResult(error, "GetBuffer() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -346,16 +359,17 @@ bool D3D11Device::CreateSwapChainRTV()
|
||||
|
||||
CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0,
|
||||
backbuffer_desc.ArraySize);
|
||||
hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.ReleaseAndGetAddressOf());
|
||||
hr = D3D11Device::GetD3DDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc,
|
||||
m_swap_chain_rtv.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("CreateRenderTargetView for swap chain failed: 0x{:08X}", static_cast<unsigned>(hr));
|
||||
Error::SetHResult(error, "CreateRenderTargetView(): ", hr);
|
||||
m_swap_chain_rtv.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_window_info.surface_width = backbuffer_desc.Width;
|
||||
m_window_info.surface_height = backbuffer_desc.Height;
|
||||
m_window_info.surface_width = static_cast<u16>(backbuffer_desc.Width);
|
||||
m_window_info.surface_height = static_cast<u16>(backbuffer_desc.Height);
|
||||
m_window_info.surface_format = s_swap_chain_format;
|
||||
VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
|
||||
|
||||
@ -374,65 +388,77 @@ bool D3D11Device::CreateSwapChainRTV()
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D11Device::DestroySwapChain()
|
||||
bool D3D11SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
m_swap_chain_rtv.Reset();
|
||||
|
||||
// switch out of fullscreen before destroying
|
||||
BOOL is_fullscreen;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||
|
||||
m_swap_chain.Reset();
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
bool D3D11Device::UpdateWindow()
|
||||
{
|
||||
DestroySwapChain();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||
{
|
||||
ERROR_LOG("Failed to create swap chain on updated window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D11Device::DestroySurface()
|
||||
{
|
||||
DestroySwapChain();
|
||||
}
|
||||
|
||||
void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
{
|
||||
if (!m_swap_chain || m_is_exclusive_fullscreen)
|
||||
return;
|
||||
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
|
||||
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
|
||||
m_window_info.surface_height == static_cast<u32>(new_window_height))
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_window_info.surface_scale = new_scale;
|
||||
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
|
||||
return true;
|
||||
|
||||
m_swap_chain_rtv.Reset();
|
||||
|
||||
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
|
||||
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
|
||||
if (FAILED(hr)) [[unlikely]]
|
||||
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
|
||||
{
|
||||
Error::SetHResult(error, "ResizeBuffers() failed: ", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateSwapChainRTV())
|
||||
Panic("Failed to recreate swap chain RTV after resize");
|
||||
return CreateRTV(error);
|
||||
}
|
||||
|
||||
bool D3D11SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
|
||||
{
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen())
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
if (m_vsync_mode == mode)
|
||||
return true;
|
||||
|
||||
const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode);
|
||||
const u32 new_buffer_count = GetNewBufferCount(mode);
|
||||
m_vsync_mode = mode;
|
||||
if (old_buffer_count == new_buffer_count)
|
||||
return true;
|
||||
|
||||
// Buffer count change => needs recreation.
|
||||
m_swap_chain_rtv.Reset();
|
||||
DestroySwapChain();
|
||||
return CreateSwapChain(error) && CreateRTV(error);
|
||||
}
|
||||
|
||||
std::unique_ptr<GPUSwapChain> D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<D3D11SwapChain> ret;
|
||||
if (wi.type != WindowInfo::Type::Win32)
|
||||
{
|
||||
Error::SetStringView(error, "Cannot create a swap chain on non-win32 window.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = std::make_unique<D3D11SwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode);
|
||||
if (ret->CreateSwapChain(error) && ret->CreateRTV(error))
|
||||
{
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
m_context->ClearRenderTargetView(ret->GetRTV(), s_clear_color.data());
|
||||
ret->GetSwapChain()->Present(0, ret->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.reset();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool D3D11Device::SupportsExclusiveFullscreen() const
|
||||
@ -471,9 +497,16 @@ std::string D3D11Device::GetDriverInfo() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
void D3D11Device::ExecuteAndWaitForGPUIdle()
|
||||
void D3D11Device::FlushCommands()
|
||||
{
|
||||
m_context->Flush();
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void D3D11Device::WaitForGPUIdle()
|
||||
{
|
||||
m_context->Flush();
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
bool D3D11Device::CreateBuffers()
|
||||
@ -610,63 +643,29 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t)
|
||||
T->CommitClear(m_context.Get());
|
||||
}
|
||||
|
||||
void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
|
||||
GPUDevice::PresentResult D3D11Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
|
||||
const u32 old_buffer_count = GetSwapChainBufferCount();
|
||||
m_vsync_mode = mode;
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
if (GetSwapChainBufferCount() != old_buffer_count)
|
||||
{
|
||||
DestroySwapChain();
|
||||
if (!CreateSwapChain())
|
||||
Panic("Failed to recreate swap chain after vsync change.");
|
||||
}
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
{
|
||||
// Note: Really slow on Intel...
|
||||
m_context->Flush();
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
D3D11SwapChain* const SC = static_cast<D3D11SwapChain*>(swap_chain);
|
||||
|
||||
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||
BOOL is_fullscreen;
|
||||
if (m_is_exclusive_fullscreen &&
|
||||
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
if (SC->IsExclusiveFullscreen() &&
|
||||
(FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
{
|
||||
Host::SetFullscreen(false);
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
return PresentResult::ExclusiveFullscreenLost;
|
||||
}
|
||||
|
||||
// When using vsync, the time here seems to include the time for the buffer to become available.
|
||||
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
||||
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
|
||||
// the time, when it's more like a couple of percent.
|
||||
if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() == GPUVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), GSVector4::rgba32(clear_color).F32);
|
||||
m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr);
|
||||
m_context->ClearRenderTargetView(SC->GetRTV(), GSVector4::rgba32(clear_color).F32);
|
||||
m_context->OMSetRenderTargets(1, SC->GetRTVArray(), nullptr);
|
||||
s_stats.num_render_passes++;
|
||||
m_num_current_render_targets = 0;
|
||||
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
|
||||
@ -675,17 +674,19 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color)
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D11Device::EndPresent(bool explicit_present, u64 present_time)
|
||||
void D3D11Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
|
||||
{
|
||||
D3D11SwapChain* const SC = static_cast<D3D11SwapChain*>(swap_chain);
|
||||
DebugAssert(!explicit_present && present_time == 0);
|
||||
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
|
||||
|
||||
if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() != GPUVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
|
||||
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
|
||||
m_swap_chain->Present(sync_interval, flags);
|
||||
const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
|
||||
const UINT flags =
|
||||
(SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
|
||||
SC->GetSwapChain()->Present(sync_interval, flags);
|
||||
|
||||
if (m_gpu_timing_enabled)
|
||||
KickTimestampQuery();
|
||||
@ -693,7 +694,7 @@ void D3D11Device::EndPresent(bool explicit_present, u64 present_time)
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void D3D11Device::SubmitPresent()
|
||||
void D3D11Device::SubmitPresent(GPUSwapChain* swap_chain)
|
||||
{
|
||||
Panic("Not supported by this API.");
|
||||
}
|
||||
@ -1124,4 +1125,4 @@ void D3D11Device::DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex)
|
||||
void D3D11Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type)
|
||||
{
|
||||
Panic("Barriers are not supported");
|
||||
}
|
||||
}
|
||||
|
@ -32,21 +32,23 @@ public:
|
||||
~D3D11Device();
|
||||
|
||||
ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast<D3D11Device*>(g_gpu_device.get()); }
|
||||
ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); }
|
||||
ALWAYS_INLINE static ID3D11Device1* GetD3DDevice() { return GetInstance().m_device.Get(); }
|
||||
ALWAYS_INLINE static ID3D11DeviceContext1* GetD3DContext() { return GetInstance().m_context.Get(); }
|
||||
ALWAYS_INLINE static IDXGIFactory5* GetDXGIFactory() { return GetInstance().m_dxgi_factory.Get(); }
|
||||
ALWAYS_INLINE static D3D_FEATURE_LEVEL GetMaxFeatureLevel() { return GetInstance().m_max_feature_level; }
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
bool SupportsExclusiveFullscreen() const override;
|
||||
void DestroySurface() override;
|
||||
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ExecuteAndWaitForGPUIdle() override;
|
||||
void FlushCommands() override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) override;
|
||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data = nullptr, u32 data_stride = 0) override;
|
||||
@ -97,21 +99,21 @@ public:
|
||||
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
|
||||
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
|
||||
|
||||
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
PresentResult BeginPresent(u32 clear_color) override;
|
||||
void EndPresent(bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent() override;
|
||||
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
|
||||
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent(GPUSwapChain* swap_chain) override;
|
||||
|
||||
void UnbindPipeline(D3D11Pipeline* pl);
|
||||
void UnbindTexture(D3D11Texture* tex);
|
||||
|
||||
protected:
|
||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) override;
|
||||
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
|
||||
void DestroyDevice() override;
|
||||
|
||||
private:
|
||||
@ -136,11 +138,6 @@ private:
|
||||
|
||||
void SetFeatures(FeatureMask disabled_features);
|
||||
|
||||
u32 GetSwapChainBufferCount() const;
|
||||
bool CreateSwapChain();
|
||||
bool CreateSwapChainRTV();
|
||||
void DestroySwapChain();
|
||||
|
||||
bool CreateBuffers();
|
||||
void DestroyBuffers();
|
||||
|
||||
@ -161,8 +158,6 @@ private:
|
||||
ComPtr<ID3DUserDefinedAnnotation> m_annotation;
|
||||
|
||||
ComPtr<IDXGIFactory5> m_dxgi_factory;
|
||||
ComPtr<IDXGISwapChain1> m_swap_chain;
|
||||
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
|
||||
|
||||
RasterizationStateMap m_rasterization_states;
|
||||
DepthStateMap m_depth_states;
|
||||
@ -170,10 +165,6 @@ private:
|
||||
InputLayoutMap m_input_layouts;
|
||||
|
||||
D3D_FEATURE_LEVEL m_max_feature_level = D3D_FEATURE_LEVEL_10_0;
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_flip_model_swap_chain = true;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
|
||||
D3D11StreamBuffer m_vertex_buffer;
|
||||
D3D11StreamBuffer m_index_buffer;
|
||||
@ -207,4 +198,45 @@ private:
|
||||
float m_accumulated_gpu_time = 0.0f;
|
||||
};
|
||||
|
||||
class D3D11SwapChain : public GPUSwapChain
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
using ComPtr = Microsoft::WRL::ComPtr<T>;
|
||||
|
||||
friend D3D11Device;
|
||||
|
||||
D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode);
|
||||
~D3D11SwapChain() override;
|
||||
|
||||
ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); }
|
||||
ALWAYS_INLINE ID3D11RenderTargetView* GetRTV() const { return m_swap_chain_rtv.Get(); }
|
||||
ALWAYS_INLINE ID3D11RenderTargetView* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); }
|
||||
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
|
||||
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
|
||||
|
||||
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
|
||||
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
|
||||
|
||||
private:
|
||||
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);
|
||||
|
||||
bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode);
|
||||
|
||||
bool CreateSwapChain(Error* error);
|
||||
bool CreateRTV(Error* error);
|
||||
|
||||
void DestroySwapChain();
|
||||
|
||||
ComPtr<IDXGISwapChain1> m_swap_chain;
|
||||
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
|
||||
|
||||
ComPtr<IDXGIOutput> m_fullscreen_output;
|
||||
std::optional<DXGI_MODE_DESC> m_fullscreen_mode;
|
||||
|
||||
bool m_using_flip_model_swap_chain = true;
|
||||
bool m_using_allow_tearing = false;
|
||||
};
|
||||
|
||||
void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name);
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
|
||||
LOG_CHANNEL(D3D11Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
D3D11StreamBuffer::D3D11StreamBuffer()
|
||||
{
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
LOG_CHANNEL(D3D11Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
std::unique_ptr<GPUTexture> D3D11Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include "d3d12_texture.h"
|
||||
#include "d3d_common.h"
|
||||
|
||||
#include "core/host.h"
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/bitutils.h"
|
||||
@ -27,7 +25,7 @@
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
LOG_CHANNEL(D3D12Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
// Tweakables
|
||||
enum : u32
|
||||
@ -69,6 +67,8 @@ static u32 s_debug_scope_depth = 0;
|
||||
|
||||
D3D12Device::D3D12Device()
|
||||
{
|
||||
m_render_api = RenderAPI::D3D12;
|
||||
|
||||
#ifdef _DEBUG
|
||||
s_debug_scope_depth = 0;
|
||||
#endif
|
||||
@ -117,8 +117,11 @@ D3D12Device::ComPtr<ID3D12RootSignature> D3D12Device::CreateRootSignature(const
|
||||
return rs;
|
||||
}
|
||||
|
||||
bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error)
|
||||
bool D3D12Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_instance_mutex);
|
||||
|
||||
@ -238,8 +241,13 @@ bool D3D12Device::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
||||
if (!CreateCommandLists(error) || !CreateDescriptorHeaps(error))
|
||||
return false;
|
||||
|
||||
if (!m_window_info.IsSurfaceless() && !CreateSwapChain(error))
|
||||
return false;
|
||||
if (!wi.IsSurfaceless())
|
||||
{
|
||||
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
|
||||
exclusive_fullscreen_control, error);
|
||||
if (!m_main_swap_chain)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateRootSignatures(error) || !CreateBuffers(error))
|
||||
return false;
|
||||
@ -256,7 +264,9 @@ void D3D12Device::DestroyDevice()
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
WaitForGPUIdle();
|
||||
WaitForAllFences();
|
||||
|
||||
m_main_swap_chain.reset();
|
||||
|
||||
DestroyDeferredObjects(m_current_fence_value);
|
||||
DestroySamplers();
|
||||
@ -264,7 +274,6 @@ void D3D12Device::DestroyDevice()
|
||||
DestroyBuffers();
|
||||
DestroyDescriptorHeaps();
|
||||
DestroyRootSignatures();
|
||||
DestroySwapChain();
|
||||
DestroyCommandLists();
|
||||
|
||||
m_pipeline_library.Reset();
|
||||
@ -656,7 +665,7 @@ void D3D12Device::WaitForFence(u64 fence)
|
||||
DestroyDeferredObjects(m_completed_fence_value);
|
||||
}
|
||||
|
||||
void D3D12Device::WaitForGPUIdle()
|
||||
void D3D12Device::WaitForAllFences()
|
||||
{
|
||||
u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
|
||||
for (u32 i = 0; i < (NUM_COMMAND_LISTS - 1); i++)
|
||||
@ -666,7 +675,16 @@ void D3D12Device::WaitForGPUIdle()
|
||||
}
|
||||
}
|
||||
|
||||
void D3D12Device::ExecuteAndWaitForGPUIdle()
|
||||
void D3D12Device::FlushCommands()
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
SubmitCommandList(false);
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void D3D12Device::WaitForGPUIdle()
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
@ -790,54 +808,54 @@ void D3D12Device::DestroyDeferredObjects(u64 fence_value)
|
||||
}
|
||||
}
|
||||
|
||||
bool D3D12Device::HasSurface() const
|
||||
D3D12SwapChain::D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode)
|
||||
: GPUSwapChain(wi, vsync_mode, allow_present_throttle)
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
if (fullscreen_mode)
|
||||
InitializeExclusiveFullscreenMode(fullscreen_mode);
|
||||
}
|
||||
|
||||
u32 D3D12Device::GetSwapChainBufferCount() const
|
||||
D3D12SwapChain::~D3D12SwapChain()
|
||||
{
|
||||
// With vsync off, we only need two buffers. Same for blocking vsync.
|
||||
// With triple buffering, we need three.
|
||||
return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
|
||||
DestroyRTVs();
|
||||
DestroySwapChain();
|
||||
}
|
||||
|
||||
bool D3D12Device::CreateSwapChain(Error* error)
|
||||
bool D3D12SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode)
|
||||
{
|
||||
if (m_window_info.type != WindowInfo::Type::Win32)
|
||||
{
|
||||
Error::SetStringView(error, "D3D12 expects a Win32 window.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
|
||||
|
||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||
RECT client_rc{};
|
||||
GetClientRect(window_hwnd, &client_rc);
|
||||
|
||||
DXGI_MODE_DESC fullscreen_mode = {};
|
||||
ComPtr<IDXGIOutput> fullscreen_output;
|
||||
if (Host::IsFullscreen())
|
||||
{
|
||||
u32 fullscreen_width, fullscreen_height;
|
||||
float fullscreen_refresh_rate;
|
||||
m_is_exclusive_fullscreen =
|
||||
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width,
|
||||
fullscreen_height, fullscreen_refresh_rate, fm.resource_format,
|
||||
&fullscreen_mode, fullscreen_output.GetAddressOf());
|
||||
m_fullscreen_mode =
|
||||
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode,
|
||||
fm.resource_format, m_fullscreen_output.GetAddressOf());
|
||||
return m_fullscreen_mode.has_value();
|
||||
}
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
m_vsync_mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
}
|
||||
else
|
||||
u32 D3D12SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode)
|
||||
{
|
||||
// With vsync off, we only need two buffers. Same for blocking vsync.
|
||||
// With triple buffering, we need three.
|
||||
return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2;
|
||||
}
|
||||
|
||||
bool D3D12SwapChain::CreateSwapChain(D3D12Device& dev, Error* error)
|
||||
{
|
||||
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format);
|
||||
|
||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||
RECT client_rc{};
|
||||
GetClientRect(window_hwnd, &client_rc);
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox)
|
||||
{
|
||||
m_is_exclusive_fullscreen = false;
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
m_vsync_mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||
@ -845,45 +863,44 @@ bool D3D12Device::CreateSwapChain(Error* error)
|
||||
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||
swap_chain_desc.Format = fm.resource_format;
|
||||
swap_chain_desc.SampleDesc.Count = 1;
|
||||
swap_chain_desc.BufferCount = GetSwapChainBufferCount();
|
||||
swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode);
|
||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||
|
||||
m_using_allow_tearing = (m_allow_tearing_supported && !m_is_exclusive_fullscreen);
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (m_is_exclusive_fullscreen)
|
||||
if (IsExclusiveFullscreen())
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||
|
||||
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||
fs_sd_desc.Width = m_fullscreen_mode->Width;
|
||||
fs_sd_desc.Height = m_fullscreen_mode->Height;
|
||||
fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate;
|
||||
fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering;
|
||||
fs_desc.Scaling = m_fullscreen_mode->Scaling;
|
||||
fs_desc.Windowed = FALSE;
|
||||
|
||||
VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
|
||||
fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
|
||||
hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &fs_sd_desc, &fs_desc,
|
||||
m_fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
|
||||
m_is_exclusive_fullscreen = false;
|
||||
m_using_allow_tearing = m_allow_tearing_supported;
|
||||
m_fullscreen_output.Reset();
|
||||
m_fullscreen_mode.reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_is_exclusive_fullscreen)
|
||||
if (!IsExclusiveFullscreen())
|
||||
{
|
||||
VERBOSE_LOG("Creating a {}x{} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
|
||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
|
||||
m_swap_chain.ReleaseAndGetAddressOf());
|
||||
m_using_allow_tearing = D3DCommon::SupportsAllowTearing(dev.GetDXGIFactory());
|
||||
if (m_using_allow_tearing)
|
||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &swap_chain_desc, nullptr,
|
||||
nullptr, m_swap_chain.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr);
|
||||
@ -891,22 +908,14 @@ bool D3D12Device::CreateSwapChain(Error* error)
|
||||
}
|
||||
}
|
||||
|
||||
hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
|
||||
hr = dev.GetDXGIFactory()->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
|
||||
if (FAILED(hr))
|
||||
WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||
|
||||
if (!CreateSwapChainRTV(error))
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
RenderBlankFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool D3D12Device::CreateSwapChainRTV(Error* error)
|
||||
bool D3D12SwapChain::CreateRTV(D3D12Device& dev, Error* error)
|
||||
{
|
||||
DXGI_SWAP_CHAIN_DESC swap_chain_desc;
|
||||
HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc);
|
||||
@ -925,148 +934,162 @@ bool D3D12Device::CreateSwapChainRTV(Error* error)
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Error::SetHResult(error, "GetBuffer for RTV failed: ", hr);
|
||||
DestroySwapChainRTVs();
|
||||
DestroyRTVs();
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12::SetObjectName(backbuffer.Get(), TinyString::from_format("Swap Chain Buffer #{}", i));
|
||||
|
||||
D3D12DescriptorHandle rtv;
|
||||
if (!m_rtv_heap_manager.Allocate(&rtv))
|
||||
if (!dev.GetRTVHeapManager().Allocate(&rtv))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to allocate RTV handle.");
|
||||
DestroySwapChainRTVs();
|
||||
DestroyRTVs();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv);
|
||||
dev.GetDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv);
|
||||
m_swap_chain_buffers.emplace_back(std::move(backbuffer), rtv);
|
||||
}
|
||||
|
||||
m_window_info.surface_width = swap_chain_desc.BufferDesc.Width;
|
||||
m_window_info.surface_height = swap_chain_desc.BufferDesc.Height;
|
||||
m_window_info.surface_width = static_cast<u16>(swap_chain_desc.BufferDesc.Width);
|
||||
m_window_info.surface_height = static_cast<u16>(swap_chain_desc.BufferDesc.Height);
|
||||
m_window_info.surface_format = s_swap_chain_format;
|
||||
VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
|
||||
|
||||
if (m_window_info.type == WindowInfo::Type::Win32)
|
||||
BOOL fullscreen = FALSE;
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
|
||||
SUCCEEDED(m_swap_chain->GetDesc(&desc)))
|
||||
{
|
||||
BOOL fullscreen = FALSE;
|
||||
DXGI_SWAP_CHAIN_DESC desc;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
|
||||
SUCCEEDED(m_swap_chain->GetDesc(&desc)))
|
||||
{
|
||||
m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
|
||||
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
|
||||
}
|
||||
m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
|
||||
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
|
||||
}
|
||||
|
||||
m_current_swap_chain_buffer = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12Device::DestroySwapChainRTVs()
|
||||
void D3D12SwapChain::DestroyRTVs()
|
||||
{
|
||||
if (m_swap_chain_buffers.empty())
|
||||
return;
|
||||
|
||||
D3D12Device& dev = D3D12Device::GetInstance();
|
||||
|
||||
// Runtime gets cranky if we don't submit the current buffer...
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
SubmitCommandList(true);
|
||||
if (dev.InRenderPass())
|
||||
dev.EndRenderPass();
|
||||
dev.SubmitCommandList(true);
|
||||
|
||||
for (auto it = m_swap_chain_buffers.rbegin(); it != m_swap_chain_buffers.rend(); ++it)
|
||||
{
|
||||
m_rtv_heap_manager.Free(it->second.index);
|
||||
dev.GetRTVHeapManager().Free(it->second.index);
|
||||
it->first.Reset();
|
||||
}
|
||||
m_swap_chain_buffers.clear();
|
||||
m_current_swap_chain_buffer = 0;
|
||||
}
|
||||
|
||||
void D3D12Device::DestroySwapChain()
|
||||
void D3D12SwapChain::DestroySwapChain()
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
DestroySwapChainRTVs();
|
||||
|
||||
// switch out of fullscreen before destroying
|
||||
BOOL is_fullscreen;
|
||||
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||
|
||||
m_swap_chain.Reset();
|
||||
m_is_exclusive_fullscreen = false;
|
||||
}
|
||||
|
||||
void D3D12Device::RenderBlankFrame()
|
||||
bool D3D12SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr);
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_RESOURCE_STATE_PRESENT);
|
||||
SubmitCommandList(false);
|
||||
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
}
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen())
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
bool D3D12Device::UpdateWindow()
|
||||
{
|
||||
WaitForGPUIdle();
|
||||
DestroySwapChain();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.IsSurfaceless())
|
||||
if (m_vsync_mode == mode)
|
||||
return true;
|
||||
|
||||
Error error;
|
||||
if (!CreateSwapChain(&error))
|
||||
{
|
||||
ERROR_LOG("Failed to create swap chain on updated window: {}", error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode);
|
||||
const u32 new_buffer_count = GetNewBufferCount(mode);
|
||||
m_vsync_mode = mode;
|
||||
if (old_buffer_count == new_buffer_count)
|
||||
return true;
|
||||
|
||||
RenderBlankFrame();
|
||||
return true;
|
||||
// Buffer count change => needs recreation.
|
||||
DestroyRTVs();
|
||||
DestroySwapChain();
|
||||
|
||||
D3D12Device& dev = D3D12Device::GetInstance();
|
||||
return CreateSwapChain(dev, error) && CreateRTV(dev, error);
|
||||
}
|
||||
|
||||
void D3D12Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
m_window_info.surface_scale = new_scale;
|
||||
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
|
||||
return true;
|
||||
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
|
||||
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
|
||||
m_window_info.surface_height == static_cast<u32>(new_window_height))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DestroySwapChainRTVs();
|
||||
DestroyRTVs();
|
||||
|
||||
HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
|
||||
m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
|
||||
if (FAILED(hr))
|
||||
ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
|
||||
|
||||
Error error;
|
||||
if (!CreateSwapChainRTV(&error))
|
||||
{
|
||||
ERROR_LOG("Failed to recreate swap chain RTV after resize", error.GetDescription());
|
||||
Panic("Failed to recreate swap chain RTV after resize");
|
||||
}
|
||||
return CreateRTV(D3D12Device::GetInstance(), error);
|
||||
}
|
||||
|
||||
void D3D12Device::DestroySurface()
|
||||
std::unique_ptr<GPUSwapChain> D3D12Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error)
|
||||
{
|
||||
DestroySwapChainRTVs();
|
||||
DestroySwapChain();
|
||||
std::unique_ptr<D3D12SwapChain> ret;
|
||||
if (wi.type != WindowInfo::Type::Win32)
|
||||
{
|
||||
Error::SetStringView(error, "Cannot create a swap chain on non-win32 window.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = std::make_unique<D3D12SwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode);
|
||||
if (ret->CreateSwapChain(*this, error) && ret->CreateRTV(*this, error))
|
||||
{
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
RenderBlankFrame(ret.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.reset();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void D3D12Device::RenderBlankFrame(D3D12SwapChain* swap_chain)
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
const D3D12SwapChain::BufferPair& swap_chain_buf = swap_chain->GetCurrentBuffer();
|
||||
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr);
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_RESOURCE_STATE_PRESENT);
|
||||
SubmitCommandList(false);
|
||||
swap_chain->GetSwapChain()->Present(0, swap_chain->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||
swap_chain->AdvanceBuffer();
|
||||
}
|
||||
|
||||
bool D3D12Device::SupportsTextureFormat(GPUTexture::Format format) const
|
||||
@ -1105,79 +1128,77 @@ std::string D3D12Device::GetDriverInfo() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
|
||||
{
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
|
||||
if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen)
|
||||
{
|
||||
WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
|
||||
mode = GPUVSyncMode::FIFO;
|
||||
}
|
||||
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
|
||||
const u32 old_buffer_count = GetSwapChainBufferCount();
|
||||
m_vsync_mode = mode;
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
if (GetSwapChainBufferCount() != old_buffer_count)
|
||||
{
|
||||
DestroySwapChain();
|
||||
|
||||
Error error;
|
||||
if (!CreateSwapChain(&error))
|
||||
{
|
||||
ERROR_LOG("Failed to recreate swap chain after vsync change: {}", error.GetDescription());
|
||||
Panic("Failed to recreate swap chain after vsync change.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult D3D12Device::BeginPresent(u32 clear_color)
|
||||
GPUDevice::PresentResult D3D12Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
if (m_device_was_lost) [[unlikely]]
|
||||
return PresentResult::DeviceLost;
|
||||
|
||||
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
|
||||
if (!m_swap_chain)
|
||||
{
|
||||
SubmitCommandList(false);
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
|
||||
// TODO: Check if the device was lost.
|
||||
|
||||
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||
BOOL is_fullscreen;
|
||||
if (m_is_exclusive_fullscreen &&
|
||||
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
if (SC->IsExclusiveFullscreen() &&
|
||||
(FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||
{
|
||||
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
|
||||
FlushCommands();
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
return PresentResult::ExclusiveFullscreenLost;
|
||||
}
|
||||
|
||||
BeginSwapChainRenderPass(clear_color);
|
||||
m_current_swap_chain = SC;
|
||||
|
||||
const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer();
|
||||
ID3D12GraphicsCommandList4* const cmdlist = GetCommandList();
|
||||
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
|
||||
// All textures should be in shader read only optimal already, but just in case..
|
||||
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
|
||||
for (u32 i = 0; i < num_textures; i++)
|
||||
{
|
||||
if (m_current_textures[i])
|
||||
m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
||||
}
|
||||
|
||||
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second,
|
||||
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}},
|
||||
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
|
||||
GSVector4::store<false>(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color));
|
||||
cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE);
|
||||
|
||||
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
|
||||
m_num_current_render_targets = 0;
|
||||
m_dirty_flags =
|
||||
(m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0);
|
||||
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
|
||||
m_current_depth_target = nullptr;
|
||||
m_in_render_pass = true;
|
||||
s_stats.num_render_passes++;
|
||||
|
||||
// Clear pipeline, it's likely incompatible.
|
||||
m_current_pipeline = nullptr;
|
||||
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void D3D12Device::EndPresent(bool explicit_present, u64 present_time)
|
||||
void D3D12Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
|
||||
{
|
||||
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
|
||||
DebugAssert(present_time == 0);
|
||||
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
|
||||
EndRenderPass();
|
||||
|
||||
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
DebugAssert(SC == m_current_swap_chain);
|
||||
m_current_swap_chain = nullptr;
|
||||
|
||||
const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer();
|
||||
SC->AdvanceBuffer();
|
||||
|
||||
ID3D12GraphicsCommandList* cmdlist = GetCommandList();
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
@ -1187,18 +1208,19 @@ void D3D12Device::EndPresent(bool explicit_present, u64 present_time)
|
||||
TrimTexturePool();
|
||||
|
||||
if (!explicit_present)
|
||||
SubmitPresent();
|
||||
SubmitPresent(swap_chain);
|
||||
}
|
||||
|
||||
void D3D12Device::SubmitPresent()
|
||||
void D3D12Device::SubmitPresent(GPUSwapChain* swap_chain)
|
||||
{
|
||||
DebugAssert(m_swap_chain);
|
||||
D3D12SwapChain* const SC = static_cast<D3D12SwapChain*>(swap_chain);
|
||||
if (m_device_was_lost) [[unlikely]]
|
||||
return;
|
||||
|
||||
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
|
||||
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
|
||||
m_swap_chain->Present(sync_interval, flags);
|
||||
const UINT sync_interval = static_cast<UINT>(SC->GetVSyncMode() == GPUVSyncMode::FIFO);
|
||||
const UINT flags =
|
||||
(SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0;
|
||||
SC->GetSwapChain()->Present(sync_interval, flags);
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
@ -1251,7 +1273,6 @@ void D3D12Device::InsertDebugMessage(const char* msg)
|
||||
|
||||
void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features)
|
||||
{
|
||||
m_render_api = RenderAPI::D3D12;
|
||||
m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level);
|
||||
m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
m_max_multisamples = 1;
|
||||
@ -1286,11 +1307,6 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab
|
||||
m_features.pipeline_cache = true;
|
||||
m_features.prefer_unused_textures = true;
|
||||
|
||||
BOOL allow_tearing_supported = false;
|
||||
HRESULT hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
|
||||
sizeof(allow_tearing_supported));
|
||||
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
||||
|
||||
m_features.raster_order_views = false;
|
||||
if (!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS))
|
||||
{
|
||||
@ -1841,7 +1857,7 @@ void D3D12Device::BeginRenderPass()
|
||||
else
|
||||
{
|
||||
// Re-rendering to swap chain.
|
||||
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
const auto& swap_chain_buf = m_current_swap_chain->GetCurrentBuffer();
|
||||
rt_desc[0] = {swap_chain_buf.second,
|
||||
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, {}},
|
||||
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
|
||||
@ -1869,43 +1885,6 @@ void D3D12Device::BeginRenderPass()
|
||||
SetInitialPipelineState();
|
||||
}
|
||||
|
||||
void D3D12Device::BeginSwapChainRenderPass(u32 clear_color)
|
||||
{
|
||||
DebugAssert(!InRenderPass());
|
||||
|
||||
ID3D12GraphicsCommandList4* const cmdlist = GetCommandList();
|
||||
const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||
|
||||
D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
|
||||
// All textures should be in shader read only optimal already, but just in case..
|
||||
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
|
||||
for (u32 i = 0; i < num_textures; i++)
|
||||
{
|
||||
if (m_current_textures[i])
|
||||
m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
||||
}
|
||||
|
||||
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second,
|
||||
{D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}},
|
||||
{D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}};
|
||||
GSVector4::store<false>(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color));
|
||||
cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE);
|
||||
|
||||
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
|
||||
m_num_current_render_targets = 0;
|
||||
m_dirty_flags =
|
||||
(m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0);
|
||||
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
|
||||
m_current_depth_target = nullptr;
|
||||
m_in_render_pass = true;
|
||||
s_stats.num_render_passes++;
|
||||
|
||||
// Clear pipeline, it's likely incompatible.
|
||||
m_current_pipeline = nullptr;
|
||||
}
|
||||
|
||||
bool D3D12Device::InRenderPass()
|
||||
{
|
||||
return m_in_render_pass;
|
||||
|
@ -37,6 +37,8 @@ namespace D3D12MA {
|
||||
class Allocator;
|
||||
}
|
||||
|
||||
class D3D12SwapChain;
|
||||
|
||||
class D3D12Device final : public GPUDevice
|
||||
{
|
||||
public:
|
||||
@ -58,17 +60,16 @@ public:
|
||||
D3D12Device();
|
||||
~D3D12Device() override;
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
|
||||
void DestroySurface() override;
|
||||
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ExecuteAndWaitForGPUIdle() override;
|
||||
void FlushCommands() override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) override;
|
||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data = nullptr, u32 data_stride = 0) override;
|
||||
@ -119,23 +120,22 @@ public:
|
||||
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
|
||||
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
|
||||
|
||||
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
PresentResult BeginPresent(u32 clear_color) override;
|
||||
void EndPresent(bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent() override;
|
||||
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
|
||||
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent(GPUSwapChain* swap_chain) override;
|
||||
|
||||
// Global state accessors
|
||||
ALWAYS_INLINE static D3D12Device& GetInstance() { return *static_cast<D3D12Device*>(g_gpu_device.get()); }
|
||||
ALWAYS_INLINE IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); }
|
||||
ALWAYS_INLINE ID3D12Device1* GetDevice() const { return m_device.Get(); }
|
||||
ALWAYS_INLINE ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.Get(); }
|
||||
ALWAYS_INLINE IDXGIFactory5* GetDXGIFactory() { return m_dxgi_factory.Get(); }
|
||||
ALWAYS_INLINE D3D12MA::Allocator* GetAllocator() const { return m_allocator.Get(); }
|
||||
|
||||
void WaitForGPUIdle();
|
||||
void WaitForAllFences();
|
||||
|
||||
// Descriptor manager access.
|
||||
D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; }
|
||||
@ -173,6 +173,12 @@ public:
|
||||
// Also invokes callbacks for completion.
|
||||
void WaitForFence(u64 fence_counter);
|
||||
|
||||
// Ends a render pass if we're currently in one.
|
||||
// When Bind() is next called, the pass will be restarted.
|
||||
void BeginRenderPass();
|
||||
void EndRenderPass();
|
||||
bool InRenderPass();
|
||||
|
||||
/// Ends any render pass, executes the command buffer, and invalidates cached state.
|
||||
void SubmitCommandList(bool wait_for_completion);
|
||||
void SubmitCommandList(bool wait_for_completion, const std::string_view reason);
|
||||
@ -183,8 +189,10 @@ public:
|
||||
void UnbindTextureBuffer(D3D12TextureBuffer* buf);
|
||||
|
||||
protected:
|
||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) override;
|
||||
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
|
||||
void DestroyDevice() override;
|
||||
|
||||
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
|
||||
@ -232,12 +240,6 @@ private:
|
||||
void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr);
|
||||
void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features);
|
||||
|
||||
u32 GetSwapChainBufferCount() const;
|
||||
bool CreateSwapChain(Error* error);
|
||||
bool CreateSwapChainRTV(Error* error);
|
||||
void DestroySwapChainRTVs();
|
||||
void DestroySwapChain();
|
||||
|
||||
bool CreateCommandLists(Error* error);
|
||||
void DestroyCommandLists();
|
||||
bool CreateRootSignatures(Error* error);
|
||||
@ -252,7 +254,7 @@ private:
|
||||
void DestroySamplers();
|
||||
void DestroyDeferredObjects(u64 fence_value);
|
||||
|
||||
void RenderBlankFrame();
|
||||
void RenderBlankFrame(D3D12SwapChain* swap_chain);
|
||||
void MoveToNextCommandList();
|
||||
|
||||
bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
|
||||
@ -280,13 +282,6 @@ private:
|
||||
bool UpdateParametersForLayout(u32 dirty);
|
||||
bool UpdateRootParameters(u32 dirty);
|
||||
|
||||
// Ends a render pass if we're currently in one.
|
||||
// When Bind() is next called, the pass will be restarted.
|
||||
void BeginRenderPass();
|
||||
void BeginSwapChainRenderPass(u32 clear_color);
|
||||
void EndRenderPass();
|
||||
bool InRenderPass();
|
||||
|
||||
ComPtr<IDXGIAdapter1> m_adapter;
|
||||
ComPtr<ID3D12Device1> m_device;
|
||||
ComPtr<ID3D12CommandQueue> m_command_queue;
|
||||
@ -299,15 +294,9 @@ private:
|
||||
|
||||
std::array<CommandList, NUM_COMMAND_LISTS> m_command_lists;
|
||||
u32 m_current_command_list = NUM_COMMAND_LISTS - 1;
|
||||
bool m_device_was_lost = false;
|
||||
|
||||
ComPtr<IDXGIFactory5> m_dxgi_factory;
|
||||
ComPtr<IDXGISwapChain1> m_swap_chain;
|
||||
std::vector<std::pair<ComPtr<ID3D12Resource>, D3D12DescriptorHandle>> m_swap_chain_buffers;
|
||||
u32 m_current_swap_chain_buffer = 0;
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_allow_tearing = false;
|
||||
bool m_is_exclusive_fullscreen = false;
|
||||
bool m_device_was_lost = false;
|
||||
|
||||
D3D12DescriptorHeapManager m_descriptor_heap_manager;
|
||||
D3D12DescriptorHeapManager m_rtv_heap_manager;
|
||||
@ -358,4 +347,52 @@ private:
|
||||
D3D12TextureBuffer* m_current_texture_buffer = nullptr;
|
||||
GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1);
|
||||
GSVector4i m_current_scissor = {};
|
||||
|
||||
D3D12SwapChain* m_current_swap_chain = nullptr;
|
||||
};
|
||||
|
||||
class D3D12SwapChain : public GPUSwapChain
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
using ComPtr = Microsoft::WRL::ComPtr<T>;
|
||||
|
||||
friend D3D12Device;
|
||||
|
||||
using BufferPair = std::pair<ComPtr<ID3D12Resource>, D3D12DescriptorHandle>;
|
||||
|
||||
D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode);
|
||||
~D3D12SwapChain() override;
|
||||
|
||||
ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); }
|
||||
ALWAYS_INLINE const BufferPair& GetCurrentBuffer() const { return m_swap_chain_buffers[m_current_swap_chain_buffer]; }
|
||||
ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; }
|
||||
ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); }
|
||||
|
||||
void AdvanceBuffer()
|
||||
{
|
||||
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||
}
|
||||
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
|
||||
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
|
||||
|
||||
private:
|
||||
static u32 GetNewBufferCount(GPUVSyncMode vsync_mode);
|
||||
|
||||
bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode);
|
||||
|
||||
bool CreateSwapChain(D3D12Device& dev, Error* error);
|
||||
bool CreateRTV(D3D12Device& dev, Error* error);
|
||||
|
||||
void DestroySwapChain();
|
||||
void DestroyRTVs();
|
||||
|
||||
ComPtr<IDXGISwapChain1> m_swap_chain;
|
||||
std::vector<BufferPair> m_swap_chain_buffers;
|
||||
u32 m_current_swap_chain_buffer = 0;
|
||||
bool m_using_allow_tearing = false;
|
||||
|
||||
ComPtr<IDXGIOutput> m_fullscreen_output;
|
||||
std::optional<DXGI_MODE_DESC> m_fullscreen_mode;
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
LOG_CHANNEL(D3D12Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(stage), m_bytecode(std::move(bytecode))
|
||||
{
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LOG_CHANNEL(D3D12StreamBuffer);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
D3D12StreamBuffer::D3D12StreamBuffer() = default;
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
#include "D3D12MemAlloc.h"
|
||||
|
||||
LOG_CHANNEL(D3D12Device);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
|
||||
DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include <dxcapi.h>
|
||||
#include <dxgi1_5.h>
|
||||
|
||||
LOG_CHANNEL(D3DCommon);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
namespace D3DCommon {
|
||||
namespace {
|
||||
@ -123,6 +123,14 @@ Microsoft::WRL::ComPtr<IDXGIFactory5> D3DCommon::CreateFactory(bool debug, Error
|
||||
return factory;
|
||||
}
|
||||
|
||||
bool D3DCommon::SupportsAllowTearing(IDXGIFactory5* factory)
|
||||
{
|
||||
BOOL allow_tearing_supported = false;
|
||||
HRESULT hr = factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
|
||||
sizeof(allow_tearing_supported));
|
||||
return (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
||||
}
|
||||
|
||||
static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList& adapter_names, std::string adapter_name)
|
||||
{
|
||||
if (std::any_of(adapter_names.begin(), adapter_names.end(),
|
||||
@ -183,9 +191,11 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
|
||||
{
|
||||
for (const DXGI_MODE_DESC& mode : dmodes)
|
||||
{
|
||||
ai.fullscreen_modes.push_back(GPUDevice::GetFullscreenModeString(
|
||||
mode.Width, mode.Height,
|
||||
static_cast<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
|
||||
ai.fullscreen_modes.push_back(
|
||||
GPUDevice::ExclusiveFullscreenMode{.width = mode.Width,
|
||||
.height = mode.Height,
|
||||
.refresh_rate = static_cast<float>(mode.RefreshRate.Numerator) /
|
||||
static_cast<float>(mode.RefreshRate.Denominator)});
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -211,10 +221,13 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList()
|
||||
return adapters;
|
||||
}
|
||||
|
||||
bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
|
||||
u32 height, float refresh_rate, DXGI_FORMAT format,
|
||||
DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output)
|
||||
std::optional<DXGI_MODE_DESC>
|
||||
D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
|
||||
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
|
||||
DXGI_FORMAT format, IDXGIOutput** output)
|
||||
{
|
||||
std::optional<DXGI_MODE_DESC> ret;
|
||||
|
||||
// We need to find which monitor the window is located on.
|
||||
const GSVector4i client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);
|
||||
|
||||
@ -260,7 +273,7 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
|
||||
if (!first_output)
|
||||
{
|
||||
ERROR_LOG("No DXGI output found. Can't use exclusive fullscreen.");
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
WARNING_LOG("No DXGI output found for window, using first.");
|
||||
@ -268,22 +281,25 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
|
||||
}
|
||||
|
||||
DXGI_MODE_DESC request_mode = {};
|
||||
request_mode.Width = width;
|
||||
request_mode.Height = height;
|
||||
request_mode.Width = requested_fullscreen_mode->width;
|
||||
request_mode.Height = requested_fullscreen_mode->height;
|
||||
request_mode.Format = format;
|
||||
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
|
||||
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(requested_fullscreen_mode->refresh_rate * 1000.0f));
|
||||
request_mode.RefreshRate.Denominator = 1000u;
|
||||
|
||||
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
|
||||
ret = DXGI_MODE_DESC();
|
||||
|
||||
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, &ret.value(), nullptr)) ||
|
||||
request_mode.Format != format)
|
||||
{
|
||||
ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
|
||||
return false;
|
||||
ret.reset();
|
||||
return ret;
|
||||
}
|
||||
|
||||
*output = intersecting_output.Get();
|
||||
intersecting_output->AddRef();
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> D3DCommon::GetAdapterByName(IDXGIFactory5* factory, std::string_view name)
|
||||
|
@ -35,14 +35,16 @@ D3D_FEATURE_LEVEL GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter);
|
||||
|
||||
// create a dxgi factory
|
||||
Microsoft::WRL::ComPtr<IDXGIFactory5> CreateFactory(bool debug, Error* error);
|
||||
bool SupportsAllowTearing(IDXGIFactory5* factory);
|
||||
|
||||
// returns a list of all adapter names
|
||||
GPUDevice::AdapterInfoList GetAdapterInfoList();
|
||||
|
||||
// returns the fullscreen mode to use for the specified dimensions
|
||||
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
|
||||
float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode,
|
||||
IDXGIOutput** output);
|
||||
std::optional<DXGI_MODE_DESC>
|
||||
GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect,
|
||||
const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode,
|
||||
DXGI_FORMAT format, IDXGIOutput** output);
|
||||
|
||||
// get an adapter based on name
|
||||
Microsoft::WRL::ComPtr<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, std::string_view name);
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
#include "gpu_device.h"
|
||||
#include "compress_helpers.h"
|
||||
#include "core/host.h" // TODO: Remove, needed for getting fullscreen mode.
|
||||
#include "core/settings.h" // TODO: Remove, needed for dump directory.
|
||||
#include "gpu_framebuffer_manager.h"
|
||||
#include "shadergen.h"
|
||||
|
||||
@ -44,6 +42,7 @@ LOG_CHANNEL(GPUDevice);
|
||||
|
||||
std::unique_ptr<GPUDevice> g_gpu_device;
|
||||
|
||||
static std::string s_shader_dump_path;
|
||||
static std::string s_pipeline_cache_path;
|
||||
static size_t s_pipeline_cache_size;
|
||||
static std::array<u8, SHA1Digest::DIGEST_SIZE> s_pipeline_cache_hash;
|
||||
@ -226,6 +225,49 @@ size_t GPUFramebufferManagerBase::KeyHash::operator()(const Key& key) const
|
||||
return XXH32(&key, sizeof(key), 0x1337);
|
||||
}
|
||||
|
||||
GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
: m_window_info(wi), m_vsync_mode(vsync_mode), m_allow_present_throttle(allow_present_throttle)
|
||||
{
|
||||
}
|
||||
|
||||
GPUSwapChain::~GPUSwapChain() = default;
|
||||
|
||||
bool GPUSwapChain::ShouldSkipPresentingFrame()
|
||||
{
|
||||
// Only needed with FIFO. But since we're so fast, we allow it always.
|
||||
if (!m_allow_present_throttle)
|
||||
return false;
|
||||
|
||||
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
|
||||
const float throttle_period = 1.0f / throttle_rate;
|
||||
|
||||
const u64 now = Common::Timer::GetCurrentValue();
|
||||
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
|
||||
if (diff < throttle_period)
|
||||
return true;
|
||||
|
||||
m_last_frame_displayed_time = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GPUSwapChain::ThrottlePresentation()
|
||||
{
|
||||
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
|
||||
|
||||
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
|
||||
const u64 current_ts = Common::Timer::GetCurrentValue();
|
||||
|
||||
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
|
||||
// allow time for the actual rendering.
|
||||
const u64 max_variance = sleep_period * 2;
|
||||
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
|
||||
m_last_frame_displayed_time = current_ts + sleep_period;
|
||||
else
|
||||
m_last_frame_displayed_time += sleep_period;
|
||||
|
||||
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
|
||||
}
|
||||
|
||||
GPUDevice::GPUDevice()
|
||||
{
|
||||
ResetStatistics();
|
||||
@ -346,21 +388,18 @@ GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version,
|
||||
bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle,
|
||||
std::optional<bool> exclusive_fullscreen_control, FeatureMask disabled_features, Error* error)
|
||||
bool GPUDevice::Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
|
||||
std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
m_vsync_mode = vsync;
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
m_debug_device = debug_device;
|
||||
s_shader_dump_path = shader_dump_path;
|
||||
|
||||
if (!AcquireWindow(true))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to acquire window from host.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateDevice(adapter, exclusive_fullscreen_control, disabled_features, error))
|
||||
INFO_LOG("Main render window is {}x{}.", wi.surface_width, wi.surface_height);
|
||||
if (!CreateDeviceAndMainSwapChain(adapter, disabled_features, wi, vsync, allow_present_throttle,
|
||||
exclusive_fullscreen_mode, exclusive_fullscreen_control, error))
|
||||
{
|
||||
if (error && !error->IsValid())
|
||||
error->SetStringView("Failed to create device.");
|
||||
@ -383,14 +422,30 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p
|
||||
|
||||
void GPUDevice::Destroy()
|
||||
{
|
||||
s_shader_dump_path = {};
|
||||
|
||||
PurgeTexturePool();
|
||||
if (HasSurface())
|
||||
DestroySurface();
|
||||
DestroyResources();
|
||||
CloseShaderCache();
|
||||
DestroyDevice();
|
||||
}
|
||||
|
||||
bool GPUDevice::RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
|
||||
m_main_swap_chain.reset();
|
||||
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
|
||||
exclusive_fullscreen_control, error);
|
||||
return static_cast<bool>(m_main_swap_chain);
|
||||
}
|
||||
|
||||
void GPUDevice::DestroyMainSwapChain()
|
||||
{
|
||||
m_main_swap_chain.reset();
|
||||
}
|
||||
|
||||
bool GPUDevice::SupportsExclusiveFullscreen() const
|
||||
{
|
||||
return false;
|
||||
@ -533,17 +588,6 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GPUDevice::AcquireWindow(bool recreate_window)
|
||||
{
|
||||
std::optional<WindowInfo> wi = Host::AcquireRenderWindow(recreate_window);
|
||||
if (!wi.has_value())
|
||||
return false;
|
||||
|
||||
INFO_LOG("Render window is {}x{}.", wi->surface_width, wi->surface_height);
|
||||
m_window_info = wi.value();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPUDevice::CreateResources(Error* error)
|
||||
{
|
||||
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) ||
|
||||
@ -587,7 +631,7 @@ bool GPUDevice::CreateResources(Error* error)
|
||||
plconfig.depth = GPUPipeline::DepthState::GetNoTestsState();
|
||||
plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState();
|
||||
plconfig.blend.write_mask = 0x7;
|
||||
plconfig.SetTargetFormats(HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8);
|
||||
plconfig.SetTargetFormats(m_main_swap_chain ? m_main_swap_chain->GetFormat() : GPUTexture::Format::RGBA8);
|
||||
plconfig.samples = 1;
|
||||
plconfig.per_sample_shading = false;
|
||||
plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags;
|
||||
@ -619,23 +663,23 @@ void GPUDevice::DestroyResources()
|
||||
m_shader_cache.Close();
|
||||
}
|
||||
|
||||
void GPUDevice::RenderImGui()
|
||||
void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
|
||||
{
|
||||
GL_SCOPE("RenderImGui");
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
const ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
if (draw_data->CmdListsCount == 0)
|
||||
if (draw_data->CmdListsCount == 0 || !swap_chain)
|
||||
return;
|
||||
|
||||
SetPipeline(m_imgui_pipeline.get());
|
||||
SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
|
||||
|
||||
const float L = 0.0f;
|
||||
const float R = static_cast<float>(m_window_info.surface_width);
|
||||
const float R = static_cast<float>(swap_chain->GetWidth());
|
||||
const float T = 0.0f;
|
||||
const float B = static_cast<float>(m_window_info.surface_height);
|
||||
const float B = static_cast<float>(swap_chain->GetHeight());
|
||||
const float ortho_projection[4][4] = {
|
||||
{2.0f / (R - L), 0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 2.0f / (T - B), 0.0f, 0.0f},
|
||||
@ -666,8 +710,7 @@ void GPUDevice::RenderImGui()
|
||||
if (flip)
|
||||
{
|
||||
const s32 height = static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y);
|
||||
const s32 flipped_y =
|
||||
static_cast<s32>(m_window_info.surface_height) - static_cast<s32>(pcmd->ClipRect.y) - height;
|
||||
const s32 flipped_y = static_cast<s32>(swap_chain->GetHeight()) - static_cast<s32>(pcmd->ClipRect.y) - height;
|
||||
SetScissor(static_cast<s32>(pcmd->ClipRect.x), flipped_y, static_cast<s32>(pcmd->ClipRect.z - pcmd->ClipRect.x),
|
||||
height);
|
||||
}
|
||||
@ -789,69 +832,59 @@ std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, GPUShad
|
||||
return shader;
|
||||
}
|
||||
|
||||
bool GPUDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
|
||||
std::optional<GPUDevice::ExclusiveFullscreenMode> GPUDevice::ExclusiveFullscreenMode::Parse(std::string_view str)
|
||||
{
|
||||
const std::string mode = Host::GetBaseStringSettingValue("GPU", "FullscreenMode", "");
|
||||
if (!mode.empty())
|
||||
std::optional<ExclusiveFullscreenMode> ret;
|
||||
std::string_view::size_type sep1 = str.find('x');
|
||||
if (sep1 != std::string_view::npos)
|
||||
{
|
||||
const std::string_view mode_view = mode;
|
||||
std::string_view::size_type sep1 = mode.find('x');
|
||||
if (sep1 != std::string_view::npos)
|
||||
{
|
||||
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
|
||||
std::optional<u32> owidth = StringUtil::FromChars<u32>(str.substr(0, sep1));
|
||||
sep1++;
|
||||
|
||||
while (sep1 < str.length() && std::isspace(str[sep1]))
|
||||
sep1++;
|
||||
|
||||
while (sep1 < mode.length() && std::isspace(mode[sep1]))
|
||||
sep1++;
|
||||
|
||||
if (owidth.has_value() && sep1 < mode.length())
|
||||
if (owidth.has_value() && sep1 < str.length())
|
||||
{
|
||||
std::string_view::size_type sep2 = str.find('@', sep1);
|
||||
if (sep2 != std::string_view::npos)
|
||||
{
|
||||
std::string_view::size_type sep2 = mode.find('@', sep1);
|
||||
if (sep2 != std::string_view::npos)
|
||||
{
|
||||
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1));
|
||||
std::optional<u32> oheight = StringUtil::FromChars<u32>(str.substr(sep1, sep2 - sep1));
|
||||
sep2++;
|
||||
|
||||
while (sep2 < str.length() && std::isspace(str[sep2]))
|
||||
sep2++;
|
||||
|
||||
while (sep2 < mode.length() && std::isspace(mode[sep2]))
|
||||
sep2++;
|
||||
|
||||
if (oheight.has_value() && sep2 < mode.length())
|
||||
if (oheight.has_value() && sep2 < str.length())
|
||||
{
|
||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(str.substr(sep2));
|
||||
if (orefresh_rate.has_value())
|
||||
{
|
||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2));
|
||||
if (orefresh_rate.has_value())
|
||||
{
|
||||
*width = owidth.value();
|
||||
*height = oheight.value();
|
||||
*refresh_rate = orefresh_rate.value();
|
||||
return true;
|
||||
}
|
||||
ret = ExclusiveFullscreenMode{
|
||||
.width = owidth.value(), .height = oheight.value(), .refresh_rate = orefresh_rate.value()};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*refresh_rate = 0;
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string GPUDevice::GetFullscreenModeString(u32 width, u32 height, float refresh_rate)
|
||||
TinyString GPUDevice::ExclusiveFullscreenMode::ToString() const
|
||||
{
|
||||
return fmt::format("{} x {} @ {} hz", width, height, refresh_rate);
|
||||
}
|
||||
|
||||
std::string GPUDevice::GetShaderDumpPath(std::string_view name)
|
||||
{
|
||||
return Path::Combine(EmuFolders::Dumps, name);
|
||||
return TinyString::from_format("{} x {} @ {} hz", width, height, refresh_rate);
|
||||
}
|
||||
|
||||
void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors)
|
||||
{
|
||||
static u32 next_bad_shader_id = 0;
|
||||
|
||||
const std::string filename = GetShaderDumpPath(fmt::format("bad_shader_{}.txt", ++next_bad_shader_id));
|
||||
if (s_shader_dump_path.empty())
|
||||
return;
|
||||
|
||||
const std::string filename =
|
||||
Path::Combine(s_shader_dump_path, TinyString::from_format("bad_shader_{}.txt", ++next_bad_shader_id));
|
||||
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
|
||||
if (fp)
|
||||
{
|
||||
@ -1124,42 +1157,6 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GPUDevice::ShouldSkipPresentingFrame()
|
||||
{
|
||||
// Only needed with FIFO. But since we're so fast, we allow it always.
|
||||
if (!m_allow_present_throttle)
|
||||
return false;
|
||||
|
||||
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
|
||||
const float throttle_period = 1.0f / throttle_rate;
|
||||
|
||||
const u64 now = Common::Timer::GetCurrentValue();
|
||||
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
|
||||
if (diff < throttle_period)
|
||||
return true;
|
||||
|
||||
m_last_frame_displayed_time = now;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GPUDevice::ThrottlePresentation()
|
||||
{
|
||||
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
|
||||
|
||||
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
|
||||
const u64 current_ts = Common::Timer::GetCurrentValue();
|
||||
|
||||
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
|
||||
// allow time for the actual rendering.
|
||||
const u64 max_variance = sleep_period * 2;
|
||||
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
|
||||
m_last_frame_displayed_time = current_ts + sleep_period;
|
||||
else
|
||||
m_last_frame_displayed_time += sleep_period;
|
||||
|
||||
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
|
||||
}
|
||||
|
||||
bool GPUDevice::SetGPUTimingEnabled(bool enabled)
|
||||
{
|
||||
return false;
|
||||
|
@ -453,6 +453,38 @@ protected:
|
||||
u32 m_current_position = 0;
|
||||
};
|
||||
|
||||
class GPUSwapChain
|
||||
{
|
||||
public:
|
||||
GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle);
|
||||
virtual ~GPUSwapChain();
|
||||
|
||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
|
||||
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
|
||||
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
|
||||
ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; }
|
||||
|
||||
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
|
||||
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); }
|
||||
ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; }
|
||||
|
||||
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
|
||||
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
|
||||
|
||||
bool ShouldSkipPresentingFrame();
|
||||
void ThrottlePresentation();
|
||||
|
||||
protected:
|
||||
// TODO: Merge WindowInfo into this struct...
|
||||
WindowInfo m_window_info;
|
||||
|
||||
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
|
||||
bool m_allow_present_throttle = false;
|
||||
|
||||
u64 m_last_frame_displayed_time = 0;
|
||||
};
|
||||
|
||||
class GPUDevice
|
||||
{
|
||||
public:
|
||||
@ -485,6 +517,7 @@ public:
|
||||
{
|
||||
OK,
|
||||
SkipPresent,
|
||||
ExclusiveFullscreenLost,
|
||||
DeviceLost,
|
||||
};
|
||||
|
||||
@ -521,10 +554,22 @@ public:
|
||||
u32 num_uploads;
|
||||
};
|
||||
|
||||
// Parameters for exclusive fullscreen.
|
||||
struct ExclusiveFullscreenMode
|
||||
{
|
||||
u32 width;
|
||||
u32 height;
|
||||
float refresh_rate;
|
||||
|
||||
TinyString ToString() const;
|
||||
|
||||
static std::optional<ExclusiveFullscreenMode> Parse(std::string_view str);
|
||||
};
|
||||
|
||||
struct AdapterInfo
|
||||
{
|
||||
std::string name;
|
||||
std::vector<std::string> fullscreen_modes;
|
||||
std::vector<ExclusiveFullscreenMode> fullscreen_modes;
|
||||
u32 max_texture_size;
|
||||
u32 max_multisamples;
|
||||
bool supports_sample_shading;
|
||||
@ -565,15 +610,6 @@ public:
|
||||
/// Returns a list of adapters for the given API.
|
||||
static AdapterInfoList GetAdapterListForAPI(RenderAPI api);
|
||||
|
||||
/// Parses a fullscreen mode into its components (width * height @ refresh hz)
|
||||
static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate);
|
||||
|
||||
/// Converts a fullscreen mode to a string.
|
||||
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
|
||||
|
||||
/// Returns the directory bad shaders are saved to.
|
||||
static std::string GetShaderDumpPath(std::string_view name);
|
||||
|
||||
/// Dumps out a shader that failed compilation.
|
||||
static void DumpBadShader(std::string_view code, std::string_view errors);
|
||||
|
||||
@ -600,35 +636,44 @@ public:
|
||||
ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; }
|
||||
ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; }
|
||||
|
||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
|
||||
ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
|
||||
ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
|
||||
ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; }
|
||||
ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); }
|
||||
ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); }
|
||||
// ALWAYS_INLINE u32 GetMainSwapChainWidth() const { return m_main_swap_chain->GetWidth(); }
|
||||
// ALWAYS_INLINE u32 GetMainSwapChainHeight() const { return m_main_swap_chain->GetHeight(); }
|
||||
// ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
|
||||
// ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; }
|
||||
|
||||
ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler.get(); }
|
||||
ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); }
|
||||
|
||||
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
|
||||
|
||||
bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device,
|
||||
GPUVSyncMode vsync, bool allow_present_throttle, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error);
|
||||
bool Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path,
|
||||
std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error);
|
||||
void Destroy();
|
||||
|
||||
virtual bool HasSurface() const = 0;
|
||||
virtual void DestroySurface() = 0;
|
||||
virtual bool UpdateWindow() = 0;
|
||||
virtual std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) = 0;
|
||||
|
||||
bool RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error);
|
||||
void DestroyMainSwapChain();
|
||||
|
||||
virtual bool SupportsExclusiveFullscreen() const;
|
||||
|
||||
/// Call when the window size changes externally to recreate any resources.
|
||||
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0;
|
||||
|
||||
virtual std::string GetDriverInfo() const = 0;
|
||||
|
||||
// Flushes current command buffer, but does not wait for completion.
|
||||
virtual void FlushCommands() = 0;
|
||||
|
||||
// Executes current command buffer, waits for its completion, and destroys all pending resources.
|
||||
virtual void ExecuteAndWaitForGPUIdle() = 0;
|
||||
virtual void WaitForGPUIdle() = 0;
|
||||
|
||||
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
@ -710,17 +755,12 @@ public:
|
||||
virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0;
|
||||
|
||||
/// Returns false if the window was completely occluded.
|
||||
virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
|
||||
virtual void EndPresent(bool explicit_submit, u64 submit_time = 0) = 0;
|
||||
virtual void SubmitPresent() = 0;
|
||||
virtual PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
|
||||
virtual void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 submit_time = 0) = 0;
|
||||
virtual void SubmitPresent(GPUSwapChain* swap_chain) = 0;
|
||||
|
||||
/// Renders ImGui screen elements. Call before EndPresent().
|
||||
void RenderImGui();
|
||||
|
||||
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
|
||||
ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); }
|
||||
ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; }
|
||||
virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0;
|
||||
void RenderImGui(GPUSwapChain* swap_chain);
|
||||
|
||||
ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; }
|
||||
ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; }
|
||||
@ -730,8 +770,6 @@ public:
|
||||
static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height);
|
||||
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
|
||||
GPUTexture::Format format, bool preserve = true);
|
||||
bool ShouldSkipPresentingFrame();
|
||||
void ThrottlePresentation();
|
||||
|
||||
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
|
||||
|
||||
@ -745,8 +783,10 @@ public:
|
||||
static void ResetStatistics();
|
||||
|
||||
protected:
|
||||
virtual bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) = 0;
|
||||
virtual bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) = 0;
|
||||
virtual void DestroyDevice() = 0;
|
||||
|
||||
std::string GetShaderCacheBaseName(std::string_view type) const;
|
||||
@ -762,8 +802,6 @@ protected:
|
||||
std::string_view source, const char* entry_point,
|
||||
DynamicHeapArray<u8>* out_binary, Error* error) = 0;
|
||||
|
||||
bool AcquireWindow(bool recreate_window);
|
||||
|
||||
void TrimTexturePool();
|
||||
|
||||
bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source,
|
||||
@ -784,8 +822,7 @@ protected:
|
||||
u32 m_max_texture_size = 0;
|
||||
u32 m_max_multisamples = 0;
|
||||
|
||||
WindowInfo m_window_info;
|
||||
u64 m_last_frame_displayed_time = 0;
|
||||
std::unique_ptr<GPUSwapChain> m_main_swap_chain;
|
||||
|
||||
GPUShaderCache m_shader_cache;
|
||||
|
||||
@ -852,8 +889,6 @@ private:
|
||||
protected:
|
||||
static Statistics s_stats;
|
||||
|
||||
GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled;
|
||||
bool m_allow_present_throttle = false;
|
||||
bool m_gpu_timing_enabled = false;
|
||||
bool m_debug_device = false;
|
||||
};
|
||||
@ -865,21 +900,6 @@ ALWAYS_INLINE void GPUDevice::PooledTextureDeleter::operator()(GPUTexture* const
|
||||
g_gpu_device->RecycleTexture(std::unique_ptr<GPUTexture>(tex));
|
||||
}
|
||||
|
||||
namespace Host {
|
||||
/// Called when the core is creating a render device.
|
||||
/// This could also be fullscreen transition.
|
||||
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
|
||||
|
||||
/// Called when the core is finished with a render window.
|
||||
void ReleaseRenderWindow();
|
||||
|
||||
/// Returns true if the hosting application is currently fullscreen.
|
||||
bool IsFullscreen();
|
||||
|
||||
/// Alters fullscreen state of hosting application.
|
||||
void SetFullscreen(bool enabled);
|
||||
} // namespace Host
|
||||
|
||||
// Macros for debug messages.
|
||||
#ifdef _DEBUG
|
||||
struct GLAutoPop
|
||||
|
@ -231,7 +231,8 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
|
||||
}
|
||||
|
||||
s_global_prescale = global_scale;
|
||||
s_global_scale = std::max(g_gpu_device->GetWindowScale() * global_scale, 1.0f);
|
||||
s_global_scale = std::max(
|
||||
(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f) * global_scale, 1.0f);
|
||||
s_scale_changed = false;
|
||||
|
||||
ImGui::CreateContext();
|
||||
@ -250,8 +251,10 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
|
||||
#endif
|
||||
|
||||
s_window_width = static_cast<float>(g_gpu_device->GetWindowWidth());
|
||||
s_window_height = static_cast<float>(g_gpu_device->GetWindowHeight());
|
||||
s_window_width =
|
||||
g_gpu_device->HasMainSwapChain() ? static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f;
|
||||
s_window_height =
|
||||
g_gpu_device->HasMainSwapChain() ? static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight()) : 0.0f;
|
||||
io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling
|
||||
io.DisplaySize = ImVec2(s_window_width, s_window_height);
|
||||
|
||||
@ -316,7 +319,8 @@ void ImGuiManager::RequestScaleUpdate()
|
||||
|
||||
void ImGuiManager::UpdateScale()
|
||||
{
|
||||
const float window_scale = g_gpu_device ? g_gpu_device->GetWindowScale() : 1.0f;
|
||||
const float window_scale =
|
||||
(g_gpu_device && g_gpu_device->HasMainSwapChain()) ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f;
|
||||
const float scale = std::max(window_scale * s_global_prescale, 1.0f);
|
||||
|
||||
if ((!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()) && scale == s_global_scale)
|
||||
|
@ -607,11 +607,12 @@ static std::array<const char*, static_cast<u32>(InputSourceType::Count)> s_input
|
||||
#ifdef _WIN32
|
||||
"DInput",
|
||||
"XInput",
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
"SDL",
|
||||
"RawInput",
|
||||
#else
|
||||
#endif
|
||||
#ifdef ENABLE_SDL
|
||||
"SDL",
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
"Android",
|
||||
#endif
|
||||
}};
|
||||
@ -640,14 +641,17 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type)
|
||||
|
||||
case InputSourceType::XInput:
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID__
|
||||
case InputSourceType::SDL:
|
||||
return true;
|
||||
case InputSourceType::RawInput:
|
||||
return false;
|
||||
#else
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SDL
|
||||
case InputSourceType::SDL:
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
case InputSourceType::Android:
|
||||
return true;
|
||||
#endif
|
||||
@ -1229,7 +1233,7 @@ void InputManager::UpdatePointerCount()
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef __ANDROID__
|
||||
#ifdef _WIN32
|
||||
InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput);
|
||||
DebugAssert(ris);
|
||||
|
||||
@ -2048,9 +2052,10 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock<std::mu
|
||||
UpdateInputSourceState(si, settings_lock, InputSourceType::XInput, &InputSource::CreateXInputSource);
|
||||
UpdateInputSourceState(si, settings_lock, InputSourceType::RawInput, &InputSource::CreateWin32RawInputSource);
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
#ifdef ENABLE_SDL
|
||||
UpdateInputSourceState(si, settings_lock, InputSourceType::SDL, &InputSource::CreateSDLSource);
|
||||
#else
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
UpdateInputSourceState(si, settings_lock, InputSourceType::Android, &InputSource::CreateAndroidSource);
|
||||
#endif
|
||||
|
||||
|
@ -27,11 +27,12 @@ enum class InputSourceType : u32
|
||||
#ifdef _WIN32
|
||||
DInput,
|
||||
XInput,
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
SDL,
|
||||
RawInput,
|
||||
#else
|
||||
#endif
|
||||
#ifdef ENABLE_SDL
|
||||
SDL,
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
Android,
|
||||
#endif
|
||||
Count,
|
||||
|
@ -184,6 +184,23 @@ private:
|
||||
MetalStreamBuffer m_buffer;
|
||||
};
|
||||
|
||||
class MetalSwapChain : public GPUSwapChain
|
||||
{
|
||||
public:
|
||||
MetalSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, CAMetalLayer* layer);
|
||||
~MetalSwapChain() override;
|
||||
|
||||
void Destroy(bool wait_for_gpu);
|
||||
|
||||
CAMetalLayer* GetLayer() const { return m_layer; }
|
||||
|
||||
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
|
||||
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
|
||||
|
||||
private:
|
||||
CAMetalLayer* m_layer = nil;
|
||||
};
|
||||
|
||||
class MetalDevice final : public GPUDevice
|
||||
{
|
||||
friend MetalTexture;
|
||||
@ -198,16 +215,16 @@ public:
|
||||
MetalDevice();
|
||||
~MetalDevice();
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
void DestroySurface() override;
|
||||
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ExecuteAndWaitForGPUIdle() override;
|
||||
void FlushCommands() override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) override;
|
||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data = nullptr, u32 data_stride = 0) override;
|
||||
@ -261,11 +278,9 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
|
||||
|
||||
PresentResult BeginPresent(u32 clear_color) override;
|
||||
void EndPresent(bool explicit_submit, u64 present_time) override;
|
||||
void SubmitPresent() override;
|
||||
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
|
||||
void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 present_time) override;
|
||||
void SubmitPresent(GPUSwapChain* swap_chain) override;
|
||||
|
||||
void WaitForFenceCounter(u64 counter);
|
||||
|
||||
@ -285,8 +300,10 @@ public:
|
||||
static void DeferRelease(u64 fence_counter, id obj);
|
||||
|
||||
protected:
|
||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) override;
|
||||
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
|
||||
void DestroyDevice() override;
|
||||
bool OpenPipelineCache(const std::string& path, Error* error) override;
|
||||
bool CreatePipelineCache(const std::string& path, Error* error) override;
|
||||
@ -315,8 +332,6 @@ private:
|
||||
};
|
||||
static_assert(sizeof(ClearPipelineConfig) == 8);
|
||||
|
||||
ALWAYS_INLINE NSView* GetWindowView() const { return (__bridge NSView*)m_window_info.window_handle; }
|
||||
|
||||
void SetFeatures(FeatureMask disabled_features);
|
||||
bool LoadShaders();
|
||||
|
||||
@ -340,15 +355,12 @@ private:
|
||||
void EndInlineUploading();
|
||||
void EndAnyEncoding();
|
||||
|
||||
GSVector4i ClampToFramebufferSize(const GSVector4i rc) const;
|
||||
void PreDrawCheck();
|
||||
void SetInitialEncoderState();
|
||||
void SetViewportInRenderEncoder();
|
||||
void SetScissorInRenderEncoder();
|
||||
|
||||
bool CreateLayer();
|
||||
void DestroyLayer();
|
||||
void RenderBlankFrame();
|
||||
void RenderBlankFrame(MetalSwapChain* swap_chain);
|
||||
|
||||
bool CreateBuffers();
|
||||
void DestroyBuffers();
|
||||
@ -358,10 +370,6 @@ private:
|
||||
id<MTLDevice> m_device;
|
||||
id<MTLCommandQueue> m_queue;
|
||||
|
||||
CAMetalLayer* m_layer = nil;
|
||||
id<MTLDrawable> m_layer_drawable = nil;
|
||||
MTLRenderPassDescriptor* m_layer_pass_desc = nil;
|
||||
|
||||
std::mutex m_fence_mutex;
|
||||
u64 m_current_fence_counter = 0;
|
||||
std::atomic<u64> m_completed_fence_counter{0};
|
||||
@ -402,10 +410,11 @@ private:
|
||||
id<MTLBuffer> m_current_ssbo = nil;
|
||||
GSVector4i m_current_viewport = {};
|
||||
GSVector4i m_current_scissor = {};
|
||||
|
||||
bool m_vsync_enabled = false;
|
||||
bool m_pipeline_cache_modified = false;
|
||||
GSVector4i m_current_framebuffer_size = {};
|
||||
|
||||
double m_accumulated_gpu_time = 0;
|
||||
double m_last_gpu_time_end = 0;
|
||||
|
||||
id<MTLDrawable> m_layer_drawable = nil;
|
||||
bool m_pipeline_cache_modified = false;
|
||||
};
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include <mach/mach_time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
LOG_CHANNEL(MetalDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
// TODO: Disable hazard tracking and issue barriers explicitly.
|
||||
|
||||
@ -128,34 +128,160 @@ static void RunOnMainThread(F&& f)
|
||||
|
||||
MetalDevice::MetalDevice() : m_current_viewport(0, 0, 1, 1), m_current_scissor(0, 0, 1, 1)
|
||||
{
|
||||
m_render_api = RenderAPI::Metal;
|
||||
}
|
||||
|
||||
MetalDevice::~MetalDevice()
|
||||
{
|
||||
Assert(m_pipeline_archive == nil && m_layer == nil && m_device == nil);
|
||||
Assert(m_pipeline_archive == nil && m_layer_drawable == nil && m_device == nil);
|
||||
}
|
||||
|
||||
bool MetalDevice::HasSurface() const
|
||||
MetalSwapChain::MetalSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
CAMetalLayer* layer)
|
||||
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_layer(layer)
|
||||
{
|
||||
return (m_layer != nil);
|
||||
}
|
||||
|
||||
void MetalDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
|
||||
MetalSwapChain::~MetalSwapChain()
|
||||
{
|
||||
Destroy(true);
|
||||
}
|
||||
|
||||
void MetalSwapChain::Destroy(bool wait_for_gpu)
|
||||
{
|
||||
if (!m_layer)
|
||||
return;
|
||||
|
||||
if (wait_for_gpu)
|
||||
MetalDevice::GetInstance().WaitForGPUIdle();
|
||||
|
||||
RunOnMainThread([this]() {
|
||||
NSView* view = (__bridge NSView*)m_window_info.window_handle;
|
||||
[view setLayer:nil];
|
||||
[view setWantsLayer:FALSE];
|
||||
[m_layer release];
|
||||
m_layer = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
bool MetalSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
m_window_info.surface_scale = new_scale;
|
||||
if (new_width == m_window_info.surface_width && new_height == m_window_info.surface_height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
m_window_info.surface_width = new_width;
|
||||
m_window_info.surface_height = new_height;
|
||||
|
||||
[m_layer setDrawableSize:CGSizeMake(new_width, new_height)];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
|
||||
{
|
||||
// Metal does not support mailbox mode.
|
||||
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
return true;
|
||||
|
||||
m_vsync_mode = mode;
|
||||
if (m_layer != nil)
|
||||
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error)
|
||||
std::unique_ptr<GPUSwapChain> MetalDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
CAMetalLayer* layer;
|
||||
WindowInfo wi_copy(wi);
|
||||
RunOnMainThread([this, &layer, &wi_copy, error]() {
|
||||
@autoreleasepool
|
||||
{
|
||||
INFO_LOG("Creating a {}x{} Metal layer.", wi_copy.surface_width, wi_copy.surface_height);
|
||||
layer = [CAMetalLayer layer]; // TODO: Does this need retain??
|
||||
if (layer == nil)
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create metal layer.");
|
||||
return;
|
||||
}
|
||||
|
||||
[layer setDevice:m_device];
|
||||
[layer setDrawableSize:CGSizeMake(static_cast<float>(wi_copy.surface_width),
|
||||
static_cast<float>(wi_copy.surface_height))];
|
||||
|
||||
// Default should be BGRA8.
|
||||
const MTLPixelFormat layer_fmt = [layer pixelFormat];
|
||||
wi_copy.surface_format = GetTextureFormatForMTLFormat(layer_fmt);
|
||||
if (wi_copy.surface_format == GPUTexture::Format::Unknown)
|
||||
{
|
||||
ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
|
||||
[layer setPixelFormat:MTLPixelFormatBGRA8Unorm];
|
||||
wi_copy.surface_format = GPUTexture::Format::BGRA8;
|
||||
}
|
||||
|
||||
VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(wi_copy.surface_format));
|
||||
|
||||
NSView* view = (__bridge NSView*)wi_copy.window_handle;
|
||||
[view setWantsLayer:TRUE];
|
||||
[view setLayer:layer];
|
||||
}
|
||||
});
|
||||
|
||||
if (!layer)
|
||||
return {};
|
||||
|
||||
// Metal does not support mailbox mode.
|
||||
vsync_mode = (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode;
|
||||
[layer setDisplaySyncEnabled:vsync_mode == GPUVSyncMode::FIFO];
|
||||
|
||||
// Clear it out ASAP.
|
||||
std::unique_ptr<MetalSwapChain> swap_chain =
|
||||
std::make_unique<MetalSwapChain>(wi_copy, vsync_mode, allow_present_throttle, layer);
|
||||
RenderBlankFrame(swap_chain.get());
|
||||
return swap_chain;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDevice::RenderBlankFrame(MetalSwapChain* swap_chain)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
// has to be encoding, we don't "begin" a render pass here, so the inline encoder won't get flushed otherwise.
|
||||
EndAnyEncoding();
|
||||
|
||||
id<MTLDrawable> drawable = [[swap_chain->GetLayer() nextDrawable] retain];
|
||||
MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
desc.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||
desc.colorAttachments[0].texture = [drawable texture];
|
||||
id<MTLRenderCommandEncoder> encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:desc];
|
||||
[encoder endEncoding];
|
||||
[m_render_cmdbuf presentDrawable:drawable];
|
||||
DeferRelease(drawable);
|
||||
SubmitCommandBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
@ -199,15 +325,20 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
||||
INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]);
|
||||
|
||||
SetFeatures(disabled_features);
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create layer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateCommandBuffer();
|
||||
RenderBlankFrame();
|
||||
|
||||
if (!wi.IsSurfaceless())
|
||||
{
|
||||
m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode,
|
||||
exclusive_fullscreen_control, error);
|
||||
if (!m_main_swap_chain)
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create layer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderBlankFrame(static_cast<MetalSwapChain*>(m_main_swap_chain.get()));
|
||||
}
|
||||
|
||||
if (!LoadShaders())
|
||||
{
|
||||
@ -228,7 +359,6 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional<bool> exc
|
||||
void MetalDevice::SetFeatures(FeatureMask disabled_features)
|
||||
{
|
||||
// Set version to Metal 2.3, that's all we're using. Use SPIRV-Cross version encoding.
|
||||
m_render_api = RenderAPI::Metal;
|
||||
m_render_api_version = 20300;
|
||||
m_max_texture_size = GetMetalMaxTextureSize(m_device);
|
||||
m_max_multisamples = GetMetalMaxMultisamples(m_device);
|
||||
@ -416,6 +546,12 @@ void MetalDevice::DestroyDevice()
|
||||
m_render_cmdbuf = nil;
|
||||
}
|
||||
|
||||
if (m_main_swap_chain)
|
||||
{
|
||||
static_cast<MetalSwapChain*>(m_main_swap_chain.get())->Destroy(false);
|
||||
m_main_swap_chain.reset();
|
||||
}
|
||||
|
||||
DestroyBuffers();
|
||||
|
||||
for (auto& it : m_cleanup_objects)
|
||||
@ -457,135 +593,6 @@ void MetalDevice::DestroyDevice()
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalDevice::CreateLayer()
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
RunOnMainThread([this]() {
|
||||
@autoreleasepool
|
||||
{
|
||||
INFO_LOG("Creating a {}x{} Metal layer.", m_window_info.surface_width, m_window_info.surface_height);
|
||||
const auto size =
|
||||
CGSizeMake(static_cast<float>(m_window_info.surface_width), static_cast<float>(m_window_info.surface_height));
|
||||
m_layer = [CAMetalLayer layer];
|
||||
[m_layer setDevice:m_device];
|
||||
[m_layer setDrawableSize:size];
|
||||
|
||||
// Default should be BGRA8.
|
||||
const MTLPixelFormat layer_fmt = [m_layer pixelFormat];
|
||||
m_window_info.surface_format = GetTextureFormatForMTLFormat(layer_fmt);
|
||||
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
||||
{
|
||||
ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
|
||||
[m_layer setPixelFormat:MTLPixelFormatBGRA8Unorm];
|
||||
m_window_info.surface_format = GPUTexture::Format::BGRA8;
|
||||
}
|
||||
|
||||
VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(m_window_info.surface_format));
|
||||
|
||||
NSView* view = GetWindowView();
|
||||
[view setWantsLayer:TRUE];
|
||||
[view setLayer:m_layer];
|
||||
}
|
||||
});
|
||||
|
||||
// Metal does not support mailbox mode.
|
||||
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
|
||||
[m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO];
|
||||
|
||||
DebugAssert(m_layer_pass_desc == nil);
|
||||
m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];
|
||||
m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width;
|
||||
m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height;
|
||||
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
m_layer_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
m_layer_pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDevice::DestroyLayer()
|
||||
{
|
||||
if (m_layer == nil)
|
||||
return;
|
||||
|
||||
// Should wait for previous command buffers to finish, which might be rendering to drawables.
|
||||
WaitForPreviousCommandBuffers();
|
||||
|
||||
[m_layer_pass_desc release];
|
||||
m_layer_pass_desc = nil;
|
||||
m_window_info.surface_format = GPUTexture::Format::Unknown;
|
||||
|
||||
RunOnMainThread([this]() {
|
||||
NSView* view = GetWindowView();
|
||||
[view setLayer:nil];
|
||||
[view setWantsLayer:FALSE];
|
||||
[m_layer release];
|
||||
m_layer = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void MetalDevice::RenderBlankFrame()
|
||||
{
|
||||
DebugAssert(!InRenderPass());
|
||||
if (m_layer == nil)
|
||||
return;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
id<MTLDrawable> drawable = [[m_layer nextDrawable] retain];
|
||||
m_layer_pass_desc.colorAttachments[0].texture = [drawable texture];
|
||||
id<MTLRenderCommandEncoder> encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc];
|
||||
[encoder endEncoding];
|
||||
[m_render_cmdbuf presentDrawable:drawable];
|
||||
DeferRelease(drawable);
|
||||
SubmitCommandBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalDevice::UpdateWindow()
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
DestroyLayer();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
|
||||
{
|
||||
ERROR_LOG("Failed to create layer on updated window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetalDevice::DestroySurface()
|
||||
{
|
||||
DestroyLayer();
|
||||
}
|
||||
|
||||
void MetalDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
if (static_cast<u32>(new_window_width) == m_window_info.surface_width &&
|
||||
static_cast<u32>(new_window_height) == m_window_info.surface_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_window_info.surface_width = new_window_width;
|
||||
m_window_info.surface_height = new_window_height;
|
||||
|
||||
[m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)];
|
||||
m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width;
|
||||
m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height;
|
||||
}
|
||||
}
|
||||
|
||||
std::string MetalDevice::GetDriverInfo() const
|
||||
{
|
||||
@autoreleasepool
|
||||
@ -2082,6 +2089,8 @@ void MetalDevice::BeginRenderPass()
|
||||
// Rendering to view, but we got interrupted...
|
||||
desc.colorAttachments[0].texture = [m_layer_drawable texture];
|
||||
desc.colorAttachments[0].loadAction = MTLLoadActionLoad;
|
||||
desc.renderTargetWidth = m_current_framebuffer_size.width();
|
||||
desc.renderTargetHeight = m_current_framebuffer_size.height();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2157,6 +2166,10 @@ void MetalDevice::BeginRenderPass()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MetalTexture* rt_or_ds =
|
||||
(m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target;
|
||||
m_current_framebuffer_size = GSVector4i(0, 0, rt_or_ds->GetWidth(), rt_or_ds->GetHeight());
|
||||
}
|
||||
|
||||
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain];
|
||||
@ -2218,7 +2231,7 @@ void MetalDevice::SetInitialEncoderState()
|
||||
|
||||
void MetalDevice::SetViewportInRenderEncoder()
|
||||
{
|
||||
const GSVector4i rc = ClampToFramebufferSize(m_current_viewport);
|
||||
const GSVector4i rc = m_current_viewport.rintersect(m_current_framebuffer_size);
|
||||
[m_render_encoder
|
||||
setViewport:(MTLViewport){static_cast<double>(rc.left), static_cast<double>(rc.top),
|
||||
static_cast<double>(rc.width()), static_cast<double>(rc.height()), 0.0, 1.0}];
|
||||
@ -2226,21 +2239,12 @@ void MetalDevice::SetViewportInRenderEncoder()
|
||||
|
||||
void MetalDevice::SetScissorInRenderEncoder()
|
||||
{
|
||||
const GSVector4i rc = ClampToFramebufferSize(m_current_scissor);
|
||||
const GSVector4i rc = m_current_scissor.rintersect(m_current_framebuffer_size);
|
||||
[m_render_encoder
|
||||
setScissorRect:(MTLScissorRect){static_cast<NSUInteger>(rc.left), static_cast<NSUInteger>(rc.top),
|
||||
static_cast<NSUInteger>(rc.width()), static_cast<NSUInteger>(rc.height())}];
|
||||
}
|
||||
|
||||
GSVector4i MetalDevice::ClampToFramebufferSize(const GSVector4i rc) const
|
||||
{
|
||||
const MetalTexture* rt_or_ds =
|
||||
(m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target;
|
||||
const s32 clamp_width = rt_or_ds ? rt_or_ds->GetWidth() : m_window_info.surface_width;
|
||||
const s32 clamp_height = rt_or_ds ? rt_or_ds->GetHeight() : m_window_info.surface_height;
|
||||
return rc.rintersect(GSVector4i(0, 0, clamp_width, clamp_height));
|
||||
}
|
||||
|
||||
void MetalDevice::PreDrawCheck()
|
||||
{
|
||||
if (!InRenderPass())
|
||||
@ -2413,35 +2417,35 @@ id<MTLBlitCommandEncoder> MetalDevice::GetBlitEncoder(bool is_inline)
|
||||
}
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color)
|
||||
GPUDevice::PresentResult MetalDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
if (m_layer == nil)
|
||||
{
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
|
||||
EndAnyEncoding();
|
||||
|
||||
m_layer_drawable = [[m_layer nextDrawable] retain];
|
||||
m_layer_drawable = [[static_cast<MetalSwapChain*>(swap_chain)->GetLayer() nextDrawable] retain];
|
||||
if (m_layer_drawable == nil)
|
||||
{
|
||||
WARNING_LOG("Failed to get drawable from layer.");
|
||||
SubmitCommandBuffer();
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
|
||||
SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
m_current_framebuffer_size = GSVector4i(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
|
||||
SetViewportAndScissor(m_current_framebuffer_size);
|
||||
|
||||
// Set up rendering to layer.
|
||||
const GSVector4 clear_color_v = GSVector4::rgba32(clear_color);
|
||||
id<MTLTexture> layer_texture = [m_layer_drawable texture];
|
||||
m_layer_pass_desc.colorAttachments[0].texture = layer_texture;
|
||||
m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
m_layer_pass_desc.colorAttachments[0].clearColor =
|
||||
MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
desc.colorAttachments[0].texture = layer_texture;
|
||||
desc.colorAttachments[0].loadAction = MTLLoadActionClear;
|
||||
desc.colorAttachments[0].clearColor =
|
||||
MTLClearColorMake(clear_color_v.r, clear_color_v.g, clear_color_v.g, clear_color_v.a);
|
||||
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc] retain];
|
||||
desc.renderTargetWidth = swap_chain->GetWidth();
|
||||
desc.renderTargetHeight = swap_chain->GetHeight();
|
||||
m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain];
|
||||
s_stats.num_render_passes++;
|
||||
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
|
||||
m_num_current_render_targets = 0;
|
||||
@ -2454,7 +2458,7 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color)
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDevice::EndPresent(bool explicit_present, u64 present_time)
|
||||
void MetalDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
|
||||
{
|
||||
DebugAssert(!explicit_present);
|
||||
DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target);
|
||||
@ -2480,7 +2484,7 @@ void MetalDevice::EndPresent(bool explicit_present, u64 present_time)
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void MetalDevice::SubmitPresent()
|
||||
void MetalDevice::SubmitPresent(GPUSwapChain* swap_chainwel)
|
||||
{
|
||||
Panic("Not supported by this API.");
|
||||
}
|
||||
@ -2585,12 +2589,18 @@ void MetalDevice::WaitForPreviousCommandBuffers()
|
||||
WaitForFenceCounter(m_current_fence_counter - 1);
|
||||
}
|
||||
|
||||
void MetalDevice::ExecuteAndWaitForGPUIdle()
|
||||
void MetalDevice::WaitForGPUIdle()
|
||||
{
|
||||
SubmitCommandBuffer(true);
|
||||
CleanupObjects();
|
||||
}
|
||||
|
||||
void MetalDevice::FlushCommands()
|
||||
{
|
||||
SubmitCommandBuffer();
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void MetalDevice::CleanupObjects()
|
||||
{
|
||||
const u64 counter = m_completed_fence_counter.load(std::memory_order_acquire);
|
||||
|
@ -1,12 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
class Error;
|
||||
struct WindowInfo;
|
||||
|
||||
namespace CocoaTools {
|
||||
/// Creates metal layer on specified window surface.
|
||||
bool CreateMetalLayer(WindowInfo* wi);
|
||||
void* CreateMetalLayer(const WindowInfo& wi, Error* error);
|
||||
|
||||
/// Destroys metal layer on specified window surface.
|
||||
void DestroyMetalLayer(WindowInfo* wi);
|
||||
void DestroyMetalLayer(const WindowInfo& wi, void* layer);
|
||||
} // namespace CocoaTools
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
|
||||
LOG_CHANNEL(MetalDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
MetalStreamBuffer::MetalStreamBuffer() = default;
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(OpenGLContext);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
static bool ShouldPreferESContext()
|
||||
{
|
||||
@ -105,13 +105,11 @@ static void DisableBrokenExtensions(const char* gl_vendor, const char* gl_render
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLContext::OpenGLContext(const WindowInfo& wi) : m_wi(wi)
|
||||
{
|
||||
}
|
||||
OpenGLContext::OpenGLContext() = default;
|
||||
|
||||
OpenGLContext::~OpenGLContext() = default;
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContext::Create(WindowInfo& wi, SurfaceHandle* surface, Error* error)
|
||||
{
|
||||
static constexpr std::array<Version, 14> vlist = {{{Profile::Core, 4, 6},
|
||||
{Profile::Core, 4, 5},
|
||||
@ -149,22 +147,22 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error
|
||||
|
||||
std::unique_ptr<OpenGLContext> context;
|
||||
#if defined(_WIN32) && !defined(_M_ARM64)
|
||||
context = OpenGLContextWGL::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextWGL::Create(wi, surface, versions_to_try, error);
|
||||
#elif defined(__APPLE__)
|
||||
context = OpenGLContextAGL::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextAGL::Create(wi, surface, versions_to_try, error);
|
||||
#elif defined(__ANDROID__)
|
||||
context = OpenGLContextEGLAndroid::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error);
|
||||
#else
|
||||
#if defined(ENABLE_X11)
|
||||
if (wi.type == WindowInfo::Type::X11)
|
||||
context = OpenGLContextEGLX11::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextEGLX11::Create(wi, surface, versions_to_try, error);
|
||||
#endif
|
||||
#if defined(ENABLE_WAYLAND)
|
||||
if (wi.type == WindowInfo::Type::Wayland)
|
||||
context = OpenGLContextEGLWayland::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextEGLWayland::Create(wi, surface, versions_to_try, error);
|
||||
#endif
|
||||
if (wi.type == WindowInfo::Type::Surfaceless)
|
||||
context = OpenGLContextEGL::Create(wi, versions_to_try, error);
|
||||
context = OpenGLContextEGL::Create(wi, surface, versions_to_try, error);
|
||||
#endif
|
||||
|
||||
if (!context)
|
||||
|
@ -15,9 +15,12 @@ class Error;
|
||||
class OpenGLContext
|
||||
{
|
||||
public:
|
||||
OpenGLContext(const WindowInfo& wi);
|
||||
OpenGLContext();
|
||||
virtual ~OpenGLContext();
|
||||
|
||||
using SurfaceHandle = void*;
|
||||
static constexpr SurfaceHandle MAIN_SURFACE = nullptr;
|
||||
|
||||
enum class Profile
|
||||
{
|
||||
NoProfile,
|
||||
@ -32,26 +35,22 @@ public:
|
||||
int minor_version;
|
||||
};
|
||||
|
||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_wi; }
|
||||
ALWAYS_INLINE bool IsGLES() const { return (m_version.profile == Profile::ES); }
|
||||
ALWAYS_INLINE u32 GetSurfaceWidth() const { return m_wi.surface_width; }
|
||||
ALWAYS_INLINE u32 GetSurfaceHeight() const { return m_wi.surface_height; }
|
||||
ALWAYS_INLINE GPUTexture::Format GetSurfaceFormat() const { return m_wi.surface_format; }
|
||||
|
||||
virtual void* GetProcAddress(const char* name) = 0;
|
||||
virtual bool ChangeSurface(const WindowInfo& new_wi) = 0;
|
||||
virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0;
|
||||
virtual SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) = 0;
|
||||
virtual void DestroySurface(SurfaceHandle handle) = 0;
|
||||
virtual void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) = 0;
|
||||
virtual bool SwapBuffers() = 0;
|
||||
virtual bool IsCurrent() const = 0;
|
||||
virtual bool MakeCurrent() = 0;
|
||||
virtual bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) = 0;
|
||||
virtual bool DoneCurrent() = 0;
|
||||
virtual bool SupportsNegativeSwapInterval() const = 0;
|
||||
virtual bool SetSwapInterval(s32 interval) = 0;
|
||||
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) = 0;
|
||||
virtual bool SetSwapInterval(s32 interval, Error* error = nullptr) = 0;
|
||||
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) = 0;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface, Error* error);
|
||||
|
||||
protected:
|
||||
WindowInfo m_wi;
|
||||
Version m_version = {};
|
||||
};
|
||||
|
@ -18,32 +18,30 @@ struct NSView;
|
||||
class OpenGLContextAGL final : public OpenGLContext
|
||||
{
|
||||
public:
|
||||
OpenGLContextAGL(const WindowInfo& wi);
|
||||
OpenGLContextAGL();
|
||||
~OpenGLContextAGL() override;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
void* GetProcAddress(const char* name) override;
|
||||
bool ChangeSurface(const WindowInfo& new_wi) override;
|
||||
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
||||
SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
|
||||
void DestroySurface(SurfaceHandle handle) override;
|
||||
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
|
||||
bool SwapBuffers() override;
|
||||
bool IsCurrent() const override;
|
||||
bool MakeCurrent() override;
|
||||
bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
|
||||
bool DoneCurrent() override;
|
||||
bool SupportsNegativeSwapInterval() const override;
|
||||
bool SetSwapInterval(s32 interval) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
|
||||
bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE 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);
|
||||
bool CreateContext(NSOpenGLContext* share_context, int profile, bool make_current, Error* error);
|
||||
void BindContextToView();
|
||||
|
||||
// returns true if dimensions have changed
|
||||
bool UpdateDimensions();
|
||||
static void BindContextToView(WindowInfo& wi, NSOpenGLContext* context);
|
||||
static void UpdateSurfaceSize(WindowInfo& wi, NSOpenGLContext* context);
|
||||
|
||||
NSOpenGLContext* m_context = nullptr;
|
||||
NSOpenGLPixelFormat* m_pixel_format = nullptr;
|
||||
|
@ -9,9 +9,9 @@
|
||||
|
||||
#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);
|
||||
if (!m_opengl_module_handle)
|
||||
@ -33,24 +33,28 @@ OpenGLContextAGL::~OpenGLContextAGL()
|
||||
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);
|
||||
if (!context->Initialize(versions_to_try, error))
|
||||
std::unique_ptr<OpenGLContextAGL> context = std::make_unique<OpenGLContextAGL>();
|
||||
if (!context->Initialize(wi, surface, versions_to_try, error))
|
||||
return nullptr;
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
BindContextToView(wi, m_context);
|
||||
*surface = wi.window_handle;
|
||||
m_version = cv;
|
||||
return true;
|
||||
return MakeCurrent(*surface, error);
|
||||
}
|
||||
else if (cv.profile == Profile::Core)
|
||||
{
|
||||
@ -59,10 +63,12 @@ bool OpenGLContextAGL::Initialize(const std::span<const Version> versions_to_try
|
||||
|
||||
const NSOpenGLPixelFormatAttribute profile =
|
||||
(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;
|
||||
return true;
|
||||
return MakeCurrent(*surface, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,41 +86,31 @@ void* OpenGLContextAGL::GetProcAddress(const char* 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;
|
||||
BindContextToView();
|
||||
return true;
|
||||
if (m_context.view != nil)
|
||||
{
|
||||
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;
|
||||
const CGFloat window_scale = [[GetView() 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 (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;
|
||||
DebugAssert(m_context.view == handle);
|
||||
UpdateSurfaceSize(wi, m_context);
|
||||
}
|
||||
|
||||
bool OpenGLContextAGL::SwapBuffers()
|
||||
@ -128,8 +124,9 @@ bool OpenGLContextAGL::IsCurrent() const
|
||||
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];
|
||||
return true;
|
||||
}
|
||||
@ -145,35 +142,21 @@ bool OpenGLContextAGL::SupportsNegativeSwapInterval() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenGLContextAGL::SetSwapInterval(s32 interval)
|
||||
bool OpenGLContextAGL::SetSwapInterval(s32 interval, Error* error)
|
||||
{
|
||||
GLint gl_interval = static_cast<GLint>(interval);
|
||||
[m_context setValues:&gl_interval forParameter:NSOpenGLCPSwapInterval];
|
||||
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);
|
||||
|
||||
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;
|
||||
Error::SetStringView(error, "Not supported on this backend.");
|
||||
return {};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -201,26 +184,18 @@ bool OpenGLContextAGL::CreateContext(NSOpenGLContext* share_context, int profile
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_wi.type == WindowInfo::Type::MacOS)
|
||||
BindContextToView();
|
||||
|
||||
if (make_current)
|
||||
[m_context makeCurrentContext];
|
||||
|
||||
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];
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
|
||||
UpdateDimensions();
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
[window makeFirstResponder:view];
|
||||
[m_context setView:view];
|
||||
[context setView:view];
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
};
|
||||
|
||||
@ -228,4 +203,32 @@ void OpenGLContextAGL::BindContextToView()
|
||||
block();
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
LOG_CHANNEL(OpenGLContext);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
static DynamicLibrary s_egl_library;
|
||||
static std::atomic_uint32_t s_egl_refcount = 0;
|
||||
@ -67,34 +67,42 @@ static bool LoadGLADEGL(EGLDisplay display, Error* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
OpenGLContextEGL::OpenGLContextEGL(const WindowInfo& wi) : OpenGLContext(wi)
|
||||
OpenGLContextEGL::OpenGLContextEGL() : OpenGLContext()
|
||||
{
|
||||
LoadEGL();
|
||||
}
|
||||
|
||||
OpenGLContextEGL::~OpenGLContextEGL()
|
||||
{
|
||||
DestroySurface();
|
||||
DestroyContext();
|
||||
if (m_context != EGL_NO_CONTEXT && eglGetCurrentContext() == m_context)
|
||||
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (m_pbuffer_surface != EGL_NO_SURFACE)
|
||||
eglDestroySurface(m_display, m_pbuffer_surface);
|
||||
|
||||
if (m_context != EGL_NO_CONTEXT)
|
||||
eglDestroyContext(m_display, m_context);
|
||||
|
||||
UnloadEGL();
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGL::Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>(wi);
|
||||
if (!context->Initialize(versions_to_try, error))
|
||||
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>();
|
||||
if (!context->Initialize(wi, surface, versions_to_try, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Error* error)
|
||||
bool OpenGLContextEGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try,
|
||||
Error* error)
|
||||
{
|
||||
if (!LoadGLADEGL(EGL_NO_DISPLAY, error))
|
||||
return false;
|
||||
|
||||
m_display = GetPlatformDisplay(error);
|
||||
m_display = GetPlatformDisplay(wi, error);
|
||||
if (m_display == EGL_NO_DISPLAY)
|
||||
return false;
|
||||
|
||||
@ -117,7 +125,7 @@ bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Erro
|
||||
|
||||
for (const Version& cv : versions_to_try)
|
||||
{
|
||||
if (CreateContextAndSurface(cv, nullptr, true))
|
||||
if (CreateContextAndSurface(wi, surface, cv, nullptr, true, error))
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -125,24 +133,30 @@ bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Erro
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGL::GetPlatformDisplay(Error* error)
|
||||
EGLDisplay OpenGLContextEGL::GetPlatformDisplay(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless");
|
||||
EGLDisplay dpy =
|
||||
TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless");
|
||||
if (dpy == EGL_NO_DISPLAY)
|
||||
dpy = GetFallbackDisplay(error);
|
||||
dpy = GetFallbackDisplay(wi.display_connection, error);
|
||||
|
||||
return dpy;
|
||||
}
|
||||
|
||||
EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
|
||||
EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
|
||||
{
|
||||
EGLSurface surface = TryCreatePlatformSurface(config, win, error);
|
||||
if (!surface)
|
||||
surface = CreateFallbackSurface(config, win, error);
|
||||
EGLSurface surface = TryCreatePlatformSurface(config, wi.window_handle, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
surface = CreateFallbackSurface(config, wi.window_handle, error);
|
||||
return surface;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char* platform_ext)
|
||||
bool OpenGLContextEGL::SupportsSurfaceless() const
|
||||
{
|
||||
return GLAD_EGL_KHR_surfaceless_context;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext)
|
||||
{
|
||||
const char* extensions_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
||||
if (!extensions_str)
|
||||
@ -160,7 +174,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
|
||||
(PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||
if (get_platform_display_ext)
|
||||
{
|
||||
dpy = get_platform_display_ext(platform, m_wi.display_connection, nullptr);
|
||||
dpy = get_platform_display_ext(platform, display, nullptr);
|
||||
m_use_ext_platform_base = (dpy != EGL_NO_DISPLAY);
|
||||
if (!m_use_ext_platform_base)
|
||||
{
|
||||
@ -181,7 +195,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
|
||||
return dpy;
|
||||
}
|
||||
|
||||
EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* win, Error* error)
|
||||
EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* window, Error* error)
|
||||
{
|
||||
EGLSurface surface = EGL_NO_SURFACE;
|
||||
if (m_use_ext_platform_base)
|
||||
@ -190,7 +204,7 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
|
||||
(PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
|
||||
if (create_platform_window_surface_ext)
|
||||
{
|
||||
surface = create_platform_window_surface_ext(m_display, config, win, nullptr);
|
||||
surface = create_platform_window_surface_ext(m_display, config, window, nullptr);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
{
|
||||
const EGLint err = eglGetError();
|
||||
@ -206,11 +220,11 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
|
||||
return surface;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGL::GetFallbackDisplay(Error* error)
|
||||
EGLDisplay OpenGLContextEGL::GetFallbackDisplay(void* display, Error* error)
|
||||
{
|
||||
WARNING_LOG("Using fallback eglGetDisplay() path.");
|
||||
|
||||
EGLDisplay dpy = eglGetDisplay(m_wi.display_connection);
|
||||
EGLDisplay dpy = eglGetDisplay((EGLNativeDisplayType)display);
|
||||
if (dpy == EGL_NO_DISPLAY)
|
||||
{
|
||||
const EGLint err = eglGetError();
|
||||
@ -234,61 +248,56 @@ EGLSurface OpenGLContextEGL::CreateFallbackSurface(EGLConfig config, void* win,
|
||||
return surface;
|
||||
}
|
||||
|
||||
void OpenGLContextEGL::DestroyPlatformSurface(EGLSurface surface)
|
||||
{
|
||||
eglDestroySurface(m_display, surface);
|
||||
}
|
||||
|
||||
void* OpenGLContextEGL::GetProcAddress(const char* name)
|
||||
{
|
||||
return reinterpret_cast<void*>(eglGetProcAddress(name));
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::ChangeSurface(const WindowInfo& new_wi)
|
||||
OpenGLContext::SurfaceHandle OpenGLContextEGL::CreateSurface(WindowInfo& wi, Error* error /* = nullptr */)
|
||||
{
|
||||
const bool was_current = (eglGetCurrentContext() == m_context);
|
||||
if (was_current)
|
||||
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (m_surface != EGL_NO_SURFACE)
|
||||
if (wi.IsSurfaceless()) [[unlikely]]
|
||||
{
|
||||
eglDestroySurface(m_display, m_surface);
|
||||
m_surface = EGL_NO_SURFACE;
|
||||
Error::SetStringView(error, "Trying to create a surfaceless surface.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_wi = new_wi;
|
||||
if (!CreateSurface())
|
||||
return false;
|
||||
EGLSurface surface = CreatePlatformSurface(m_config, wi, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
return nullptr;
|
||||
|
||||
if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
|
||||
{
|
||||
ERROR_LOG("Failed to make context current again after surface change");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
UpdateWindowInfoSize(wi, surface);
|
||||
return (SurfaceHandle)surface;
|
||||
}
|
||||
|
||||
void OpenGLContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
|
||||
void OpenGLContextEGL::DestroySurface(SurfaceHandle handle)
|
||||
{
|
||||
if (new_surface_width == 0 && new_surface_height == 0)
|
||||
{
|
||||
EGLint surface_width, surface_height;
|
||||
if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
|
||||
eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) [[likely]]
|
||||
{
|
||||
m_wi.surface_width = static_cast<u32>(surface_width);
|
||||
m_wi.surface_height = static_cast<u32>(surface_height);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
|
||||
}
|
||||
}
|
||||
// pbuffer surface?
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
m_wi.surface_width = new_surface_width;
|
||||
m_wi.surface_height = new_surface_height;
|
||||
EGLSurface surface = (EGLSurface)handle;
|
||||
if (eglGetCurrentSurface(EGL_DRAW) == surface)
|
||||
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
DestroyPlatformSurface(surface);
|
||||
}
|
||||
|
||||
void OpenGLContextEGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
|
||||
{
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
UpdateWindowInfoSize(wi, (EGLSurface)handle);
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::SwapBuffers()
|
||||
{
|
||||
return eglSwapBuffers(m_display, m_surface);
|
||||
return eglSwapBuffers(m_display, m_current_surface);
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::IsCurrent() const
|
||||
@ -296,20 +305,29 @@ bool OpenGLContextEGL::IsCurrent() const
|
||||
return m_context && eglGetCurrentContext() == m_context;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::MakeCurrent()
|
||||
bool OpenGLContextEGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */)
|
||||
{
|
||||
if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) [[unlikely]]
|
||||
EGLSurface esurface = surface ? (EGLSurface)surface : GetSurfacelessSurface();
|
||||
if (esurface == m_current_surface)
|
||||
return true;
|
||||
|
||||
if (!eglMakeCurrent(m_display, esurface, esurface, m_context)) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
|
||||
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_surface = esurface;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::DoneCurrent()
|
||||
{
|
||||
return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (!eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT))
|
||||
return false;
|
||||
|
||||
m_current_surface = EGL_NO_SURFACE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::SupportsNegativeSwapInterval() const
|
||||
@ -317,17 +335,24 @@ bool OpenGLContextEGL::SupportsNegativeSwapInterval() const
|
||||
return m_supports_negative_swap_interval;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::SetSwapInterval(s32 interval)
|
||||
bool OpenGLContextEGL::SetSwapInterval(s32 interval, Error* error /* = nullptr */)
|
||||
{
|
||||
return eglSwapInterval(m_display, interval);
|
||||
if (!eglSwapInterval(m_display, interval))
|
||||
{
|
||||
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(const WindowInfo& wi, Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>(wi);
|
||||
std::unique_ptr<OpenGLContextEGL> context = std::make_unique<OpenGLContextEGL>();
|
||||
context->m_display = m_display;
|
||||
|
||||
if (!context->CreateContextAndSurface(m_version, m_context, false))
|
||||
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create context/surface");
|
||||
return nullptr;
|
||||
@ -336,63 +361,33 @@ std::unique_ptr<OpenGLContext> OpenGLContextEGL::CreateSharedContext(const Windo
|
||||
return context;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::CreateSurface()
|
||||
EGLSurface OpenGLContextEGL::GetSurfacelessSurface()
|
||||
{
|
||||
if (m_wi.type == WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
if (GLAD_EGL_KHR_surfaceless_context)
|
||||
return true;
|
||||
else
|
||||
return CreatePBufferSurface();
|
||||
}
|
||||
|
||||
Error error;
|
||||
m_surface = CreatePlatformSurface(m_config, m_wi.window_handle, &error);
|
||||
if (m_surface == EGL_NO_SURFACE)
|
||||
{
|
||||
ERROR_LOG("Failed to create platform surface: {}", error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some implementations may require the size to be queried at runtime.
|
||||
EGLint surface_width, surface_height;
|
||||
if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) &&
|
||||
eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height))
|
||||
{
|
||||
m_wi.surface_width = static_cast<u32>(surface_width);
|
||||
m_wi.surface_height = static_cast<u32>(surface_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
|
||||
}
|
||||
|
||||
m_wi.surface_format = GetSurfaceTextureFormat();
|
||||
|
||||
return true;
|
||||
return SupportsSurfaceless() ? EGL_NO_SURFACE : GetPBufferSurface(nullptr);
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::CreatePBufferSurface()
|
||||
EGLSurface OpenGLContextEGL::GetPBufferSurface(Error* error)
|
||||
{
|
||||
const u32 width = std::max<u32>(m_wi.surface_width, 1);
|
||||
const u32 height = std::max<u32>(m_wi.surface_height, 1);
|
||||
if (m_pbuffer_surface)
|
||||
return m_pbuffer_surface;
|
||||
|
||||
// TODO: Format
|
||||
EGLint attrib_list[] = {
|
||||
EGL_WIDTH, static_cast<EGLint>(width), EGL_HEIGHT, static_cast<EGLint>(height), EGL_NONE,
|
||||
EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE,
|
||||
};
|
||||
|
||||
m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list);
|
||||
if (!m_surface) [[unlikely]]
|
||||
m_pbuffer_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list);
|
||||
if (!m_pbuffer_surface) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError());
|
||||
return false;
|
||||
if (error)
|
||||
error->SetStringFmt("eglCreatePbufferSurface() failed: {}", eglGetError());
|
||||
else
|
||||
ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_wi.surface_format = GetSurfaceTextureFormat();
|
||||
|
||||
DEV_LOG("Created {}x{} pbuffer surface", width, height);
|
||||
return true;
|
||||
DEV_LOG("Created pbuffer surface");
|
||||
return m_pbuffer_surface;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format)
|
||||
@ -425,8 +420,21 @@ bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Fo
|
||||
}
|
||||
}
|
||||
|
||||
GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const
|
||||
void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const
|
||||
{
|
||||
// Some implementations may require the size to be queried at runtime.
|
||||
EGLint surface_width, surface_height;
|
||||
if (eglQuerySurface(m_display, surface, EGL_WIDTH, &surface_width) &&
|
||||
eglQuerySurface(m_display, surface, EGL_HEIGHT, &surface_height))
|
||||
{
|
||||
wi.surface_width = static_cast<u16>(surface_width);
|
||||
wi.surface_height = static_cast<u16>(surface_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
|
||||
}
|
||||
|
||||
int red_size = 0, green_size = 0, blue_size = 0, alpha_size = 0;
|
||||
eglGetConfigAttrib(m_display, m_config, EGL_RED_SIZE, &red_size);
|
||||
eglGetConfigAttrib(m_display, m_config, EGL_GREEN_SIZE, &green_size);
|
||||
@ -435,48 +443,25 @@ GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const
|
||||
|
||||
if (red_size == 5 && green_size == 6 && blue_size == 5)
|
||||
{
|
||||
return GPUTexture::Format::RGB565;
|
||||
wi.surface_format = GPUTexture::Format::RGB565;
|
||||
}
|
||||
else if (red_size == 5 && green_size == 5 && blue_size == 5 && alpha_size == 1)
|
||||
{
|
||||
return GPUTexture::Format::RGBA5551;
|
||||
wi.surface_format = GPUTexture::Format::RGBA5551;
|
||||
}
|
||||
else if (red_size == 8 && green_size == 8 && blue_size == 8 && alpha_size == 8)
|
||||
{
|
||||
return GPUTexture::Format::RGBA8;
|
||||
wi.surface_format = GPUTexture::Format::RGBA8;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("Unknown surface format: R={}, G={}, B={}, A={}", red_size, green_size, blue_size, alpha_size);
|
||||
return GPUTexture::Format::RGBA8;
|
||||
wi.surface_format = GPUTexture::Format::RGBA8;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContextEGL::DestroyContext()
|
||||
{
|
||||
if (eglGetCurrentContext() == m_context)
|
||||
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (m_context != EGL_NO_CONTEXT)
|
||||
{
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContextEGL::DestroySurface()
|
||||
{
|
||||
if (eglGetCurrentSurface(EGL_DRAW) == m_surface)
|
||||
eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
if (m_surface != EGL_NO_SURFACE)
|
||||
{
|
||||
eglDestroySurface(m_display, m_surface);
|
||||
m_surface = EGL_NO_SURFACE;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_context)
|
||||
bool OpenGLContextEGL::CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version,
|
||||
EGLContext share_context, Error* error)
|
||||
{
|
||||
DEV_LOG("Trying version {}.{} ({})", version.major_version, version.minor_version,
|
||||
version.profile == OpenGLContext::Profile::ES ?
|
||||
@ -489,18 +474,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) :
|
||||
EGL_OPENGL_BIT,
|
||||
EGL_SURFACE_TYPE,
|
||||
(m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0,
|
||||
surfaceless ? 0 : EGL_WINDOW_BIT,
|
||||
};
|
||||
int nsurface_attribs = 4;
|
||||
|
||||
const GPUTexture::Format format = m_wi.surface_format;
|
||||
if (format == GPUTexture::Format::Unknown)
|
||||
{
|
||||
WARNING_LOG("Surface format not specified, assuming RGBA8.");
|
||||
m_wi.surface_format = GPUTexture::Format::RGBA8;
|
||||
}
|
||||
if (surface_format == GPUTexture::Format::Unknown)
|
||||
surface_format = GPUTexture::Format::RGBA8;
|
||||
|
||||
switch (m_wi.surface_format)
|
||||
switch (surface_format)
|
||||
{
|
||||
case GPUTexture::Format::RGBA8:
|
||||
surface_attribs[nsurface_attribs++] = EGL_RED_SIZE;
|
||||
@ -522,11 +503,8 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
surface_attribs[nsurface_attribs++] = 5;
|
||||
break;
|
||||
|
||||
case GPUTexture::Format::Unknown:
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
Error::SetStringFmt(error, "Unsupported texture format {}", GPUTexture::GetFormatName(surface_format));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -536,14 +514,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
EGLint num_configs;
|
||||
if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0)
|
||||
{
|
||||
ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<EGLConfig> configs(static_cast<u32>(num_configs));
|
||||
if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs))
|
||||
{
|
||||
ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
configs.resize(static_cast<u32>(num_configs));
|
||||
@ -551,7 +529,7 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
std::optional<EGLConfig> config;
|
||||
for (EGLConfig check_config : configs)
|
||||
{
|
||||
if (CheckConfigSurfaceFormat(check_config, m_wi.surface_format))
|
||||
if (CheckConfigSurfaceFormat(check_config, surface_format))
|
||||
{
|
||||
config = check_config;
|
||||
break;
|
||||
@ -578,14 +556,15 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
|
||||
if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API))
|
||||
{
|
||||
ERROR_LOG("eglBindAPI({}) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
|
||||
Error::SetStringFmt(error, "eglBindAPI({}) failed",
|
||||
(version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_context = eglCreateContext(m_display, config.value(), share_context, attribs);
|
||||
if (!m_context)
|
||||
{
|
||||
ERROR_LOG("eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
Error::SetStringFmt(error, "eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -611,30 +590,62 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current)
|
||||
bool OpenGLContextEGL::CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version,
|
||||
EGLContext share_context, bool make_current, Error* error)
|
||||
{
|
||||
if (!CreateContext(version, share_context))
|
||||
if (!CreateContext(wi.IsSurfaceless(), wi.surface_format, version, share_context, error))
|
||||
return false;
|
||||
|
||||
if (!CreateSurface())
|
||||
// create actual surface, need to handle surfaceless here
|
||||
EGLSurface esurface;
|
||||
if (wi.IsSurfaceless())
|
||||
{
|
||||
ERROR_LOG("Failed to create surface for context");
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_CONTEXT;
|
||||
return false;
|
||||
if (!SupportsSurfaceless())
|
||||
{
|
||||
esurface = GetPBufferSurface(error);
|
||||
if (esurface == EGL_NO_SURFACE)
|
||||
{
|
||||
ERROR_LOG("Failed to create pbuffer surface for context");
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_SURFACE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
esurface = EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
*surface = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
esurface = CreatePlatformSurface(m_config, wi, error);
|
||||
if (esurface == EGL_NO_SURFACE)
|
||||
{
|
||||
ERROR_LOG("Failed to create surface for context");
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_SURFACE;
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateWindowInfoSize(wi, esurface);
|
||||
*surface = esurface;
|
||||
}
|
||||
|
||||
if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
|
||||
if (make_current)
|
||||
{
|
||||
ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
|
||||
if (m_surface != EGL_NO_SURFACE)
|
||||
if (!eglMakeCurrent(m_display, esurface, esurface, m_context))
|
||||
{
|
||||
eglDestroySurface(m_display, m_surface);
|
||||
m_surface = EGL_NO_SURFACE;
|
||||
Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError());
|
||||
if (esurface != EGL_NO_SURFACE && esurface != m_pbuffer_surface)
|
||||
DestroyPlatformSurface(esurface);
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_CONTEXT;
|
||||
return false;
|
||||
}
|
||||
eglDestroyContext(m_display, m_context);
|
||||
m_context = EGL_NO_CONTEXT;
|
||||
return false;
|
||||
|
||||
m_current_surface = esurface;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -10,48 +10,54 @@
|
||||
class OpenGLContextEGL : public OpenGLContext
|
||||
{
|
||||
public:
|
||||
OpenGLContextEGL(const WindowInfo& wi);
|
||||
OpenGLContextEGL();
|
||||
~OpenGLContextEGL() override;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
void* GetProcAddress(const char* name) override;
|
||||
virtual bool ChangeSurface(const WindowInfo& new_wi) override;
|
||||
virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
||||
SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
|
||||
void DestroySurface(SurfaceHandle handle) override;
|
||||
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
|
||||
bool SwapBuffers() override;
|
||||
bool IsCurrent() const override;
|
||||
bool MakeCurrent() override;
|
||||
bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
|
||||
bool DoneCurrent() override;
|
||||
bool SupportsNegativeSwapInterval() const override;
|
||||
bool SetSwapInterval(s32 interval) override;
|
||||
virtual std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
|
||||
bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
|
||||
|
||||
protected:
|
||||
virtual EGLDisplay GetPlatformDisplay(Error* error);
|
||||
virtual EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error);
|
||||
virtual EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error);
|
||||
virtual EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error);
|
||||
virtual void DestroyPlatformSurface(EGLSurface surface);
|
||||
|
||||
EGLDisplay TryGetPlatformDisplay(EGLenum platform, const char* platform_ext);
|
||||
bool SupportsSurfaceless() const;
|
||||
|
||||
EGLDisplay TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext);
|
||||
EGLSurface TryCreatePlatformSurface(EGLConfig config, void* window, Error* error);
|
||||
EGLDisplay GetFallbackDisplay(Error* error);
|
||||
EGLDisplay GetFallbackDisplay(void* display, Error* error);
|
||||
EGLSurface CreateFallbackSurface(EGLConfig config, void* window, Error* error);
|
||||
|
||||
bool Initialize(std::span<const Version> versions_to_try, Error* error);
|
||||
bool CreateContext(const Version& version, EGLContext share_context);
|
||||
bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current);
|
||||
bool CreateSurface();
|
||||
bool CreatePBufferSurface();
|
||||
bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try, Error* error);
|
||||
bool CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version,
|
||||
EGLContext share_context, Error* error);
|
||||
bool CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version, EGLContext share_context,
|
||||
bool make_current, Error* error);
|
||||
EGLSurface GetPBufferSurface(Error* error);
|
||||
EGLSurface GetSurfacelessSurface();
|
||||
bool CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format);
|
||||
GPUTexture::Format GetSurfaceTextureFormat() const;
|
||||
void DestroyContext();
|
||||
void DestroySurface();
|
||||
void UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const;
|
||||
|
||||
EGLDisplay m_display = EGL_NO_DISPLAY;
|
||||
EGLSurface m_surface = EGL_NO_SURFACE;
|
||||
EGLContext m_context = EGL_NO_CONTEXT;
|
||||
EGLSurface m_current_surface = EGL_NO_SURFACE;
|
||||
|
||||
EGLConfig m_config = {};
|
||||
|
||||
EGLSurface m_pbuffer_surface = EGL_NO_SURFACE;
|
||||
|
||||
bool m_use_ext_platform_base = false;
|
||||
bool m_supports_negative_swap_interval = false;
|
||||
};
|
||||
|
@ -3,91 +3,22 @@
|
||||
|
||||
#include "opengl_context_egl_wayland.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1";
|
||||
|
||||
OpenGLContextEGLWayland::OpenGLContextEGLWayland(const WindowInfo& wi) : OpenGLContextEGL(wi)
|
||||
{
|
||||
}
|
||||
OpenGLContextEGLWayland::OpenGLContextEGLWayland() = default;
|
||||
|
||||
OpenGLContextEGLWayland::~OpenGLContextEGLWayland()
|
||||
{
|
||||
if (m_wl_window)
|
||||
m_wl_egl_window_destroy(m_wl_window);
|
||||
AssertMsg(m_wl_window_map.empty(), "WL window map should be empty on destructor.");
|
||||
if (m_wl_module)
|
||||
dlclose(m_wl_module);
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::Create(const WindowInfo& wi,
|
||||
std::span<const Version> versions_to_try, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>(wi);
|
||||
if (!context->LoadModule(error) || !context->Initialize(versions_to_try, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::CreateSharedContext(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>(wi);
|
||||
context->m_display = m_display;
|
||||
|
||||
if (!context->LoadModule(error) || !context->CreateContextAndSurface(m_version, m_context, false))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void OpenGLContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height)
|
||||
{
|
||||
if (m_wl_window)
|
||||
m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0);
|
||||
|
||||
OpenGLContextEGL::ResizeSurface(new_surface_width, new_surface_height);
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(Error* error)
|
||||
{
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland");
|
||||
if (dpy == EGL_NO_DISPLAY)
|
||||
dpy = GetFallbackDisplay(error);
|
||||
|
||||
return dpy;
|
||||
}
|
||||
|
||||
EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
|
||||
{
|
||||
if (m_wl_window)
|
||||
{
|
||||
m_wl_egl_window_destroy(m_wl_window);
|
||||
m_wl_window = nullptr;
|
||||
}
|
||||
|
||||
m_wl_window = m_wl_egl_window_create(static_cast<wl_surface*>(win), m_wi.surface_width, m_wi.surface_height);
|
||||
if (!m_wl_window)
|
||||
{
|
||||
Error::SetStringView(error, "wl_egl_window_create() failed");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
EGLSurface surface = TryCreatePlatformSurface(config, m_wl_window, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
{
|
||||
surface = CreateFallbackSurface(config, m_wl_window, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
{
|
||||
m_wl_egl_window_destroy(m_wl_window);
|
||||
m_wl_window = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
bool OpenGLContextEGLWayland::LoadModule(Error* error)
|
||||
{
|
||||
m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL);
|
||||
@ -112,3 +43,78 @@ bool OpenGLContextEGLWayland::LoadModule(Error* error)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland");
|
||||
if (dpy == EGL_NO_DISPLAY)
|
||||
dpy = GetFallbackDisplay(wi.display_connection, error);
|
||||
|
||||
return dpy;
|
||||
}
|
||||
|
||||
EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
|
||||
{
|
||||
struct wl_egl_window* wl_window =
|
||||
m_wl_egl_window_create(static_cast<wl_surface*>(wi.window_handle), wi.surface_width, wi.surface_height);
|
||||
if (!wl_window)
|
||||
{
|
||||
Error::SetStringView(error, "wl_egl_window_create() failed");
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
EGLSurface surface = TryCreatePlatformSurface(config, wl_window, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
{
|
||||
surface = CreateFallbackSurface(config, wl_window, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
{
|
||||
m_wl_egl_window_destroy(wl_window);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_wl_window_map.emplace(surface, wl_window);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void OpenGLContextEGLWayland::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
|
||||
{
|
||||
const auto it = m_wl_window_map.find((EGLSurface)handle);
|
||||
AssertMsg(it != m_wl_window_map.end(), "Missing WL window");
|
||||
m_wl_egl_window_resize(it->second, wi.surface_width, wi.surface_height, 0, 0);
|
||||
|
||||
OpenGLContextEGL::ResizeSurface(wi, handle);
|
||||
}
|
||||
|
||||
void OpenGLContextEGLWayland::DestroyPlatformSurface(EGLSurface surface)
|
||||
{
|
||||
const auto it = m_wl_window_map.find((EGLSurface)surface);
|
||||
AssertMsg(it != m_wl_window_map.end(), "Missing WL window");
|
||||
m_wl_egl_window_destroy(it->second);
|
||||
m_wl_window_map.erase(it);
|
||||
|
||||
OpenGLContextEGL::DestroyPlatformSurface(surface);
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>();
|
||||
if (!context->LoadModule(error) || !context->Initialize(wi, surface, versions_to_try, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLWayland::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLWayland> context = std::make_unique<OpenGLContextEGLWayland>();
|
||||
context->m_display = m_display;
|
||||
|
||||
if (!context->LoadModule(error) || !context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
@ -10,26 +10,32 @@
|
||||
class OpenGLContextEGLWayland final : public OpenGLContextEGL
|
||||
{
|
||||
public:
|
||||
OpenGLContextEGLWayland(const WindowInfo& wi);
|
||||
OpenGLContextEGLWayland();
|
||||
~OpenGLContextEGLWayland() override;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
|
||||
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
|
||||
|
||||
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
|
||||
|
||||
protected:
|
||||
EGLDisplay GetPlatformDisplay(Error* error) override;
|
||||
EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override;
|
||||
EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
|
||||
EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
|
||||
void DestroyPlatformSurface(EGLSurface surface) override;
|
||||
|
||||
private:
|
||||
bool LoadModule(Error* error);
|
||||
// Truely awful, I hate this so much, and all this work for a bloody windowing system where
|
||||
// we can't even position windows to begin with, which makes multi-window kinda pointless...
|
||||
using WLWindowMap = std::unordered_map<EGLSurface, struct wl_egl_window*>;
|
||||
|
||||
wl_egl_window* m_wl_window = nullptr;
|
||||
bool LoadModule(Error* error);
|
||||
|
||||
void* m_wl_module = nullptr;
|
||||
wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height);
|
||||
void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window);
|
||||
void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy);
|
||||
|
||||
WLWindowMap m_wl_window_map;
|
||||
};
|
||||
|
@ -5,49 +5,49 @@
|
||||
|
||||
#include "common/error.h"
|
||||
|
||||
OpenGLContextEGLX11::OpenGLContextEGLX11(const WindowInfo& wi) : OpenGLContextEGL(wi)
|
||||
{
|
||||
}
|
||||
OpenGLContextEGLX11::OpenGLContextEGLX11() = default;
|
||||
|
||||
OpenGLContextEGLX11::~OpenGLContextEGLX11() = default;
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::Create(const WindowInfo& wi,
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(wi);
|
||||
if (!context->Initialize(versions_to_try, error))
|
||||
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>();
|
||||
if (!context->Initialize(wi, surface, versions_to_try, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(const WindowInfo& wi, Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>(wi);
|
||||
std::unique_ptr<OpenGLContextEGLX11> context = std::make_unique<OpenGLContextEGLX11>();
|
||||
context->m_display = m_display;
|
||||
|
||||
if (!context->CreateContextAndSurface(m_version, m_context, false))
|
||||
if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error))
|
||||
return nullptr;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(Error* error)
|
||||
EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11");
|
||||
EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11");
|
||||
if (dpy == EGL_NO_DISPLAY)
|
||||
dpy = GetFallbackDisplay(error);
|
||||
dpy = GetFallbackDisplay(wi.display_connection, error);
|
||||
|
||||
return dpy;
|
||||
}
|
||||
|
||||
EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, void* win, Error* error)
|
||||
EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error)
|
||||
{
|
||||
// This is hideous.. the EXT version requires a pointer to the window, whereas the base
|
||||
// version requires the window itself, casted to void*...
|
||||
void* win = wi.window_handle;
|
||||
EGLSurface surface = TryCreatePlatformSurface(config, &win, error);
|
||||
if (surface == EGL_NO_SURFACE)
|
||||
surface = CreateFallbackSurface(config, win, error);
|
||||
surface = CreateFallbackSurface(config, wi.window_handle, error);
|
||||
|
||||
return surface;
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,15 @@
|
||||
class OpenGLContextEGLX11 final : public OpenGLContextEGL
|
||||
{
|
||||
public:
|
||||
OpenGLContextEGLX11(const WindowInfo& wi);
|
||||
OpenGLContextEGLX11();
|
||||
~OpenGLContextEGLX11() override;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
|
||||
|
||||
protected:
|
||||
EGLDisplay GetPlatformDisplay(Error* error) override;
|
||||
EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override;
|
||||
EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override;
|
||||
EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override;
|
||||
};
|
||||
|
@ -5,77 +5,134 @@
|
||||
#include "opengl_loader.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/scoped_guard.h"
|
||||
|
||||
LOG_CHANNEL(GL::OpenGLContext);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic ignored "-Wmicrosoft-cast"
|
||||
#endif
|
||||
|
||||
static void* GetProcAddressCallback(const char* name)
|
||||
namespace dyn_libs {
|
||||
|
||||
#define OPENGL_FUNCTIONS(X) \
|
||||
X(wglCreateContext) \
|
||||
X(wglDeleteContext) \
|
||||
X(wglGetCurrentContext) \
|
||||
X(wglGetCurrentDC) \
|
||||
X(wglGetProcAddress) \
|
||||
X(wglMakeCurrent) \
|
||||
X(wglShareLists)
|
||||
|
||||
static bool LoadOpenGLLibrary(Error* error);
|
||||
static void CloseOpenGLLibrary();
|
||||
static void* GetProcAddressCallback(const char* name);
|
||||
|
||||
static DynamicLibrary s_opengl_library;
|
||||
|
||||
#define DECLARE_OPENGL_FUNCTION(F) static decltype(&::F) F;
|
||||
OPENGL_FUNCTIONS(DECLARE_OPENGL_FUNCTION)
|
||||
#undef DECLARE_OPENGL_FUNCTION
|
||||
} // namespace dyn_libs
|
||||
|
||||
bool dyn_libs::LoadOpenGLLibrary(Error* error)
|
||||
{
|
||||
void* addr = wglGetProcAddress(name);
|
||||
if (s_opengl_library.IsOpen())
|
||||
return true;
|
||||
else if (!s_opengl_library.Open("opengl32.dll", error))
|
||||
return false;
|
||||
|
||||
bool result = true;
|
||||
#define RESOLVE_OPENGL_FUNCTION(F) result = result && s_opengl_library.GetSymbol(#F, &F);
|
||||
OPENGL_FUNCTIONS(RESOLVE_OPENGL_FUNCTION);
|
||||
#undef RESOLVE_OPENGL_FUNCTION
|
||||
|
||||
if (!result)
|
||||
{
|
||||
CloseOpenGLLibrary();
|
||||
Error::SetStringView(error, "One or more required functions from opengl32.dll is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::atexit(&CloseOpenGLLibrary);
|
||||
return true;
|
||||
}
|
||||
|
||||
void dyn_libs::CloseOpenGLLibrary()
|
||||
{
|
||||
#define CLOSE_OPENGL_FUNCTION(F) F = nullptr;
|
||||
OPENGL_FUNCTIONS(CLOSE_OPENGL_FUNCTION);
|
||||
#undef CLOSE_OPENGL_FUNCTION
|
||||
|
||||
s_opengl_library.Close();
|
||||
}
|
||||
|
||||
#undef OPENGL_FUNCTIONS
|
||||
|
||||
void* dyn_libs::GetProcAddressCallback(const char* name)
|
||||
{
|
||||
void* addr = dyn_libs::wglGetProcAddress(name);
|
||||
if (addr)
|
||||
return addr;
|
||||
|
||||
// try opengl32.dll
|
||||
return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name);
|
||||
return s_opengl_library.GetSymbolAddress(name);
|
||||
}
|
||||
|
||||
static bool ReloadWGL(HDC dc)
|
||||
static bool ReloadWGL(HDC dc, Error* error)
|
||||
{
|
||||
if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
|
||||
if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)dyn_libs::wglGetProcAddress(name); }))
|
||||
{
|
||||
ERROR_LOG("Loading GLAD WGL functions failed");
|
||||
Error::SetStringView(error, "Loading GLAD WGL functions failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
OpenGLContextWGL::OpenGLContextWGL(const WindowInfo& wi) : OpenGLContext(wi)
|
||||
{
|
||||
}
|
||||
OpenGLContextWGL::OpenGLContextWGL() = default;
|
||||
|
||||
OpenGLContextWGL::~OpenGLContextWGL()
|
||||
{
|
||||
if (wglGetCurrentContext() == m_rc)
|
||||
wglMakeCurrent(m_dc, nullptr);
|
||||
|
||||
if (m_rc)
|
||||
wglDeleteContext(m_rc);
|
||||
{
|
||||
if (dyn_libs::wglGetCurrentContext() == m_rc)
|
||||
dyn_libs::wglMakeCurrent(nullptr, nullptr);
|
||||
|
||||
ReleaseDC();
|
||||
dyn_libs::wglDeleteContext(m_rc);
|
||||
}
|
||||
|
||||
if (m_pbuffer)
|
||||
{
|
||||
wglReleasePbufferDCARB(m_pbuffer, m_pbuffer_dc);
|
||||
wglDestroyPbufferARB(m_pbuffer);
|
||||
DeleteDC(m_dummy_dc);
|
||||
DestroyWindow(m_dummy_window);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextWGL::Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextWGL::Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
|
||||
if (!context->Initialize(versions_to_try, error))
|
||||
return nullptr;
|
||||
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>();
|
||||
if (!dyn_libs::LoadOpenGLLibrary(error) || !context->Initialize(wi, surface, versions_to_try, error))
|
||||
context.reset();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Error* error)
|
||||
bool OpenGLContextWGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try,
|
||||
Error* error)
|
||||
{
|
||||
if (m_wi.type == WindowInfo::Type::Win32)
|
||||
{
|
||||
if (!InitializeDC(error))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CreatePBuffer(error))
|
||||
return false;
|
||||
}
|
||||
const HDC hdc = wi.IsSurfaceless() ? GetPBufferDC(error) : CreateDCAndSetPixelFormat(wi, error);
|
||||
if (!hdc)
|
||||
return false;
|
||||
|
||||
// Everything including core/ES requires a dummy profile to load the WGL extensions.
|
||||
if (!CreateAnyContext(nullptr, true, error))
|
||||
if (!CreateAnyContext(hdc, nullptr, true, error))
|
||||
return false;
|
||||
|
||||
for (const Version& cv : versions_to_try)
|
||||
@ -84,11 +141,13 @@ bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Erro
|
||||
{
|
||||
// we already have the dummy context, so just use that
|
||||
m_version = cv;
|
||||
*surface = hdc;
|
||||
return true;
|
||||
}
|
||||
else if (CreateVersionContext(cv, nullptr, true, error))
|
||||
else if (CreateVersionContext(cv, hdc, nullptr, true, error))
|
||||
{
|
||||
m_version = cv;
|
||||
*surface = hdc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -99,65 +158,76 @@ bool OpenGLContextWGL::Initialize(std::span<const Version> versions_to_try, Erro
|
||||
|
||||
void* OpenGLContextWGL::GetProcAddress(const char* name)
|
||||
{
|
||||
return GetProcAddressCallback(name);
|
||||
return dyn_libs::GetProcAddressCallback(name);
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::ChangeSurface(const WindowInfo& new_wi)
|
||||
OpenGLContext::SurfaceHandle OpenGLContextWGL::CreateSurface(WindowInfo& wi, Error* error /*= nullptr*/)
|
||||
{
|
||||
const bool was_current = (wglGetCurrentContext() == m_rc);
|
||||
Error error;
|
||||
|
||||
ReleaseDC();
|
||||
|
||||
m_wi = new_wi;
|
||||
if (!InitializeDC(&error))
|
||||
if (wi.IsSurfaceless()) [[unlikely]]
|
||||
{
|
||||
ERROR_LOG("Failed to change surface: {}", error.GetDescription());
|
||||
return false;
|
||||
Error::SetStringView(error, "Trying to create a surfaceless surface.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (was_current && !wglMakeCurrent(m_dc, m_rc))
|
||||
{
|
||||
error.SetWin32(GetLastError());
|
||||
ERROR_LOG("Failed to make context current again after surface change: {}", error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CreateDCAndSetPixelFormat(wi, error);
|
||||
}
|
||||
|
||||
void OpenGLContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/)
|
||||
void OpenGLContextWGL::DestroySurface(SurfaceHandle handle)
|
||||
{
|
||||
// pbuffer/surfaceless?
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
// current buffer? switch to pbuffer first
|
||||
if (dyn_libs::wglGetCurrentDC() == static_cast<HDC>(handle))
|
||||
MakeCurrent(nullptr);
|
||||
|
||||
DeleteDC(static_cast<HDC>(handle));
|
||||
}
|
||||
|
||||
void OpenGLContextWGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle)
|
||||
{
|
||||
RECT client_rc = {};
|
||||
GetClientRect(GetHWND(), &client_rc);
|
||||
m_wi.surface_width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||
m_wi.surface_height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||
GetClientRect(static_cast<HWND>(wi.window_handle), &client_rc);
|
||||
wi.surface_width = static_cast<u16>(client_rc.right - client_rc.left);
|
||||
wi.surface_height = static_cast<u16>(client_rc.bottom - client_rc.top);
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::SwapBuffers()
|
||||
{
|
||||
return ::SwapBuffers(m_dc);
|
||||
return ::SwapBuffers(m_current_dc);
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::IsCurrent() const
|
||||
{
|
||||
return (m_rc && wglGetCurrentContext() == m_rc);
|
||||
return (m_rc && dyn_libs::wglGetCurrentContext() == m_rc);
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::MakeCurrent()
|
||||
bool OpenGLContextWGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */)
|
||||
{
|
||||
if (!wglMakeCurrent(m_dc, m_rc))
|
||||
const HDC new_dc = surface ? static_cast<HDC>(surface) : GetPBufferDC(error);
|
||||
if (!new_dc)
|
||||
return false;
|
||||
else if (m_current_dc == new_dc)
|
||||
return true;
|
||||
|
||||
if (!dyn_libs::wglMakeCurrent(new_dc, m_rc))
|
||||
{
|
||||
ERROR_LOG("wglMakeCurrent() failed: {}", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_dc = new_dc;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::DoneCurrent()
|
||||
{
|
||||
return wglMakeCurrent(m_dc, nullptr);
|
||||
if (!dyn_libs::wglMakeCurrent(m_current_dc, nullptr))
|
||||
return false;
|
||||
|
||||
m_current_dc = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::SupportsNegativeSwapInterval() const
|
||||
@ -165,45 +235,55 @@ bool OpenGLContextWGL::SupportsNegativeSwapInterval() const
|
||||
return GLAD_WGL_EXT_swap_control && GLAD_WGL_EXT_swap_control_tear;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::SetSwapInterval(s32 interval)
|
||||
bool OpenGLContextWGL::SetSwapInterval(s32 interval, Error* error)
|
||||
{
|
||||
if (!GLAD_WGL_EXT_swap_control)
|
||||
{
|
||||
Error::SetStringView(error, "WGL_EXT_swap_control is not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return wglSwapIntervalEXT(interval);
|
||||
if (!wglSwapIntervalEXT(interval))
|
||||
{
|
||||
Error::SetWin32(error, "wglSwapIntervalEXT() failed: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextWGL::CreateSharedContext(const WindowInfo& wi, Error* error)
|
||||
std::unique_ptr<OpenGLContext> OpenGLContextWGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface,
|
||||
Error* error)
|
||||
{
|
||||
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>(wi);
|
||||
if (wi.type == WindowInfo::Type::Win32)
|
||||
{
|
||||
if (!context->InitializeDC(error))
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context->CreatePBuffer(error))
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<OpenGLContextWGL> context = std::make_unique<OpenGLContextWGL>();
|
||||
const HDC hdc = wi.IsSurfaceless() ? context->GetPBufferDC(error) : context->CreateDCAndSetPixelFormat(wi, error);
|
||||
if (!hdc)
|
||||
return nullptr;
|
||||
|
||||
if (m_version.profile == Profile::NoProfile)
|
||||
{
|
||||
if (!context->CreateAnyContext(m_rc, false, error))
|
||||
if (!context->CreateAnyContext(hdc, m_rc, false, error))
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context->CreateVersionContext(m_version, m_rc, false, error))
|
||||
if (!context->CreateVersionContext(m_version, hdc, m_rc, false, error))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
context->m_version = m_version;
|
||||
*surface = wi.IsSurfaceless() ? hdc : nullptr;
|
||||
return context;
|
||||
}
|
||||
|
||||
HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
|
||||
HDC OpenGLContextWGL::CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error)
|
||||
{
|
||||
if (wi.type != WindowInfo::Type::Win32)
|
||||
{
|
||||
Error::SetStringFmt(error, "Unknown window info type {}", static_cast<unsigned>(wi.type));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd = {};
|
||||
pfd.nSize = sizeof(pfd);
|
||||
pfd.nVersion = 1;
|
||||
@ -215,6 +295,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
|
||||
pfd.cBlueBits = 8;
|
||||
pfd.cColorBits = 24;
|
||||
|
||||
const HWND hwnd = static_cast<HWND>(wi.window_handle);
|
||||
HDC hDC = ::GetDC(hwnd);
|
||||
if (!hDC)
|
||||
{
|
||||
@ -228,7 +309,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
|
||||
if (pf == 0)
|
||||
{
|
||||
Error::SetWin32(error, "ChoosePixelFormat() failed: ", GetLastError());
|
||||
::ReleaseDC(hwnd, hDC);
|
||||
DeleteDC(hDC);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -238,63 +319,22 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error)
|
||||
if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd))
|
||||
{
|
||||
Error::SetWin32(error, "SetPixelFormat() failed: ", GetLastError());
|
||||
::ReleaseDC(hwnd, hDC);
|
||||
DeleteDC(hDC);
|
||||
return {};
|
||||
}
|
||||
|
||||
m_wi.surface_format = GPUTexture::Format::RGBA8;
|
||||
wi.surface_format = GPUTexture::Format::RGBA8;
|
||||
return hDC;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::InitializeDC(Error* error)
|
||||
{
|
||||
if (m_wi.type == WindowInfo::Type::Win32)
|
||||
{
|
||||
m_dc = GetDCAndSetPixelFormat(GetHWND(), error);
|
||||
if (!m_dc)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (m_wi.type == WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
return CreatePBuffer(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Error::SetStringFmt(error, "Unknown window info type {}", static_cast<unsigned>(m_wi.type));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContextWGL::ReleaseDC()
|
||||
{
|
||||
if (m_pbuffer)
|
||||
{
|
||||
wglReleasePbufferDCARB(m_pbuffer, m_dc);
|
||||
m_dc = {};
|
||||
|
||||
wglDestroyPbufferARB(m_pbuffer);
|
||||
m_pbuffer = {};
|
||||
|
||||
::ReleaseDC(m_dummy_window, m_dummy_dc);
|
||||
m_dummy_dc = {};
|
||||
|
||||
DestroyWindow(m_dummy_window);
|
||||
m_dummy_window = {};
|
||||
}
|
||||
else if (m_dc)
|
||||
{
|
||||
::ReleaseDC(GetHWND(), m_dc);
|
||||
m_dc = {};
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::CreatePBuffer(Error* error)
|
||||
HDC OpenGLContextWGL::GetPBufferDC(Error* error)
|
||||
{
|
||||
static bool window_class_registered = false;
|
||||
static const wchar_t* window_class_name = L"ContextWGLPBuffer";
|
||||
|
||||
if (m_pbuffer_dc)
|
||||
return m_pbuffer_dc;
|
||||
|
||||
if (!window_class_registered)
|
||||
{
|
||||
WNDCLASSEXW wc = {};
|
||||
@ -314,24 +354,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
|
||||
if (!RegisterClassExW(&wc))
|
||||
{
|
||||
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) RegisterClassExW() failed");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
window_class_registered = true;
|
||||
}
|
||||
|
||||
Assert(m_dummy_window == NULL);
|
||||
Assert(m_pbuffer == NULL);
|
||||
|
||||
HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
|
||||
if (!hwnd)
|
||||
{
|
||||
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) CreateWindowEx() failed");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); });
|
||||
|
||||
HDC hdc = GetDCAndSetPixelFormat(hwnd, error);
|
||||
WindowInfo wi = {.type = WindowInfo::Type::Win32, .window_handle = hwnd};
|
||||
HDC hdc = CreateDCAndSetPixelFormat(wi, error);
|
||||
if (!hdc)
|
||||
return false;
|
||||
return NULL;
|
||||
|
||||
ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); });
|
||||
|
||||
@ -341,25 +385,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
|
||||
ScopedGuard temp_rc_guard([&temp_rc, hdc]() {
|
||||
if (temp_rc)
|
||||
{
|
||||
wglMakeCurrent(hdc, nullptr);
|
||||
wglDeleteContext(temp_rc);
|
||||
dyn_libs::wglMakeCurrent(hdc, nullptr);
|
||||
dyn_libs::wglDeleteContext(temp_rc);
|
||||
}
|
||||
});
|
||||
|
||||
if (!GLAD_WGL_ARB_pbuffer)
|
||||
{
|
||||
// we're probably running completely surfaceless... need a temporary context.
|
||||
temp_rc = wglCreateContext(hdc);
|
||||
if (!temp_rc || !wglMakeCurrent(hdc, temp_rc))
|
||||
temp_rc = dyn_libs::wglCreateContext(hdc);
|
||||
if (!temp_rc || !dyn_libs::wglMakeCurrent(hdc, temp_rc))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create temporary context to load WGL for pbuffer.");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer)
|
||||
if (!ReloadWGL(hdc, error))
|
||||
return NULL;
|
||||
|
||||
if (!GLAD_WGL_ARB_pbuffer)
|
||||
{
|
||||
Error::SetStringView(error, "Missing WGL_ARB_pbuffer");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,32 +415,33 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error)
|
||||
if (!pbuffer)
|
||||
{
|
||||
Error::SetStringView(error, "(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); });
|
||||
|
||||
m_dc = wglGetPbufferDCARB(pbuffer);
|
||||
if (!m_dc)
|
||||
HDC dc = wglGetPbufferDCARB(pbuffer);
|
||||
if (!dc)
|
||||
{
|
||||
Error::SetStringView(error, "(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed");
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
m_dummy_window = hwnd;
|
||||
m_dummy_dc = hdc;
|
||||
m_pbuffer = pbuffer;
|
||||
m_pbuffer_dc = dc;
|
||||
|
||||
temp_rc_guard.Run();
|
||||
pbuffer_guard.Cancel();
|
||||
hdc_guard.Cancel();
|
||||
hwnd_guard.Cancel();
|
||||
return true;
|
||||
return dc;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, Error* error)
|
||||
bool OpenGLContextWGL::CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error)
|
||||
{
|
||||
m_rc = wglCreateContext(m_dc);
|
||||
m_rc = dyn_libs::wglCreateContext(hdc);
|
||||
if (!m_rc)
|
||||
{
|
||||
Error::SetWin32(error, "wglCreateContext() failed: ", GetLastError());
|
||||
@ -402,21 +450,23 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current,
|
||||
|
||||
if (make_current)
|
||||
{
|
||||
if (!wglMakeCurrent(m_dc, m_rc))
|
||||
if (!dyn_libs::wglMakeCurrent(hdc, m_rc))
|
||||
{
|
||||
Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_dc = hdc;
|
||||
|
||||
// re-init glad-wgl
|
||||
if (!gladLoadWGL(m_dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
|
||||
if (!ReloadWGL(m_current_dc, error))
|
||||
{
|
||||
Error::SetStringView(error, "Loading GLAD WGL functions failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (share_context && !wglShareLists(share_context, m_rc))
|
||||
if (share_context && !dyn_libs::wglShareLists(share_context, m_rc))
|
||||
{
|
||||
Error::SetWin32(error, "wglShareLists() failed: ", GetLastError());
|
||||
return false;
|
||||
@ -425,7 +475,7 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current,
|
||||
bool OpenGLContextWGL::CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current,
|
||||
Error* error)
|
||||
{
|
||||
// we need create context attribs
|
||||
@ -454,7 +504,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
|
||||
0,
|
||||
0};
|
||||
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
|
||||
new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs);
|
||||
}
|
||||
else if (version.profile == Profile::ES)
|
||||
{
|
||||
@ -475,7 +525,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
|
||||
0,
|
||||
0};
|
||||
|
||||
new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs);
|
||||
new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -489,18 +539,20 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_
|
||||
// destroy and swap contexts
|
||||
if (m_rc)
|
||||
{
|
||||
if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr))
|
||||
if (!dyn_libs::wglMakeCurrent(hdc, make_current ? new_rc : nullptr))
|
||||
{
|
||||
Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError());
|
||||
wglDeleteContext(new_rc);
|
||||
dyn_libs::wglDeleteContext(new_rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_current_dc = hdc;
|
||||
|
||||
// re-init glad-wgl
|
||||
if (make_current && !ReloadWGL(m_dc))
|
||||
if (make_current && !ReloadWGL(hdc, error))
|
||||
return false;
|
||||
|
||||
wglDeleteContext(m_rc);
|
||||
dyn_libs::wglDeleteContext(m_rc);
|
||||
}
|
||||
|
||||
m_rc = new_rc;
|
||||
|
@ -15,36 +15,34 @@
|
||||
class OpenGLContextWGL final : public OpenGLContext
|
||||
{
|
||||
public:
|
||||
OpenGLContextWGL(const WindowInfo& wi);
|
||||
OpenGLContextWGL();
|
||||
~OpenGLContextWGL() override;
|
||||
|
||||
static std::unique_ptr<OpenGLContext> Create(const WindowInfo& wi, std::span<const Version> versions_to_try,
|
||||
Error* error);
|
||||
static std::unique_ptr<OpenGLContext> Create(WindowInfo& wi, SurfaceHandle* surface,
|
||||
std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
void* GetProcAddress(const char* name) override;
|
||||
bool ChangeSurface(const WindowInfo& new_wi) override;
|
||||
void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override;
|
||||
SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override;
|
||||
void DestroySurface(SurfaceHandle handle) override;
|
||||
void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override;
|
||||
bool SwapBuffers() override;
|
||||
bool IsCurrent() const override;
|
||||
bool MakeCurrent() override;
|
||||
bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override;
|
||||
bool DoneCurrent() override;
|
||||
bool SupportsNegativeSwapInterval() const override;
|
||||
bool SetSwapInterval(s32 interval) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(const WindowInfo& wi, Error* error) override;
|
||||
bool SetSwapInterval(s32 interval, Error* error = nullptr) override;
|
||||
std::unique_ptr<OpenGLContext> CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override;
|
||||
|
||||
private:
|
||||
ALWAYS_INLINE HWND GetHWND() const { return static_cast<HWND>(m_wi.window_handle); }
|
||||
bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span<const Version> versions_to_try, Error* error);
|
||||
|
||||
HDC GetDCAndSetPixelFormat(HWND hwnd, Error* error);
|
||||
bool CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error);
|
||||
bool CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current, Error* error);
|
||||
|
||||
bool Initialize(std::span<const Version> versions_to_try, Error* error);
|
||||
bool InitializeDC(Error* error);
|
||||
void ReleaseDC();
|
||||
bool CreatePBuffer(Error* error);
|
||||
bool CreateAnyContext(HGLRC share_context, bool make_current, Error* error);
|
||||
bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current, Error* error);
|
||||
HDC CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error);
|
||||
HDC GetPBufferDC(Error* error);
|
||||
|
||||
HDC m_dc = {};
|
||||
HDC m_current_dc = {};
|
||||
HGLRC m_rc = {};
|
||||
|
||||
// Can't change pixel format once it's set for a RC.
|
||||
@ -54,4 +52,5 @@ private:
|
||||
HWND m_dummy_window = {};
|
||||
HDC m_dummy_dc = {};
|
||||
HPBUFFERARB m_pbuffer = {};
|
||||
HDC m_pbuffer_dc = {};
|
||||
};
|
||||
|
@ -19,13 +19,16 @@
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
LOG_CHANNEL(OpenGLDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
static constexpr const std::array<GLenum, GPUDevice::MAX_RENDER_TARGETS> s_draw_buffers = {
|
||||
{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}};
|
||||
|
||||
OpenGLDevice::OpenGLDevice()
|
||||
{
|
||||
// Could change to GLES later.
|
||||
m_render_api = RenderAPI::OpenGL;
|
||||
|
||||
// Something which won't be matched..
|
||||
std::memset(&m_last_rasterization_state, 0xFF, sizeof(m_last_rasterization_state));
|
||||
std::memset(&m_last_depth_state, 0xFF, sizeof(m_last_depth_state));
|
||||
@ -238,19 +241,6 @@ void OpenGLDevice::InsertDebugMessage(const char* msg)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
|
||||
{
|
||||
// OpenGL does not support Mailbox.
|
||||
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
|
||||
m_vsync_mode = mode;
|
||||
SetSwapInterval();
|
||||
}
|
||||
|
||||
static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
|
||||
const GLchar* message, const void* userParam)
|
||||
{
|
||||
@ -271,15 +261,15 @@ static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id,
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLDevice::HasSurface() const
|
||||
bool OpenGLDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
return m_window_info.type != WindowInfo::Type::Surfaceless;
|
||||
}
|
||||
|
||||
bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error)
|
||||
{
|
||||
m_gl_context = OpenGLContext::Create(m_window_info, error);
|
||||
WindowInfo wi_copy(wi);
|
||||
OpenGLContext::SurfaceHandle wi_surface;
|
||||
m_gl_context = OpenGLContext::Create(wi_copy, &wi_surface, error);
|
||||
if (!m_gl_context)
|
||||
{
|
||||
ERROR_LOG("Failed to create any GL context");
|
||||
@ -287,9 +277,11 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Is this needed?
|
||||
m_window_info = m_gl_context->GetWindowInfo();
|
||||
m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode;
|
||||
m_vsync_mode = ;
|
||||
#endif
|
||||
|
||||
const bool opengl_is_available =
|
||||
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
|
||||
@ -303,10 +295,6 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
return false;
|
||||
}
|
||||
|
||||
SetSwapInterval();
|
||||
if (HasSurface())
|
||||
RenderBlankFrame();
|
||||
|
||||
if (m_debug_device && GLAD_GL_KHR_debug)
|
||||
{
|
||||
if (m_gl_context->IsGLES())
|
||||
@ -326,6 +314,21 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
glObjectLabel = nullptr;
|
||||
}
|
||||
|
||||
// create main swap chain
|
||||
if (!wi_copy.IsSurfaceless())
|
||||
{
|
||||
// OpenGL does not support mailbox.
|
||||
m_main_swap_chain = std::make_unique<OpenGLSwapChain>(
|
||||
wi_copy, (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode, allow_present_throttle,
|
||||
wi_surface);
|
||||
|
||||
Error swap_interval_error;
|
||||
if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), m_main_swap_chain->GetVSyncMode(), &swap_interval_error))
|
||||
WARNING_LOG("Failed to set swap interval on main swap chain: {}", swap_interval_error.GetDescription());
|
||||
|
||||
RenderBlankFrame();
|
||||
}
|
||||
|
||||
if (!CheckFeatures(disabled_features))
|
||||
return false;
|
||||
|
||||
@ -532,50 +535,104 @@ void OpenGLDevice::DestroyDevice()
|
||||
DestroyBuffers();
|
||||
|
||||
m_gl_context->DoneCurrent();
|
||||
m_main_swap_chain.reset();
|
||||
m_gl_context.reset();
|
||||
}
|
||||
|
||||
bool OpenGLDevice::UpdateWindow()
|
||||
OpenGLSwapChain::OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
OpenGLContext::SurfaceHandle surface_handle)
|
||||
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_surface_handle(surface_handle)
|
||||
{
|
||||
Assert(m_gl_context);
|
||||
}
|
||||
|
||||
DestroySurface();
|
||||
OpenGLSwapChain::~OpenGLSwapChain()
|
||||
{
|
||||
OpenGLDevice::GetContext()->DestroySurface(m_surface_handle);
|
||||
}
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
bool OpenGLSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
|
||||
{
|
||||
m_window_info.surface_scale = new_scale;
|
||||
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
|
||||
return true;
|
||||
|
||||
if (!m_gl_context->ChangeSurface(m_window_info))
|
||||
{
|
||||
ERROR_LOG("Failed to change surface");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_window_info = m_gl_context->GetWindowInfo();
|
||||
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
// reset vsync rate, since it (usually) gets lost
|
||||
SetSwapInterval();
|
||||
RenderBlankFrame();
|
||||
}
|
||||
m_window_info.surface_width = new_width;
|
||||
m_window_info.surface_height = new_height;
|
||||
|
||||
OpenGLDevice::GetContext()->ResizeSurface(m_window_info, m_surface_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
bool OpenGLSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
|
||||
{
|
||||
if (m_window_info.IsSurfaceless())
|
||||
return;
|
||||
// OpenGL does not support Mailbox.
|
||||
mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode;
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
if (m_window_info.surface_width == static_cast<u32>(new_window_width) &&
|
||||
m_window_info.surface_height == static_cast<u32>(new_window_height))
|
||||
if (m_vsync_mode == mode)
|
||||
return true;
|
||||
|
||||
const bool is_main_swap_chain = (g_gpu_device->GetMainSwapChain() == this);
|
||||
|
||||
OpenGLContext* ctx = OpenGLDevice::GetContext();
|
||||
if (!is_main_swap_chain && !ctx->MakeCurrent(m_surface_handle))
|
||||
return false;
|
||||
|
||||
const bool result = SetSwapInterval(ctx, mode, error);
|
||||
|
||||
if (!is_main_swap_chain)
|
||||
ctx->MakeCurrent(static_cast<OpenGLSwapChain*>(g_gpu_device->GetMainSwapChain())->m_surface_handle);
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
m_vsync_mode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLSwapChain::SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error)
|
||||
{
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
const s32 interval = static_cast<s32>(mode == GPUVSyncMode::FIFO);
|
||||
GLint current_fbo = 0;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
const bool result = ctx->SetSwapInterval(interval);
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<GPUSwapChain> OpenGLDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error)
|
||||
{
|
||||
if (wi.IsSurfaceless())
|
||||
{
|
||||
return;
|
||||
Error::SetStringView(error, "Trying to create a surfaceless swap chain.");
|
||||
return {};
|
||||
}
|
||||
|
||||
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(new_window_height));
|
||||
m_window_info = m_gl_context->GetWindowInfo();
|
||||
WindowInfo wi_copy(wi);
|
||||
const OpenGLContext::SurfaceHandle surface_handle = m_gl_context->CreateSurface(wi_copy, error);
|
||||
if (!surface_handle || !m_gl_context->MakeCurrent(surface_handle, error))
|
||||
return {};
|
||||
|
||||
Error swap_interval_error;
|
||||
if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), vsync_mode, &swap_interval_error))
|
||||
WARNING_LOG("Failed to set swap interval on new swap chain: {}", swap_interval_error.GetDescription());
|
||||
|
||||
RenderBlankFrame();
|
||||
|
||||
// only bother switching back if we actually have a main swap chain, avoids a couple of
|
||||
// SetCurrent() calls when we're switching to and from fullscreen.
|
||||
if (m_main_swap_chain)
|
||||
m_gl_context->MakeCurrent(static_cast<OpenGLSwapChain*>(m_main_swap_chain.get())->GetSurfaceHandle());
|
||||
|
||||
return std::make_unique<OpenGLSwapChain>(wi_copy, vsync_mode, allow_present_throttle, surface_handle);
|
||||
}
|
||||
|
||||
std::string OpenGLDevice::GetDriverInfo() const
|
||||
@ -588,29 +645,18 @@ std::string OpenGLDevice::GetDriverInfo() const
|
||||
gl_shading_language_version);
|
||||
}
|
||||
|
||||
void OpenGLDevice::ExecuteAndWaitForGPUIdle()
|
||||
void OpenGLDevice::FlushCommands()
|
||||
{
|
||||
glFlush();
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void OpenGLDevice::WaitForGPUIdle()
|
||||
{
|
||||
// Could be glFinish(), but I'm afraid for mobile drivers...
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void OpenGLDevice::SetSwapInterval()
|
||||
{
|
||||
if (m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||
return;
|
||||
|
||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||
const s32 interval = static_cast<s32>(m_vsync_mode == GPUVSyncMode::FIFO);
|
||||
GLint current_fbo = 0;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
|
||||
if (!m_gl_context->SetSwapInterval(interval))
|
||||
WARNING_LOG("Failed to set swap interval to {}", interval);
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||
}
|
||||
|
||||
void OpenGLDevice::RenderBlankFrame()
|
||||
{
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
@ -675,16 +721,6 @@ void OpenGLDevice::DestroyFramebuffer(GLuint fbo)
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
}
|
||||
|
||||
void OpenGLDevice::DestroySurface()
|
||||
{
|
||||
if (!m_gl_context)
|
||||
return;
|
||||
|
||||
m_window_info.SetSurfaceless();
|
||||
if (!m_gl_context->ChangeSurface(m_window_info))
|
||||
ERROR_LOG("Failed to switch to surfaceless");
|
||||
}
|
||||
|
||||
bool OpenGLDevice::CreateBuffers()
|
||||
{
|
||||
if (!(m_vertex_buffer = OpenGLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE)) ||
|
||||
@ -742,14 +778,9 @@ void OpenGLDevice::DestroyBuffers()
|
||||
m_vertex_buffer.reset();
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
|
||||
GPUDevice::PresentResult OpenGLDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
if (m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||
{
|
||||
glFlush();
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
m_gl_context->MakeCurrent(static_cast<OpenGLSwapChain*>(swap_chain)->GetSurfaceHandle());
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
@ -764,7 +795,8 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
|
||||
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
|
||||
m_current_depth_target = nullptr;
|
||||
|
||||
const GSVector4i window_rc = GSVector4i(0, 0, m_window_info.surface_width, m_window_info.surface_height);
|
||||
const GSVector4i window_rc =
|
||||
GSVector4i(0, 0, static_cast<s32>(swap_chain->GetWidth()), static_cast<s32>(swap_chain->GetHeight()));
|
||||
m_last_viewport = window_rc;
|
||||
m_last_scissor = window_rc;
|
||||
UpdateViewport();
|
||||
@ -772,23 +804,23 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color)
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void OpenGLDevice::EndPresent(bool explicit_present, u64 present_time)
|
||||
void OpenGLDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
|
||||
{
|
||||
DebugAssert(!explicit_present && present_time == 0);
|
||||
DebugAssert(m_current_fbo == 0);
|
||||
|
||||
if (m_gpu_timing_enabled)
|
||||
if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
m_gl_context->SwapBuffers();
|
||||
|
||||
if (m_gpu_timing_enabled)
|
||||
if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled)
|
||||
KickTimestampQuery();
|
||||
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void OpenGLDevice::SubmitPresent()
|
||||
void OpenGLDevice::SubmitPresent(GPUSwapChain* swap_chain)
|
||||
{
|
||||
Panic("Not supported by this API.");
|
||||
}
|
||||
|
@ -36,20 +36,21 @@ public:
|
||||
return GetInstance().m_texture_stream_buffer.get();
|
||||
}
|
||||
ALWAYS_INLINE static bool IsGLES() { return GetInstance().m_gl_context->IsGLES(); }
|
||||
ALWAYS_INLINE static OpenGLContext* GetContext() { return GetInstance().m_gl_context.get(); }
|
||||
static void BindUpdateTextureUnit();
|
||||
static bool ShouldUsePBOsForDownloads();
|
||||
static void SetErrorObject(Error* errptr, std::string_view prefix, GLenum glerr);
|
||||
|
||||
bool HasSurface() const override;
|
||||
void DestroySurface() override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ExecuteAndWaitForGPUIdle() override;
|
||||
void FlushCommands() override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) override;
|
||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data = nullptr, u32 data_stride = 0) override;
|
||||
@ -100,11 +101,9 @@ public:
|
||||
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
|
||||
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
|
||||
|
||||
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
|
||||
|
||||
PresentResult BeginPresent(u32 clear_color) override;
|
||||
void EndPresent(bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent() override;
|
||||
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
|
||||
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent(GPUSwapChain* swap_chain) override;
|
||||
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
@ -131,9 +130,13 @@ public:
|
||||
void UnbindSampler(GLuint id);
|
||||
void UnbindPipeline(const OpenGLPipeline* pl);
|
||||
|
||||
void RenderBlankFrame();
|
||||
|
||||
protected:
|
||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) override;
|
||||
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
|
||||
void DestroyDevice() override;
|
||||
|
||||
bool OpenPipelineCache(const std::string& path, Error* error) override;
|
||||
@ -154,9 +157,6 @@ private:
|
||||
bool CreateBuffers();
|
||||
void DestroyBuffers();
|
||||
|
||||
void SetSwapInterval();
|
||||
void RenderBlankFrame();
|
||||
|
||||
s32 IsRenderTargetBound(const GPUTexture* tex) const;
|
||||
static GLuint CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags);
|
||||
static void DestroyFramebuffer(GLuint fbo);
|
||||
@ -230,3 +230,21 @@ private:
|
||||
bool m_disable_pbo = false;
|
||||
bool m_disable_async_download = false;
|
||||
};
|
||||
|
||||
class OpenGLSwapChain : public GPUSwapChain
|
||||
{
|
||||
public:
|
||||
OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
OpenGLContext::SurfaceHandle surface_handle);
|
||||
~OpenGLSwapChain() override;
|
||||
|
||||
ALWAYS_INLINE OpenGLContext::SurfaceHandle GetSurfaceHandle() const { return m_surface_handle; }
|
||||
|
||||
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
|
||||
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
|
||||
|
||||
static bool SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error);
|
||||
|
||||
private:
|
||||
OpenGLContext::SurfaceHandle m_surface_handle;
|
||||
};
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
LOG_CHANNEL(OpenGLDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
struct PipelineDiskCacheFooter
|
||||
{
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
LOG_CHANNEL(OpenGLDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
// Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems
|
||||
// to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here.
|
||||
@ -260,8 +260,7 @@ bool OpenGLTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data
|
||||
const GLenum target = GetGLTarget();
|
||||
const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_format, OpenGLDevice::IsGLES());
|
||||
const u32 pixel_size = GetPixelSize();
|
||||
const u32 preferred_pitch =
|
||||
Common::AlignUpPow2(static_cast<u32>(width) * pixel_size, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
const u32 preferred_pitch = Common::AlignUpPow2(static_cast<u32>(width) * pixel_size, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
|
||||
const u32 map_size = preferred_pitch * static_cast<u32>(height);
|
||||
OpenGLStreamBuffer* sb = OpenGLDevice::GetTextureStreamBuffer();
|
||||
|
||||
@ -551,7 +550,8 @@ void OpenGLDevice::CommitRTClearInFB(OpenGLTexture* tex, u32 idx)
|
||||
case GPUTexture::State::Invalidated:
|
||||
{
|
||||
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);
|
||||
}
|
||||
break;
|
||||
@ -589,7 +589,8 @@ void OpenGLDevice::CommitDSClearInFB(OpenGLTexture* tex)
|
||||
case GPUTexture::State::Invalidated:
|
||||
{
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "platform_misc.h"
|
||||
#include "window_info.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
#include "common/small_string.h"
|
||||
|
||||
@ -87,49 +88,45 @@ bool PlatformMisc::PlaySoundAsync(const char* path)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CocoaTools::CreateMetalLayer(WindowInfo* wi)
|
||||
void* CocoaTools::CreateMetalLayer(const WindowInfo& wi, Error* error)
|
||||
{
|
||||
// Punt off to main thread if we're not calling from it already.
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
bool ret;
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { ret = CreateMetalLayer(wi); });
|
||||
void* ret;
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, &wi, error]() { ret = CreateMetalLayer(wi, error); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
CAMetalLayer* layer = [CAMetalLayer layer];
|
||||
if (layer == nil)
|
||||
{
|
||||
ERROR_LOG("Failed to create CAMetalLayer");
|
||||
return false;
|
||||
Error::SetStringView(error, "Failed to create CAMetalLayer");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NSView* view = (__bridge NSView*)wi->window_handle;
|
||||
NSView* view = (__bridge NSView*)wi.window_handle;
|
||||
[view setWantsLayer:TRUE];
|
||||
[view setLayer:layer];
|
||||
[layer setContentsScale:[[[view window] screen] backingScaleFactor]];
|
||||
|
||||
wi->surface_handle = (__bridge void*)layer;
|
||||
return true;
|
||||
return (__bridge void*)layer;
|
||||
}
|
||||
|
||||
void CocoaTools::DestroyMetalLayer(WindowInfo* wi)
|
||||
void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer)
|
||||
{
|
||||
if (!wi->surface_handle)
|
||||
return;
|
||||
|
||||
// Punt off to main thread if we're not calling from it already.
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), [wi]() { DestroyMetalLayer(wi); });
|
||||
dispatch_sync(dispatch_get_main_queue(), [&wi, layer]() { DestroyMetalLayer(wi, layer); });
|
||||
return;
|
||||
}
|
||||
|
||||
NSView* view = (__bridge NSView*)wi->window_handle;
|
||||
CAMetalLayer* layer = (__bridge CAMetalLayer*)wi->surface_handle;
|
||||
NSView* view = (__bridge NSView*)wi.window_handle;
|
||||
CAMetalLayer* clayer = (__bridge CAMetalLayer*)layer;
|
||||
[view setLayer:nil];
|
||||
[view setWantsLayer:NO];
|
||||
[layer release];
|
||||
[clayer release];
|
||||
}
|
||||
|
||||
std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
|
||||
@ -137,7 +134,7 @@ std::optional<float> CocoaTools::GetViewRefreshRate(const WindowInfo& wi)
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
std::optional<float> ret;
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = GetViewRefreshRate(wi); });
|
||||
dispatch_sync(dispatch_get_main_queue(), [&ret, wi] { ret = GetViewRefreshRate(wi); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -437,10 +437,10 @@ void PostProcessing::Chain::LoadStages()
|
||||
DEV_LOG("Loaded {} post-processing stages.", stage_count);
|
||||
|
||||
// precompile shaders
|
||||
if (!IsInternalChain() && g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown)
|
||||
if (!IsInternalChain() && g_gpu_device && g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(),
|
||||
&progress);
|
||||
CheckTargets(g_gpu_device->GetMainSwapChain()->GetFormat(), g_gpu_device->GetMainSwapChain()->GetWidth(),
|
||||
g_gpu_device->GetMainSwapChain()->GetHeight(), &progress);
|
||||
}
|
||||
|
||||
// must be down here, because we need to compile first, triggered by CheckTargets()
|
||||
|
@ -333,9 +333,9 @@ bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::stri
|
||||
// TODO: This could use spv, it's probably fastest.
|
||||
const auto& [cg, cg_language] = CreateRFXCodegen();
|
||||
|
||||
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(),
|
||||
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), cg.get(), std::move(code),
|
||||
error))
|
||||
if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetMainSwapChain()->GetWidth(),
|
||||
only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetMainSwapChain()->GetHeight(), cg.get(),
|
||||
std::move(code), error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1762,7 +1762,8 @@ GPUDevice::PresentResult PostProcessing::ReShadeFXShader::Apply(GPUTexture* inpu
|
||||
if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target)
|
||||
{
|
||||
// Special case: drawing to final buffer.
|
||||
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
|
||||
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
|
||||
if (pres != GPUDevice::PresentResult::OK)
|
||||
{
|
||||
GL_POP();
|
||||
return pres;
|
||||
|
@ -177,7 +177,8 @@ GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_col
|
||||
// Assumes final stage has been cleared already.
|
||||
if (!final_target)
|
||||
{
|
||||
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK)
|
||||
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain());
|
||||
if (pres != GPUDevice::PresentResult::OK)
|
||||
return pres;
|
||||
}
|
||||
else
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>%(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>%(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1;ENABLE_SDL=1</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(Platform)'!='ARM64'">%(PreprocessorDefinitions);ENABLE_OPENGL=1</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\ffmpeg\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Platform)'!='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include</AdditionalIncludeDirectories>
|
||||
@ -14,7 +14,6 @@
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
// TODO: VK_KHR_display.
|
||||
|
||||
@ -110,6 +110,8 @@ static std::mutex s_instance_mutex;
|
||||
|
||||
VulkanDevice::VulkanDevice()
|
||||
{
|
||||
m_render_api = RenderAPI::Vulkan;
|
||||
|
||||
#ifdef _DEBUG
|
||||
s_debug_scope_depth = 0;
|
||||
#endif
|
||||
@ -637,14 +639,6 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
|
||||
enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
|
||||
device_info.pEnabledFeatures = &enabled_features;
|
||||
|
||||
// Enable debug layer on debug builds
|
||||
if (enable_validation_layer)
|
||||
{
|
||||
static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"};
|
||||
device_info.enabledLayerCount = 1;
|
||||
device_info.ppEnabledLayerNames = layer_names;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr, VK_TRUE, VK_FALSE,
|
||||
VK_FALSE};
|
||||
@ -1266,11 +1260,6 @@ void VulkanDevice::WaitForFenceCounter(u64 fence_counter)
|
||||
WaitForCommandBufferCompletion(index);
|
||||
}
|
||||
|
||||
void VulkanDevice::WaitForGPUIdle()
|
||||
{
|
||||
vkDeviceWaitIdle(m_device);
|
||||
}
|
||||
|
||||
float VulkanDevice::GetAndResetAccumulatedGPUTime()
|
||||
{
|
||||
const float time = m_accumulated_gpu_time;
|
||||
@ -1445,9 +1434,15 @@ void VulkanDevice::QueuePresent(VulkanSwapChain* present_swap_chain)
|
||||
{
|
||||
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
|
||||
if (res == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
ResizeWindow(0, 0, m_window_info.surface_scale);
|
||||
{
|
||||
Error error;
|
||||
if (!present_swap_chain->ResizeBuffers(0, 0, present_swap_chain->GetScale(), &error)) [[unlikely]]
|
||||
WARNING_LOG("Failed to reszie swap chain: {}", error.GetDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -1889,13 +1884,11 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VulkanDevice::HasSurface() const
|
||||
{
|
||||
return static_cast<bool>(m_swap_chain);
|
||||
}
|
||||
|
||||
bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error)
|
||||
bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features,
|
||||
const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error)
|
||||
{
|
||||
std::unique_lock lock(s_instance_mutex);
|
||||
bool enable_debug_utils = m_debug_device;
|
||||
@ -1908,7 +1901,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
return false;
|
||||
}
|
||||
|
||||
m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
||||
m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
||||
if (m_instance == VK_NULL_HANDLE)
|
||||
{
|
||||
if (enable_debug_utils || enable_validation_layer)
|
||||
@ -1916,8 +1909,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
// Try again without the validation layer.
|
||||
enable_debug_utils = false;
|
||||
enable_validation_layer = false;
|
||||
m_instance =
|
||||
CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
||||
m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer);
|
||||
if (m_instance == VK_NULL_HANDLE)
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?");
|
||||
@ -1983,21 +1975,21 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
if (enable_debug_utils)
|
||||
EnableDebugUtils();
|
||||
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
ScopedGuard surface_cleanup = [this, &surface]() {
|
||||
if (surface != VK_NULL_HANDLE)
|
||||
vkDestroySurfaceKHR(m_instance, surface, nullptr);
|
||||
};
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||
std::unique_ptr<VulkanSwapChain> swap_chain;
|
||||
if (!wi.IsSurfaceless())
|
||||
{
|
||||
surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info);
|
||||
if (surface == VK_NULL_HANDLE)
|
||||
swap_chain =
|
||||
std::make_unique<VulkanSwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control);
|
||||
if (!swap_chain->CreateSurface(m_instance, m_physical_device, error))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to create the device.
|
||||
if (!CreateDevice(surface, enable_validation_layer, disabled_features, error))
|
||||
if (!CreateDevice(swap_chain ? swap_chain->GetSurface() : VK_NULL_HANDLE, enable_validation_layer, disabled_features,
|
||||
error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// And critical resources.
|
||||
if (!CreateAllocator() || !CreatePersistentDescriptorPool() || !CreateCommandBuffers() || !CreatePipelineLayouts())
|
||||
@ -2005,26 +1997,16 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional<bool> ex
|
||||
|
||||
m_exclusive_fullscreen_control = exclusive_fullscreen_control;
|
||||
|
||||
if (surface != VK_NULL_HANDLE)
|
||||
if (swap_chain)
|
||||
{
|
||||
VkPresentModeKHR present_mode;
|
||||
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
|
||||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create swap chain");
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
if (!swap_chain->CreateSwapChain(*this, error) || !swap_chain->CreateSwapChainImages(*this, error))
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal).
|
||||
m_window_info = m_swap_chain->GetWindowInfo();
|
||||
RenderBlankFrame(swap_chain.get());
|
||||
m_main_swap_chain = std::move(swap_chain);
|
||||
}
|
||||
|
||||
surface_cleanup.Cancel();
|
||||
|
||||
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||
RenderBlankFrame();
|
||||
|
||||
if (!CreateNullTexture())
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create dummy texture");
|
||||
@ -2049,9 +2031,14 @@ void VulkanDevice::DestroyDevice()
|
||||
|
||||
// Don't both submitting the current command buffer, just toss it.
|
||||
if (m_device != VK_NULL_HANDLE)
|
||||
WaitForGPUIdle();
|
||||
vkDeviceWaitIdle(m_device);
|
||||
|
||||
m_swap_chain.reset();
|
||||
if (m_main_swap_chain)
|
||||
{
|
||||
// Explicit swap chain destroy, we don't want to execute the current cmdbuffer.
|
||||
static_cast<VulkanSwapChain*>(m_main_swap_chain.get())->Destroy(*this, false);
|
||||
m_main_swap_chain.reset();
|
||||
}
|
||||
|
||||
if (m_null_texture)
|
||||
{
|
||||
@ -2208,73 +2195,27 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanDevice::UpdateWindow()
|
||||
std::unique_ptr<GPUSwapChain> VulkanDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error)
|
||||
{
|
||||
DestroySurface();
|
||||
|
||||
if (!AcquireWindow(false))
|
||||
return false;
|
||||
|
||||
if (m_window_info.IsSurfaceless())
|
||||
return true;
|
||||
|
||||
// make sure previous frames are presented
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
SubmitCommandBuffer(false);
|
||||
WaitForGPUIdle();
|
||||
|
||||
VkSurfaceKHR surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info);
|
||||
if (surface == VK_NULL_HANDLE)
|
||||
std::unique_ptr<VulkanSwapChain> swap_chain =
|
||||
std::make_unique<VulkanSwapChain>(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control);
|
||||
if (swap_chain->CreateSurface(m_instance, m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) &&
|
||||
swap_chain->CreateSwapChainImages(*this, error))
|
||||
{
|
||||
ERROR_LOG("Failed to create new surface for swap chain");
|
||||
return false;
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
RenderBlankFrame(swap_chain.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
swap_chain.reset();
|
||||
}
|
||||
|
||||
VkPresentModeKHR present_mode;
|
||||
if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) ||
|
||||
!(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control)))
|
||||
{
|
||||
ERROR_LOG("Failed to create swap chain");
|
||||
VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_window_info = m_swap_chain->GetWindowInfo();
|
||||
RenderBlankFrame();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||
{
|
||||
if (!m_swap_chain)
|
||||
return;
|
||||
|
||||
if (m_swap_chain->GetWidth() == static_cast<u32>(new_window_width) &&
|
||||
m_swap_chain->GetHeight() == static_cast<u32>(new_window_height))
|
||||
{
|
||||
// skip unnecessary resizes
|
||||
m_window_info.surface_scale = new_window_scale;
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure previous frames are presented
|
||||
WaitForGPUIdle();
|
||||
|
||||
if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale))
|
||||
{
|
||||
// AcquireNextImage() will fail, and we'll recreate the surface.
|
||||
ERROR_LOG("Failed to resize swap chain. Next present will fail.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_window_info = m_swap_chain->GetWindowInfo();
|
||||
}
|
||||
|
||||
void VulkanDevice::DestroySurface()
|
||||
{
|
||||
WaitForGPUIdle();
|
||||
m_swap_chain.reset();
|
||||
return swap_chain;
|
||||
}
|
||||
|
||||
bool VulkanDevice::SupportsTextureFormat(GPUTexture::Format format) const
|
||||
@ -2308,7 +2249,16 @@ std::string VulkanDevice::GetDriverInfo() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VulkanDevice::ExecuteAndWaitForGPUIdle()
|
||||
void VulkanDevice::FlushCommands()
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
SubmitCommandBuffer(false);
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void VulkanDevice::WaitForGPUIdle()
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
@ -2316,39 +2266,7 @@ void VulkanDevice::ExecuteAndWaitForGPUIdle()
|
||||
SubmitCommandBuffer(true);
|
||||
}
|
||||
|
||||
void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
|
||||
{
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
if (!m_swap_chain)
|
||||
{
|
||||
// For when it is re-created.
|
||||
m_vsync_mode = mode;
|
||||
return;
|
||||
}
|
||||
|
||||
VkPresentModeKHR present_mode;
|
||||
if (!VulkanSwapChain::SelectPresentMode(m_swap_chain->GetSurface(), &mode, &present_mode))
|
||||
{
|
||||
ERROR_LOG("Ignoring vsync mode change.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually changed? If using a fallback, it might not have.
|
||||
if (m_vsync_mode == mode)
|
||||
return;
|
||||
|
||||
m_vsync_mode = mode;
|
||||
|
||||
// This swap chain should not be used by the current buffer, thus safe to destroy.
|
||||
WaitForGPUIdle();
|
||||
if (!m_swap_chain->SetPresentMode(present_mode))
|
||||
{
|
||||
Panic("Failed to update swap chain present mode.");
|
||||
m_swap_chain.reset();
|
||||
}
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
|
||||
GPUDevice::PresentResult VulkanDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
@ -2356,37 +2274,30 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
|
||||
if (m_device_was_lost) [[unlikely]]
|
||||
return PresentResult::DeviceLost;
|
||||
|
||||
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
|
||||
if (!m_swap_chain)
|
||||
{
|
||||
SubmitCommandBuffer(false);
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
|
||||
VkResult res = m_swap_chain->AcquireNextImage();
|
||||
VulkanSwapChain* const SC = static_cast<VulkanSwapChain*>(swap_chain);
|
||||
VkResult res = SC->AcquireNextImage();
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
|
||||
m_swap_chain->ReleaseCurrentImage();
|
||||
SC->ReleaseCurrentImage();
|
||||
|
||||
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
ResizeWindow(0, 0, m_window_info.surface_scale);
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
Error error;
|
||||
if (!SC->ResizeBuffers(0, 0, SC->GetScale(), &error)) [[unlikely]]
|
||||
WARNING_LOG("Failed to resize buffers: {}", error.GetDescription());
|
||||
else
|
||||
res = SC->AcquireNextImage();
|
||||
}
|
||||
else if (res == VK_ERROR_SURFACE_LOST_KHR)
|
||||
{
|
||||
WARNING_LOG("Surface lost, attempting to recreate");
|
||||
if (!m_swap_chain->RecreateSurface(m_window_info))
|
||||
{
|
||||
ERROR_LOG("Failed to recreate surface after loss");
|
||||
SubmitCommandBuffer(false);
|
||||
TrimTexturePool();
|
||||
return PresentResult::SkipPresent;
|
||||
}
|
||||
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
Error error;
|
||||
if (!SC->RecreateSurface(&error))
|
||||
ERROR_LOG("Failed to recreate surface after loss: {}", error.GetDescription());
|
||||
else
|
||||
res = SC->AcquireNextImage();
|
||||
}
|
||||
|
||||
// This can happen when multiple resize events happen in quick succession.
|
||||
@ -2400,33 +2311,38 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color)
|
||||
}
|
||||
}
|
||||
|
||||
BeginSwapChainRenderPass(clear_color);
|
||||
BeginSwapChainRenderPass(SC, clear_color);
|
||||
return PresentResult::OK;
|
||||
}
|
||||
|
||||
void VulkanDevice::EndPresent(bool explicit_present, u64 present_time)
|
||||
void VulkanDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time)
|
||||
{
|
||||
VulkanSwapChain* const SC = static_cast<VulkanSwapChain*>(swap_chain);
|
||||
|
||||
DebugAssert(present_time == 0);
|
||||
DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target);
|
||||
EndRenderPass();
|
||||
|
||||
DebugAssert(SC == m_current_swap_chain);
|
||||
m_current_swap_chain = nullptr;
|
||||
|
||||
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
|
||||
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, m_swap_chain->GetCurrentImage(), GPUTexture::Type::RenderTarget,
|
||||
0, 1, 0, 1, VulkanTexture::Layout::ColorAttachment,
|
||||
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, SC->GetCurrentImage(), GPUTexture::Type::RenderTarget, 0, 1, 0,
|
||||
1, VulkanTexture::Layout::ColorAttachment,
|
||||
VulkanTexture::Layout::PresentSrc);
|
||||
EndAndSubmitCommandBuffer(m_swap_chain.get(), explicit_present);
|
||||
EndAndSubmitCommandBuffer(SC, explicit_present);
|
||||
MoveToNextCommandBuffer();
|
||||
InvalidateCachedState();
|
||||
TrimTexturePool();
|
||||
}
|
||||
|
||||
void VulkanDevice::SubmitPresent()
|
||||
void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain)
|
||||
{
|
||||
DebugAssert(m_swap_chain);
|
||||
DebugAssert(swap_chain);
|
||||
if (m_device_was_lost) [[unlikely]]
|
||||
return;
|
||||
|
||||
QueuePresent(m_swap_chain.get());
|
||||
QueuePresent(static_cast<VulkanSwapChain*>(swap_chain));
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
@ -2515,7 +2431,6 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP
|
||||
void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features)
|
||||
{
|
||||
const u32 store_api_version = std::min(m_device_properties.apiVersion, VK_API_VERSION_1_1);
|
||||
m_render_api = RenderAPI::Vulkan;
|
||||
m_render_api_version = (VK_API_VERSION_MAJOR(store_api_version) * 100u) +
|
||||
(VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version));
|
||||
m_max_texture_size =
|
||||
@ -3076,9 +2991,9 @@ void VulkanDevice::DestroyPersistentDescriptorSets()
|
||||
FreePersistentDescriptorSet(m_ubo_descriptor_set);
|
||||
}
|
||||
|
||||
void VulkanDevice::RenderBlankFrame()
|
||||
void VulkanDevice::RenderBlankFrame(VulkanSwapChain* swap_chain)
|
||||
{
|
||||
VkResult res = m_swap_chain->AcquireNextImage();
|
||||
VkResult res = swap_chain->AcquireNextImage();
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
ERROR_LOG("Failed to acquire image for blank frame present");
|
||||
@ -3087,7 +3002,7 @@ void VulkanDevice::RenderBlankFrame()
|
||||
|
||||
VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
|
||||
|
||||
const VkImage image = m_swap_chain->GetCurrentImage();
|
||||
const VkImage image = swap_chain->GetCurrentImage();
|
||||
static constexpr VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
static constexpr VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
||||
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
|
||||
@ -3096,7 +3011,7 @@ void VulkanDevice::RenderBlankFrame()
|
||||
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
|
||||
VulkanTexture::Layout::TransferDst, VulkanTexture::Layout::PresentSrc);
|
||||
|
||||
EndAndSubmitCommandBuffer(m_swap_chain.get(), false);
|
||||
EndAndSubmitCommandBuffer(swap_chain, false);
|
||||
MoveToNextCommandBuffer();
|
||||
|
||||
InvalidateCachedState();
|
||||
@ -3363,7 +3278,7 @@ void VulkanDevice::BeginRenderPass()
|
||||
VkRenderingAttachmentInfo& ai = attachments[0];
|
||||
ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
|
||||
ai.pNext = nullptr;
|
||||
ai.imageView = m_swap_chain->GetCurrentImageView();
|
||||
ai.imageView = m_current_swap_chain->GetCurrentImageView();
|
||||
ai.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR;
|
||||
ai.resolveImageView = VK_NULL_HANDLE;
|
||||
@ -3373,7 +3288,7 @@ void VulkanDevice::BeginRenderPass()
|
||||
|
||||
ri.colorAttachmentCount = 1;
|
||||
ri.pColorAttachments = attachments.data();
|
||||
ri.renderArea = {{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}};
|
||||
ri.renderArea = {{}, {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()}};
|
||||
}
|
||||
|
||||
m_current_render_pass = DYNAMIC_RENDERING_RENDER_PASS;
|
||||
@ -3434,10 +3349,10 @@ void VulkanDevice::BeginRenderPass()
|
||||
else
|
||||
{
|
||||
// Re-rendering to swap chain.
|
||||
bi.framebuffer = m_swap_chain->GetCurrentFramebuffer();
|
||||
bi.framebuffer = m_current_swap_chain->GetCurrentFramebuffer();
|
||||
bi.renderPass = m_current_render_pass =
|
||||
GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
bi.renderArea.extent = {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()};
|
||||
GetSwapChainRenderPass(m_current_swap_chain->GetFormat(), VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
bi.renderArea.extent = {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()};
|
||||
}
|
||||
|
||||
DebugAssert(m_current_render_pass);
|
||||
@ -3451,12 +3366,12 @@ void VulkanDevice::BeginRenderPass()
|
||||
SetInitialPipelineState();
|
||||
}
|
||||
|
||||
void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
|
||||
void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color)
|
||||
{
|
||||
DebugAssert(!InRenderPass());
|
||||
|
||||
const VkCommandBuffer cmdbuf = GetCurrentCommandBuffer();
|
||||
const VkImage swap_chain_image = m_swap_chain->GetCurrentImage();
|
||||
const VkImage swap_chain_image = swap_chain->GetCurrentImage();
|
||||
|
||||
// Swap chain images start in undefined
|
||||
VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, swap_chain_image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1,
|
||||
@ -3477,7 +3392,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
|
||||
{
|
||||
VkRenderingAttachmentInfo ai = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR,
|
||||
nullptr,
|
||||
m_swap_chain->GetCurrentImageView(),
|
||||
swap_chain->GetCurrentImageView(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_RESOLVE_MODE_NONE_KHR,
|
||||
VK_NULL_HANDLE,
|
||||
@ -3489,7 +3404,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
|
||||
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
|
||||
nullptr,
|
||||
0u,
|
||||
{{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
|
||||
{{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
||||
1u,
|
||||
0u,
|
||||
1u,
|
||||
@ -3503,14 +3418,14 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
|
||||
else
|
||||
{
|
||||
m_current_render_pass =
|
||||
GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||
GetSwapChainRenderPass(swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||
DebugAssert(m_current_render_pass);
|
||||
|
||||
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
nullptr,
|
||||
m_current_render_pass,
|
||||
m_swap_chain->GetCurrentFramebuffer(),
|
||||
{{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}},
|
||||
swap_chain->GetCurrentFramebuffer(),
|
||||
{{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
||||
1u,
|
||||
&clear_value};
|
||||
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
||||
@ -3526,6 +3441,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color)
|
||||
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
|
||||
m_current_depth_target = nullptr;
|
||||
m_current_framebuffer = VK_NULL_HANDLE;
|
||||
m_current_swap_chain = swap_chain;
|
||||
}
|
||||
|
||||
bool VulkanDevice::InRenderPass()
|
||||
|
@ -75,16 +75,16 @@ public:
|
||||
static GPUList EnumerateGPUs();
|
||||
static AdapterInfoList GetAdapterList();
|
||||
|
||||
bool HasSurface() const override;
|
||||
|
||||
bool UpdateWindow() override;
|
||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||
void DestroySurface() override;
|
||||
|
||||
std::string GetDriverInfo() const override;
|
||||
|
||||
void ExecuteAndWaitForGPUIdle() override;
|
||||
void FlushCommands() override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
std::unique_ptr<GPUSwapChain> CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode,
|
||||
bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control,
|
||||
Error* error) override;
|
||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format,
|
||||
const void* data = nullptr, u32 data_stride = 0) override;
|
||||
@ -138,11 +138,9 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
|
||||
|
||||
PresentResult BeginPresent(u32 clear_color) override;
|
||||
void EndPresent(bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent() override;
|
||||
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
|
||||
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;
|
||||
void SubmitPresent(GPUSwapChain* swap_chain) override;
|
||||
|
||||
// Global state accessors
|
||||
ALWAYS_INLINE static VulkanDevice& GetInstance() { return *static_cast<VulkanDevice*>(g_gpu_device.get()); }
|
||||
@ -167,7 +165,7 @@ public:
|
||||
return static_cast<u32>(m_device_properties.limits.optimalBufferCopyRowPitchAlignment);
|
||||
}
|
||||
|
||||
void WaitForGPUIdle();
|
||||
void WaitForAllFences();
|
||||
|
||||
// Creates a simple render pass.
|
||||
VkRenderPass GetRenderPass(const GPUPipeline::GraphicsConfig& config);
|
||||
@ -219,19 +217,26 @@ public:
|
||||
// Also invokes callbacks for completion.
|
||||
void WaitForFenceCounter(u64 fence_counter);
|
||||
|
||||
// Ends a render pass if we're currently in one.
|
||||
// When Bind() is next called, the pass will be restarted.
|
||||
void BeginRenderPass();
|
||||
void EndRenderPass();
|
||||
bool InRenderPass();
|
||||
|
||||
/// Ends any render pass, executes the command buffer, and invalidates cached state.
|
||||
void SubmitCommandBuffer(bool wait_for_completion);
|
||||
void SubmitCommandBuffer(bool wait_for_completion, const std::string_view reason);
|
||||
void SubmitCommandBufferAndRestartRenderPass(const std::string_view reason);
|
||||
|
||||
void UnbindFramebuffer(VulkanTexture* tex);
|
||||
void UnbindPipeline(VulkanPipeline* pl);
|
||||
void UnbindTexture(VulkanTexture* tex);
|
||||
void UnbindTextureBuffer(VulkanTextureBuffer* buf);
|
||||
|
||||
protected:
|
||||
bool CreateDevice(std::string_view adapter, std::optional<bool> exclusive_fullscreen_control,
|
||||
FeatureMask disabled_features, Error* error) override;
|
||||
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
|
||||
GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
const ExclusiveFullscreenMode* exclusive_fullscreen_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control, Error* error) override;
|
||||
void DestroyDevice() override;
|
||||
|
||||
bool ReadPipelineCache(DynamicHeapArray<u8> data, Error* error) override;
|
||||
@ -351,7 +356,7 @@ private:
|
||||
VkSampler GetSampler(const GPUSampler::Config& config);
|
||||
void DestroySamplers();
|
||||
|
||||
void RenderBlankFrame();
|
||||
void RenderBlankFrame(VulkanSwapChain* swap_chain);
|
||||
|
||||
bool TryImportHostMemory(void* data, size_t data_size, VkBufferUsageFlags buffer_usage, VkDeviceMemory* out_memory,
|
||||
VkBuffer* out_buffer, VkDeviceSize* out_offset);
|
||||
@ -371,12 +376,7 @@ private:
|
||||
bool UpdateDescriptorSetsForLayout(u32 dirty);
|
||||
bool UpdateDescriptorSets(u32 dirty);
|
||||
|
||||
// Ends a render pass if we're currently in one.
|
||||
// When Bind() is next called, the pass will be restarted.
|
||||
void BeginRenderPass();
|
||||
void BeginSwapChainRenderPass(u32 clear_color);
|
||||
void EndRenderPass();
|
||||
bool InRenderPass();
|
||||
void BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color);
|
||||
|
||||
VkRenderPass CreateCachedRenderPass(RenderPassCacheKey key);
|
||||
static VkFramebuffer CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags);
|
||||
@ -427,7 +427,6 @@ private:
|
||||
OptionalExtensions m_optional_extensions = {};
|
||||
std::optional<bool> m_exclusive_fullscreen_control;
|
||||
|
||||
std::unique_ptr<VulkanSwapChain> m_swap_chain;
|
||||
std::unique_ptr<VulkanTexture> m_null_texture;
|
||||
|
||||
VkDescriptorSetLayout m_ubo_ds_layout = VK_NULL_HANDLE;
|
||||
@ -468,4 +467,5 @@ private:
|
||||
VulkanTextureBuffer* m_current_texture_buffer = nullptr;
|
||||
GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1);
|
||||
GSVector4i m_current_scissor = GSVector4i::cxpr(0, 0, 1, 1);
|
||||
VulkanSwapChain* m_current_swap_chain = nullptr;
|
||||
};
|
||||
|
@ -16,7 +16,7 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "common/heap_array.h"
|
||||
#include "common/log.h"
|
||||
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
VulkanShader::VulkanShader(GPUShaderStage stage, VkShaderModule mod) : GPUShader(stage), m_module(mod)
|
||||
{
|
||||
|
@ -9,7 +9,8 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/log.h"
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
VulkanStreamBuffer::VulkanStreamBuffer() = default;
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "vulkan_device.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/error.h"
|
||||
#include "common/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -20,9 +21,7 @@
|
||||
#include "util/metal_layer.h"
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
|
||||
static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
static VkFormat GetLinearFormat(VkFormat format)
|
||||
{
|
||||
@ -72,170 +71,142 @@ static const char* PresentModeToString(VkPresentModeKHR mode)
|
||||
}
|
||||
}
|
||||
|
||||
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
|
||||
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
std::optional<bool> exclusive_fullscreen_control)
|
||||
: m_window_info(wi), m_surface(surface), m_present_mode(present_mode),
|
||||
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
|
||||
: GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_exclusive_fullscreen_control(exclusive_fullscreen_control)
|
||||
{
|
||||
static_assert(NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
|
||||
}
|
||||
|
||||
VulkanSwapChain::~VulkanSwapChain()
|
||||
{
|
||||
DestroySwapChainImages();
|
||||
DestroySwapChain();
|
||||
DestroySurface();
|
||||
Destroy(VulkanDevice::GetInstance(), true);
|
||||
}
|
||||
|
||||
VkSurfaceKHR VulkanSwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
|
||||
bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error)
|
||||
{
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
if (wi->type == WindowInfo::Type::Win32)
|
||||
if (m_window_info.type == WindowInfo::Type::Win32)
|
||||
{
|
||||
VkWin32SurfaceCreateInfoKHR surface_create_info = {
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
|
||||
nullptr, // const void* pNext
|
||||
0, // VkWin32SurfaceCreateFlagsKHR flags
|
||||
nullptr, // HINSTANCE hinstance
|
||||
reinterpret_cast<HWND>(wi->window_handle) // HWND hwnd
|
||||
};
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface);
|
||||
const VkWin32SurfaceCreateInfoKHR surface_create_info = {.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||||
.hwnd = static_cast<HWND>(m_window_info.window_handle)};
|
||||
const VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: ");
|
||||
return VK_NULL_HANDLE;
|
||||
Vulkan::SetErrorObject(error, "vkCreateWin32SurfaceKHR() failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return surface;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
if (wi->type == WindowInfo::Type::MacOS)
|
||||
if (m_window_info.type == WindowInfo::Type::MacOS)
|
||||
{
|
||||
// TODO: FIXME
|
||||
if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi))
|
||||
return VK_NULL_HANDLE;
|
||||
m_metal_layer = CocoaTools::CreateMetalLayer(m_window_info, error);
|
||||
if (!m_metal_layer)
|
||||
return false;
|
||||
|
||||
VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
|
||||
static_cast<const CAMetalLayer*>(wi->surface_handle)};
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
|
||||
const VkMetalSurfaceCreateInfoEXT surface_create_info = {.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT,
|
||||
.pLayer = static_cast<const CAMetalLayer*>(m_metal_layer)};
|
||||
const VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &m_surface);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: ");
|
||||
return VK_NULL_HANDLE;
|
||||
Vulkan::SetErrorObject(error, "vkCreateMetalSurfaceEXT failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return surface;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
if (wi->type == WindowInfo::Type::Android)
|
||||
if (m_window_info.type == WindowInfo::Type::Android)
|
||||
{
|
||||
VkAndroidSurfaceCreateInfoKHR surface_create_info = {
|
||||
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
|
||||
nullptr, // const void* pNext
|
||||
0, // VkAndroidSurfaceCreateFlagsKHR flags
|
||||
reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
|
||||
};
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
|
||||
const VkAndroidSurfaceCreateInfoKHR surface_create_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
|
||||
.window = static_cast<ANativeWindow*>(m_window_info.window_handle)};
|
||||
const VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: ");
|
||||
return VK_NULL_HANDLE;
|
||||
Vulkan::SetErrorObject(error, "vkCreateAndroidSurfaceKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return surface;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||
if (wi->type == WindowInfo::Type::X11)
|
||||
if (m_window_info.type == WindowInfo::Type::X11)
|
||||
{
|
||||
VkXlibSurfaceCreateInfoKHR surface_create_info = {
|
||||
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
|
||||
nullptr, // const void* pNext
|
||||
0, // VkXlibSurfaceCreateFlagsKHR flags
|
||||
static_cast<Display*>(wi->display_connection), // Display* dpy
|
||||
reinterpret_cast<Window>(wi->window_handle) // Window window
|
||||
};
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
|
||||
const VkXlibSurfaceCreateInfoKHR surface_create_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = static_cast<Display*>(m_window_info.display_connection),
|
||||
.window = reinterpret_cast<Window>(m_window_info.window_handle)};
|
||||
const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: ");
|
||||
return VK_NULL_HANDLE;
|
||||
Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return surface;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
if (wi->type == WindowInfo::Type::Wayland)
|
||||
if (m_window_info.type == WindowInfo::Type::Wayland)
|
||||
{
|
||||
VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
|
||||
static_cast<struct wl_display*>(wi->display_connection),
|
||||
static_cast<struct wl_surface*>(wi->window_handle)};
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
|
||||
const VkWaylandSurfaceCreateInfoKHR surface_create_info = {
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
|
||||
static_cast<struct wl_display*>(m_window_info.display_connection),
|
||||
static_cast<struct wl_surface*>(m_window_info.window_handle)};
|
||||
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: ");
|
||||
return VK_NULL_HANDLE;
|
||||
Vulkan::SetErrorObject(error, "vkCreateWaylandSurfaceEXT failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return surface;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return VK_NULL_HANDLE;
|
||||
Error::SetStringFmt(error, "Unhandled window type: {}", static_cast<unsigned>(m_window_info.type));
|
||||
return false;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface)
|
||||
void VulkanSwapChain::DestroySurface()
|
||||
{
|
||||
vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), surface, nullptr);
|
||||
if (m_surface != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), m_surface, nullptr);
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle)
|
||||
CocoaTools::DestroyMetalLayer(wi);
|
||||
if (m_metal_layer)
|
||||
{
|
||||
CocoaTools::DestroyMetalLayer(m_window_info, m_metal_layer);
|
||||
m_metal_layer = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
|
||||
VkPresentModeKHR present_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control)
|
||||
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error)
|
||||
{
|
||||
std::unique_ptr<VulkanSwapChain> swap_chain =
|
||||
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control));
|
||||
if (!swap_chain->CreateSwapChain())
|
||||
return nullptr;
|
||||
|
||||
return swap_chain;
|
||||
}
|
||||
|
||||
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
|
||||
{
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
u32 format_count;
|
||||
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, nullptr);
|
||||
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, nullptr);
|
||||
if (res != VK_SUCCESS || format_count == 0)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
|
||||
res =
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data());
|
||||
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, surface_formats.data());
|
||||
Assert(res == VK_SUCCESS);
|
||||
|
||||
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA
|
||||
@ -255,28 +226,28 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
|
||||
return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
|
||||
}
|
||||
|
||||
ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:");
|
||||
SmallString errormsg = "Failed to find a suitable format for swap chain buffers. Available formats were:";
|
||||
for (const VkSurfaceFormatKHR& sf : surface_formats)
|
||||
ERROR_LOG(" {}", static_cast<unsigned>(sf.format));
|
||||
|
||||
errormsg.append_format(" {}", static_cast<unsigned>(sf.format));
|
||||
Error::SetStringView(error, errormsg);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode)
|
||||
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode,
|
||||
Error* error)
|
||||
{
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
|
||||
VkResult res;
|
||||
u32 mode_count;
|
||||
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, nullptr);
|
||||
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, nullptr);
|
||||
if (res != VK_SUCCESS || mode_count == 0)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
|
||||
return false;
|
||||
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<VkPresentModeKHR> present_modes(mode_count);
|
||||
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count,
|
||||
present_modes.data());
|
||||
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, present_modes.data());
|
||||
Assert(res == VK_SUCCESS);
|
||||
|
||||
// Checks if a particular mode is supported, if it is, returns that mode.
|
||||
@ -286,25 +257,25 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
|
||||
return it != present_modes.end();
|
||||
};
|
||||
|
||||
switch (*vsync_mode)
|
||||
switch (vsync_mode)
|
||||
{
|
||||
case GPUVSyncMode::Disabled:
|
||||
{
|
||||
// Prefer immediate > mailbox > fifo.
|
||||
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
|
||||
{
|
||||
*present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
}
|
||||
else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
|
||||
{
|
||||
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
|
||||
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
return VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO.");
|
||||
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
*vsync_mode = GPUVSyncMode::FIFO;
|
||||
vsync_mode = GPUVSyncMode::FIFO;
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -312,7 +283,7 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
|
||||
case GPUVSyncMode::FIFO:
|
||||
{
|
||||
// FIFO is always available.
|
||||
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -321,48 +292,50 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn
|
||||
// Mailbox > fifo.
|
||||
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
|
||||
{
|
||||
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
return VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO.");
|
||||
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
*vsync_mode = GPUVSyncMode::FIFO;
|
||||
vsync_mode = GPUVSyncMode::FIFO;
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
DefaultCaseIsUnreachable()
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::CreateSwapChain()
|
||||
bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
|
||||
{
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
const VkPhysicalDevice physdev = dev.GetVulkanPhysicalDevice();
|
||||
|
||||
// Select swap chain format
|
||||
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
|
||||
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(physdev, error);
|
||||
if (!surface_format.has_value())
|
||||
return false;
|
||||
|
||||
const std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(physdev, m_vsync_mode, error);
|
||||
if (!present_mode.has_value())
|
||||
return false;
|
||||
|
||||
// Look up surface properties to determine image count and dimensions
|
||||
VkSurfaceCapabilitiesKHR surface_capabilities;
|
||||
VkResult res =
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities);
|
||||
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physdev, m_surface, &surface_capabilities);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode.
|
||||
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
|
||||
u32 image_count = std::clamp<u32>(
|
||||
(m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
|
||||
(present_mode.value() == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
|
||||
(surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
|
||||
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode));
|
||||
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count,
|
||||
PresentModeToString(present_mode.value()));
|
||||
|
||||
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
|
||||
// determines window size? Android sometimes lags updating currentExtent, so don't use it.
|
||||
@ -396,7 +369,7 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
|
||||
{
|
||||
ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment");
|
||||
Error::SetStringView(error, "Swap chain does not support usage as color attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -406,25 +379,21 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
m_swap_chain = VK_NULL_HANDLE;
|
||||
|
||||
// Now we can actually create the swap chain
|
||||
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
nullptr,
|
||||
0,
|
||||
m_surface,
|
||||
image_count,
|
||||
surface_format->format,
|
||||
surface_format->colorSpace,
|
||||
size,
|
||||
1u,
|
||||
image_usage,
|
||||
VK_SHARING_MODE_EXCLUSIVE,
|
||||
0,
|
||||
nullptr,
|
||||
transform,
|
||||
alpha,
|
||||
m_present_mode,
|
||||
VK_TRUE,
|
||||
old_swap_chain};
|
||||
std::array<uint32_t, 2> indices = {{
|
||||
VkSwapchainCreateInfoKHR swap_chain_info = {.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = m_surface,
|
||||
.minImageCount = image_count,
|
||||
.imageFormat = surface_format->format,
|
||||
.imageColorSpace = surface_format->colorSpace,
|
||||
.imageExtent = size,
|
||||
.imageArrayLayers = 1u,
|
||||
.imageUsage = image_usage,
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.preTransform = transform,
|
||||
.compositeAlpha = alpha,
|
||||
.presentMode = present_mode.value(),
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = old_swap_chain};
|
||||
const std::array<u32, 2> queue_indices = {{
|
||||
dev.GetGraphicsQueueFamilyIndex(),
|
||||
dev.GetPresentQueueFamilyIndex(),
|
||||
}};
|
||||
@ -432,7 +401,7 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
{
|
||||
swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||
swap_chain_info.queueFamilyIndexCount = 2;
|
||||
swap_chain_info.pQueueFamilyIndices = indices.data();
|
||||
swap_chain_info.pQueueFamilyIndices = queue_indices.data();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -466,81 +435,101 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
|
||||
#endif
|
||||
|
||||
res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain);
|
||||
const VkDevice vkdev = dev.GetVulkanDevice();
|
||||
res = vkCreateSwapchainKHR(vkdev, &swap_chain_info, nullptr, &m_swap_chain);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkCreateSwapchainKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now destroy the old swap chain, since it's been recreated.
|
||||
// We can do this immediately since all work should have been completed before calling resize.
|
||||
if (old_swap_chain != VK_NULL_HANDLE)
|
||||
vkDestroySwapchainKHR(dev.GetVulkanDevice(), old_swap_chain, nullptr);
|
||||
vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr);
|
||||
|
||||
m_format = surface_format->format;
|
||||
m_window_info.surface_width = std::max(1u, size.width);
|
||||
m_window_info.surface_height = std::max(1u, size.height);
|
||||
if (size.width == 0 || size.width > std::numeric_limits<u16>::max() || size.height == 0 ||
|
||||
size.height > std::numeric_limits<u16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_present_mode = present_mode.value();
|
||||
m_window_info.surface_width = static_cast<u16>(size.width);
|
||||
m_window_info.surface_height = static_cast<u16>(size.height);
|
||||
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
|
||||
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
||||
{
|
||||
ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
|
||||
Error::SetStringFmt(error, "Unknown surface format {}", static_cast<u32>(surface_format->format));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
|
||||
{
|
||||
const VkDevice vkdev = dev.GetVulkanDevice();
|
||||
|
||||
// Get and create images.
|
||||
Assert(m_images.empty());
|
||||
|
||||
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, nullptr);
|
||||
u32 image_count;
|
||||
VkResult res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, nullptr);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkGetSwapchainImagesKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<VkImage> images(image_count);
|
||||
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, images.data());
|
||||
res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, images.data());
|
||||
Assert(res == VK_SUCCESS);
|
||||
|
||||
VkRenderPass render_pass = dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||
const VkRenderPass render_pass =
|
||||
dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||
if (render_pass == VK_NULL_HANDLE)
|
||||
{
|
||||
Error::SetStringFmt(error, "Failed to get render pass for format {}",
|
||||
GPUTexture::GetFormatName(m_window_info.surface_format));
|
||||
return false;
|
||||
}
|
||||
|
||||
Vulkan::FramebufferBuilder fbb;
|
||||
m_images.reserve(image_count);
|
||||
m_current_image = 0;
|
||||
for (u32 i = 0; i < image_count; i++)
|
||||
{
|
||||
Image image = {};
|
||||
Image& image = m_images.emplace_back();
|
||||
image.image = images[i];
|
||||
|
||||
const VkImageViewCreateInfo view_info = {
|
||||
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
nullptr,
|
||||
0,
|
||||
images[i],
|
||||
VK_IMAGE_VIEW_TYPE_2D,
|
||||
m_format,
|
||||
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY},
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = images[i],
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = VulkanDevice::TEXTURE_FORMAT_MAPPING[static_cast<u8>(m_window_info.surface_format)],
|
||||
.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY},
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
|
||||
};
|
||||
if ((res = vkCreateImageView(dev.GetVulkanDevice(), &view_info, nullptr, &image.view)) != VK_SUCCESS)
|
||||
if ((res = vkCreateImageView(vkdev, &view_info, nullptr, &image.view)) != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateImageView() failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkCreateImageView() failed: ", res);
|
||||
m_images.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
fbb.AddAttachment(image.view);
|
||||
fbb.SetRenderPass(render_pass);
|
||||
fbb.SetSize(size.width, size.height, 1);
|
||||
if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE)
|
||||
fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1);
|
||||
if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyImageView(dev.GetVulkanDevice(), image.view, nullptr);
|
||||
Error::SetStringView(error, "Failed to create swap chain image framebuffer.");
|
||||
vkDestroyImageView(vkdev, image.view, nullptr);
|
||||
m_images.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_images.push_back(image);
|
||||
}
|
||||
|
||||
m_current_semaphore = 0;
|
||||
@ -549,18 +538,18 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
ImageSemaphores& sema = m_semaphores[i];
|
||||
|
||||
const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
|
||||
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.available_semaphore);
|
||||
res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.available_semaphore);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
|
||||
Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
|
||||
res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
|
||||
vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr);
|
||||
Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res);
|
||||
vkDestroySemaphore(vkdev, sema.available_semaphore, nullptr);
|
||||
sema.available_semaphore = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
@ -569,22 +558,39 @@ bool VulkanSwapChain::CreateSwapChain()
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::Destroy(VulkanDevice& dev, bool wait_for_idle)
|
||||
{
|
||||
if (!m_swap_chain && !m_surface)
|
||||
return;
|
||||
|
||||
if (wait_for_idle)
|
||||
{
|
||||
if (dev.InRenderPass())
|
||||
dev.EndRenderPass();
|
||||
|
||||
dev.WaitForGPUIdle();
|
||||
}
|
||||
|
||||
DestroySwapChain();
|
||||
DestroySurface();
|
||||
}
|
||||
|
||||
void VulkanSwapChain::DestroySwapChainImages()
|
||||
{
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
const VkDevice vkdev = VulkanDevice::GetInstance().GetVulkanDevice();
|
||||
for (const auto& it : m_images)
|
||||
{
|
||||
// don't defer view destruction, images are no longer valid
|
||||
vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr);
|
||||
vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr);
|
||||
vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr);
|
||||
vkDestroyImageView(vkdev, it.view, nullptr);
|
||||
}
|
||||
m_images.clear();
|
||||
for (auto& it : m_semaphores)
|
||||
for (const auto& it : m_semaphores)
|
||||
{
|
||||
if (it.rendering_finished_semaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr);
|
||||
vkDestroySemaphore(vkdev, it.rendering_finished_semaphore, nullptr);
|
||||
if (it.available_semaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr);
|
||||
vkDestroySemaphore(vkdev, it.available_semaphore, nullptr);
|
||||
}
|
||||
m_semaphores = {};
|
||||
|
||||
@ -595,13 +601,11 @@ void VulkanSwapChain::DestroySwapChain()
|
||||
{
|
||||
DestroySwapChainImages();
|
||||
|
||||
if (m_swap_chain == VK_NULL_HANDLE)
|
||||
return;
|
||||
|
||||
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
|
||||
m_swap_chain = VK_NULL_HANDLE;
|
||||
m_window_info.surface_width = 0;
|
||||
m_window_info.surface_height = 0;
|
||||
if (m_swap_chain != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
|
||||
m_swap_chain = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
VkResult VulkanSwapChain::AcquireNextImage()
|
||||
@ -649,20 +653,27 @@ void VulkanSwapChain::ResetImageAcquireResult()
|
||||
m_image_acquire_result.reset();
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale)
|
||||
bool VulkanSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error)
|
||||
{
|
||||
m_window_info.surface_scale = new_scale;
|
||||
if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height)
|
||||
return true;
|
||||
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
if (dev.InRenderPass())
|
||||
dev.EndRenderPass();
|
||||
dev.SubmitCommandBuffer(true);
|
||||
|
||||
ReleaseCurrentImage();
|
||||
DestroySwapChainImages();
|
||||
|
||||
if (new_width != 0 && new_height != 0)
|
||||
{
|
||||
m_window_info.surface_width = new_width;
|
||||
m_window_info.surface_height = new_height;
|
||||
m_window_info.surface_width = static_cast<u16>(new_width);
|
||||
m_window_info.surface_height = static_cast<u16>(new_height);
|
||||
}
|
||||
|
||||
m_window_info.surface_scale = new_scale;
|
||||
|
||||
if (!CreateSwapChain())
|
||||
if (!CreateSwapChain(VulkanDevice::GetInstance(), error) || !CreateSwapChainImages(dev, error))
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
@ -671,18 +682,31 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
|
||||
bool VulkanSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error)
|
||||
{
|
||||
if (m_present_mode == present_mode)
|
||||
m_allow_present_throttle = allow_present_throttle;
|
||||
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
const std::optional<VkPresentModeKHR> new_present_mode =
|
||||
SelectPresentMode(dev.GetVulkanPhysicalDevice(), mode, error);
|
||||
if (!new_present_mode.has_value())
|
||||
return false;
|
||||
|
||||
// High-level mode could change without the actual backend mode changing.
|
||||
m_vsync_mode = mode;
|
||||
if (m_present_mode == new_present_mode.value())
|
||||
return true;
|
||||
|
||||
m_present_mode = present_mode;
|
||||
if (dev.InRenderPass())
|
||||
dev.EndRenderPass();
|
||||
dev.SubmitCommandBuffer(true);
|
||||
|
||||
// TODO: Use the maintenance extension to change it without recreating...
|
||||
// Recreate the swap chain with the new present mode.
|
||||
VERBOSE_LOG("Recreating swap chain to change present mode.");
|
||||
ReleaseCurrentImage();
|
||||
DestroySwapChainImages();
|
||||
if (!CreateSwapChain())
|
||||
if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
@ -691,18 +715,19 @@ bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
|
||||
bool VulkanSwapChain::RecreateSurface(Error* error)
|
||||
{
|
||||
VulkanDevice& dev = VulkanDevice::GetInstance();
|
||||
if (dev.InRenderPass())
|
||||
dev.EndRenderPass();
|
||||
dev.SubmitCommandBuffer(true);
|
||||
|
||||
// Destroy the old swap chain, images, and surface.
|
||||
DestroySwapChain();
|
||||
DestroySurface();
|
||||
|
||||
// Re-create the surface with the new native handle
|
||||
m_window_info = new_wi;
|
||||
m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info);
|
||||
if (m_surface == VK_NULL_HANDLE)
|
||||
if (!CreateSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), error))
|
||||
return false;
|
||||
|
||||
// The validation layers get angry at us if we don't call this before creating the swapchain.
|
||||
@ -711,17 +736,13 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
|
||||
m_surface, &present_supported);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
|
||||
return false;
|
||||
}
|
||||
if (!present_supported)
|
||||
{
|
||||
Panic("Recreated surface does not support presenting.");
|
||||
Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ", res);
|
||||
return false;
|
||||
}
|
||||
AssertMsg(present_supported, "Recreated surface does not support presenting.");
|
||||
|
||||
// Finally re-create the swap chain
|
||||
if (!CreateSwapChain())
|
||||
if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error))
|
||||
{
|
||||
DestroySwapChain();
|
||||
return false;
|
||||
@ -729,12 +750,3 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::DestroySurface()
|
||||
{
|
||||
if (m_surface == VK_NULL_HANDLE)
|
||||
return;
|
||||
|
||||
DestroyVulkanSurface(VulkanDevice::GetInstance().GetVulkanInstance(), &m_window_info, m_surface);
|
||||
m_surface = VK_NULL_HANDLE;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_device.h"
|
||||
#include "vulkan_loader.h"
|
||||
#include "vulkan_texture.h"
|
||||
#include "window_info.h"
|
||||
@ -14,41 +15,19 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class VulkanSwapChain
|
||||
class VulkanSwapChain final : public GPUSwapChain
|
||||
{
|
||||
public:
|
||||
// We don't actually need +1 semaphores, or, more than one really.
|
||||
// But, the validation layer gets cranky if we don't fence wait before the next image acquire.
|
||||
// So, add an additional semaphore to ensure that we're never acquiring before fence waiting.
|
||||
static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1
|
||||
|
||||
~VulkanSwapChain();
|
||||
|
||||
// Creates a vulkan-renderable surface for the specified window handle.
|
||||
static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi);
|
||||
|
||||
// Destroys a previously-created surface.
|
||||
static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface);
|
||||
|
||||
// Create a new swap chain from a pre-existing surface.
|
||||
static std::unique_ptr<VulkanSwapChain> Create(const WindowInfo& wi, VkSurfaceKHR surface,
|
||||
VkPresentModeKHR present_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control);
|
||||
|
||||
// Determines present mode to use.
|
||||
static bool SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode);
|
||||
VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
|
||||
std::optional<bool> exclusive_fullscreen_control);
|
||||
~VulkanSwapChain() override;
|
||||
|
||||
ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; }
|
||||
ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; }
|
||||
ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() const { return &m_swap_chain; }
|
||||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
|
||||
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
|
||||
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
|
||||
ALWAYS_INLINE u32 GetCurrentImageIndex() const { return m_current_image; }
|
||||
ALWAYS_INLINE const u32* GetCurrentImageIndexPtr() const { return &m_current_image; }
|
||||
ALWAYS_INLINE u32 GetImageCount() const { return static_cast<u32>(m_images.size()); }
|
||||
ALWAYS_INLINE VkFormat GetImageFormat() const { return m_format; }
|
||||
ALWAYS_INLINE VkImage GetCurrentImage() const { return m_images[m_current_image].image; }
|
||||
ALWAYS_INLINE VkImageView GetCurrentImageView() const { return m_images[m_current_image].view; }
|
||||
ALWAYS_INLINE VkFramebuffer GetCurrentFramebuffer() const { return m_images[m_current_image].framebuffer; }
|
||||
@ -69,27 +48,31 @@ public:
|
||||
return &m_semaphores[m_current_semaphore].rendering_finished_semaphore;
|
||||
}
|
||||
|
||||
bool CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error);
|
||||
bool CreateSwapChain(VulkanDevice& dev, Error* error);
|
||||
bool CreateSwapChainImages(VulkanDevice& dev, Error* error);
|
||||
void Destroy(VulkanDevice& dev, bool wait_for_idle);
|
||||
|
||||
VkResult AcquireNextImage();
|
||||
void ReleaseCurrentImage();
|
||||
void ResetImageAcquireResult();
|
||||
|
||||
bool RecreateSurface(const WindowInfo& new_wi);
|
||||
bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f);
|
||||
bool RecreateSurface(Error* error);
|
||||
|
||||
// Change vsync enabled state. This may fail as it causes a swapchain recreation.
|
||||
bool SetPresentMode(VkPresentModeKHR present_mode);
|
||||
bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override;
|
||||
bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override;
|
||||
|
||||
private:
|
||||
VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
|
||||
std::optional<bool> exclusive_fullscreen_control);
|
||||
// We don't actually need +1 semaphores, or, more than one really.
|
||||
// But, the validation layer gets cranky if we don't fence wait before the next image acquire.
|
||||
// So, add an additional semaphore to ensure that we're never acquiring before fence waiting.
|
||||
static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1
|
||||
|
||||
static std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkSurfaceKHR surface);
|
||||
|
||||
bool CreateSwapChain();
|
||||
void DestroySwapChain();
|
||||
std::optional<VkSurfaceFormatKHR> SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error);
|
||||
std::optional<VkPresentModeKHR> SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode, Error* error);
|
||||
|
||||
void DestroySwapChainImages();
|
||||
|
||||
void DestroySwapChain();
|
||||
void DestroySurface();
|
||||
|
||||
struct Image
|
||||
@ -105,9 +88,13 @@ private:
|
||||
VkSemaphore rendering_finished_semaphore;
|
||||
};
|
||||
|
||||
WindowInfo m_window_info;
|
||||
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// On MacOS, we need to store a pointer to the metal layer as well.
|
||||
void* m_metal_layer = nullptr;
|
||||
#endif
|
||||
|
||||
VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<Image> m_images;
|
||||
@ -116,7 +103,6 @@ private:
|
||||
u32 m_current_image = 0;
|
||||
u32 m_current_semaphore = 0;
|
||||
|
||||
VkFormat m_format = VK_FORMAT_UNDEFINED;
|
||||
VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
|
||||
std::optional<VkResult> m_image_acquire_result;
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "common/bitutils.h"
|
||||
#include "common/log.h"
|
||||
|
||||
LOG_CHANNEL(VulkanDevice);
|
||||
LOG_CHANNEL(GPUDevice);
|
||||
|
||||
static constexpr const VkComponentMapping s_identity_swizzle{
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
|
@ -11,21 +11,6 @@
|
||||
|
||||
LOG_CHANNEL(WindowInfo);
|
||||
|
||||
void WindowInfo::SetSurfaceless()
|
||||
{
|
||||
type = Type::Surfaceless;
|
||||
window_handle = nullptr;
|
||||
surface_width = 0;
|
||||
surface_height = 0;
|
||||
surface_refresh_rate = 0.0f;
|
||||
surface_scale = 1.0f;
|
||||
surface_format = GPUTexture::Format::Unknown;
|
||||
|
||||
#ifdef __APPLE__
|
||||
surface_handle = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "common/windows_headers.h"
|
||||
@ -318,4 +303,4 @@ std::optional<float> WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -3,15 +3,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_texture.h"
|
||||
#include "common/types.h"
|
||||
#include "gpu_texture.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
// Contains the information required to create a graphics context in a window.
|
||||
struct WindowInfo
|
||||
{
|
||||
enum class Type
|
||||
enum class Type : u8
|
||||
{
|
||||
Surfaceless,
|
||||
Win32,
|
||||
@ -19,27 +19,18 @@ struct WindowInfo
|
||||
Wayland,
|
||||
MacOS,
|
||||
Android,
|
||||
Display,
|
||||
};
|
||||
|
||||
Type type = Type::Surfaceless;
|
||||
void* display_connection = nullptr;
|
||||
void* window_handle = nullptr;
|
||||
u32 surface_width = 0;
|
||||
u32 surface_height = 0;
|
||||
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
|
||||
u16 surface_width = 0;
|
||||
u16 surface_height = 0;
|
||||
float surface_refresh_rate = 0.0f;
|
||||
float surface_scale = 1.0f;
|
||||
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
|
||||
|
||||
// Needed for macOS.
|
||||
#ifdef __APPLE__
|
||||
void* surface_handle = nullptr;
|
||||
#endif
|
||||
void* display_connection = nullptr;
|
||||
void* window_handle = nullptr;
|
||||
|
||||
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
|
||||
|
||||
// Changes the window to be surfaceless (i.e. no handle/size/etc).
|
||||
void SetSurfaceless();
|
||||
|
||||
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user