diff --git a/bin/resources/shaders/dx11/imgui.fx b/bin/resources/shaders/dx11/imgui.fx new file mode 100644 index 0000000000..60dcfa6d67 --- /dev/null +++ b/bin/resources/shaders/dx11/imgui.fx @@ -0,0 +1,36 @@ +cbuffer vertexBuffer : register(b0) +{ + float4x4 ProjectionMatrix; +}; + +struct VS_INPUT +{ + float2 pos : POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +struct PS_INPUT +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +PS_INPUT vs_main(VS_INPUT input) +{ + PS_INPUT output; + output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f)); + output.col = input.col; + output.uv = input.uv; + return output; +} + +sampler sampler0 : register(s0); +Texture2D texture0 : register(t0); + +float4 ps_main(PS_INPUT input) : SV_Target +{ + float4 out_col = input.col * texture0.Sample(sampler0, input.uv); + return out_col; +} diff --git a/bin/resources/shaders/opengl/imgui.glsl b/bin/resources/shaders/opengl/imgui.glsl new file mode 100644 index 0000000000..74573fcd15 --- /dev/null +++ b/bin/resources/shaders/opengl/imgui.glsl @@ -0,0 +1,35 @@ +#ifdef VERTEX_SHADER + +layout(location = 0) in vec2 Position; +layout(location = 1) in vec2 UV; +layout(location = 2) in vec4 Color; + +uniform mat4 ProjMtx; + +out vec2 Frag_UV; +out vec4 Frag_Color; + +void vs_main() +{ + Frag_UV = UV; + Frag_Color = Color; + gl_Position = ProjMtx * vec4(Position.xy, 0.0, 1.0); +} + +#endif + +#ifdef FRAGMENT_SHADER + +layout(binding = 0) uniform sampler2D Texture; + +in vec2 Frag_UV; +in vec4 Frag_Color; + +layout(location = 0) out vec4 Out_Color; + +void ps_main() +{ + Out_Color = Frag_Color * texture(Texture, Frag_UV.st); +} + +#endif diff --git a/bin/resources/shaders/vulkan/imgui.glsl b/bin/resources/shaders/vulkan/imgui.glsl new file mode 100644 index 0000000000..eb691e2124 --- /dev/null +++ b/bin/resources/shaders/vulkan/imgui.glsl @@ -0,0 +1,39 @@ +#ifdef VERTEX_SHADER + +layout(location = 0) in vec2 Position; +layout(location = 1) in vec2 UV; +layout(location = 2) in vec4 Color; + +layout(push_constant) uniform PushConstants +{ + vec2 uScale; + vec2 uTranslate; +}; + +layout(location = 0) out vec2 Frag_UV; +layout(location = 1) out vec4 Frag_Color; + +void vs_main() +{ + Frag_UV = UV; + Frag_Color = Color; + gl_Position = vec4(Position * uScale + uTranslate, 0.0f, 1.0f); +} + +#endif + +#ifdef FRAGMENT_SHADER + +layout(binding = 0) uniform sampler2D Texture; + +layout(location = 0) in vec2 Frag_UV; +layout(location = 1) in vec4 Frag_Color; + +layout(location = 0) out vec4 Out_Color; + +void ps_main() +{ + Out_Color = Frag_Color * texture(Texture, Frag_UV.st); +} + +#endif diff --git a/common/D3D11/ShaderCache.cpp b/common/D3D11/ShaderCache.cpp index 3d4f0351d0..9e0a0240e0 100644 --- a/common/D3D11/ShaderCache.cpp +++ b/common/D3D11/ShaderCache.cpp @@ -41,10 +41,7 @@ D3D11::ShaderCache::ShaderCache() = default; D3D11::ShaderCache::~ShaderCache() { - if (m_index_file) - std::fclose(m_index_file); - if (m_blob_file) - std::fclose(m_blob_file); + Close(); } bool D3D11::ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const @@ -82,6 +79,20 @@ bool D3D11::ShaderCache::Open(std::string_view base_path, D3D_FEATURE_LEVEL feat return true; } +void D3D11::ShaderCache::Close() +{ + if (m_index_file) + { + std::fclose(m_index_file); + m_index_file = nullptr; + } + if (m_blob_file) + { + std::fclose(m_blob_file); + m_blob_file = nullptr; + } +} + bool D3D11::ShaderCache::CreateNew(const std::string& index_filename, const std::string& blob_filename) { if (FileSystem::FileExists(index_filename.c_str())) diff --git a/common/D3D11/ShaderCache.h b/common/D3D11/ShaderCache.h index 25f3c40912..4b5ca7f9f0 100644 --- a/common/D3D11/ShaderCache.h +++ b/common/D3D11/ShaderCache.h @@ -38,6 +38,7 @@ namespace D3D11 bool UsingDebugShaders() const { return m_debug; } bool Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, u32 version, bool debug); + void Close(); wil::com_ptr_nothrow GetShaderBlob(ShaderCompiler::Type type, const std::string_view& shader_code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); @@ -103,7 +104,6 @@ namespace D3D11 bool CreateNew(const std::string& index_filename, const std::string& blob_filename); bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); - void Close(); wil::com_ptr_nothrow CompileAndAddShaderBlob(const CacheIndexKey& key, const std::string_view& shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point); diff --git a/common/D3D12/Context.cpp b/common/D3D12/Context.cpp index c3c174aa74..aa3bbf9cab 100644 --- a/common/D3D12/Context.cpp +++ b/common/D3D12/Context.cpp @@ -130,7 +130,10 @@ bool Context::Create(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, bool e pxAssertRel(!g_d3d12_context, "No context exists"); if (!LoadD3D12Library()) + { + Console.Error("Failed to load D3D12 library"); return false; + } g_d3d12_context.reset(new Context()); if (!g_d3d12_context->CreateDevice(dxgi_factory, adapter, enable_debug_layer) || @@ -188,7 +191,10 @@ bool Context::CreateDevice(IDXGIFactory5* dxgi_factory, IDXGIAdapter1* adapter, // Create the actual device. hr = s_d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device)); if (FAILED(hr)) + { + Console.Error("Failed to create D3D12 device: %08X", hr); return false; + } // get adapter const LUID luid(m_device->GetAdapterLuid()); diff --git a/common/D3D12/ShaderCache.cpp b/common/D3D12/ShaderCache.cpp index f145ab6e08..5d6ab18210 100644 --- a/common/D3D12/ShaderCache.cpp +++ b/common/D3D12/ShaderCache.cpp @@ -45,14 +45,7 @@ ShaderCache::ShaderCache() = default; ShaderCache::~ShaderCache() { - if (m_pipeline_index_file) - std::fclose(m_pipeline_index_file); - if (m_pipeline_blob_file) - std::fclose(m_pipeline_blob_file); - if (m_shader_index_file) - std::fclose(m_shader_index_file); - if (m_shader_blob_file) - std::fclose(m_shader_blob_file); + Close(); } bool ShaderCache::CacheIndexKey::operator==(const CacheIndexKey& key) const @@ -109,6 +102,32 @@ bool ShaderCache::Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_lev return result; } +void ShaderCache::Close() +{ + if (m_pipeline_index_file) + { + std::fclose(m_pipeline_index_file); + m_pipeline_index_file = nullptr; + } + if (m_pipeline_blob_file) + { + std::fclose(m_pipeline_blob_file); + m_pipeline_blob_file = nullptr; + } + if (m_shader_index_file) + { + std::fclose(m_shader_index_file); + m_shader_index_file = nullptr; + } + if (m_shader_blob_file) + { + std::fclose(m_shader_blob_file); + m_shader_blob_file = nullptr; + } + + m_base_path = {}; +} + void ShaderCache::InvalidatePipelineCache() { m_pipeline_index.clear(); diff --git a/common/D3D12/ShaderCache.h b/common/D3D12/ShaderCache.h index 7befa3436d..bab26a9988 100644 --- a/common/D3D12/ShaderCache.h +++ b/common/D3D12/ShaderCache.h @@ -52,6 +52,7 @@ namespace D3D12 __fi bool UsingDebugShaders() const { return m_debug; } bool Open(std::string_view base_path, D3D_FEATURE_LEVEL feature_level, u32 version, bool debug); + void Close(); __fi ComPtr GetVertexShader(std::string_view shader_code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main") @@ -129,7 +130,6 @@ namespace D3D12 bool ReadExisting(const std::string& index_filename, const std::string& blob_filename, std::FILE*& index_file, std::FILE*& blob_file, CacheIndex& index); void InvalidatePipelineCache(); - void Close(); ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point); diff --git a/common/GL/Program.cpp b/common/GL/Program.cpp index f4894806a3..bf7cef1f5c 100644 --- a/common/GL/Program.cpp +++ b/common/GL/Program.cpp @@ -485,151 +485,28 @@ namespace GL glUniform4fv(location, 1, v); } - void Program::Uniform1ui(const char* name, u32 x) const + void Program::UniformMatrix2fv(int index, const float* v) { - const GLint location = glGetUniformLocation(m_program_id, name); + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; if (location >= 0) - glUniform1ui(location, x); + glUniformMatrix2fv(location, 1, GL_FALSE, v); } - void Program::Uniform2ui(const char* name, u32 x, u32 y) const + void Program::UniformMatrix3fv(int index, const float* v) { - const GLint location = glGetUniformLocation(m_program_id, name); + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; if (location >= 0) - glUniform2ui(location, x, y); + glUniformMatrix3fv(location, 1, GL_FALSE, v); } - void Program::Uniform3ui(const char* name, u32 x, u32 y, u32 z) const + void Program::UniformMatrix4fv(int index, const float* v) { - const GLint location = glGetUniformLocation(m_program_id, name); + pxAssert(static_cast(index) < m_uniform_locations.size()); + const GLint location = m_uniform_locations[index]; if (location >= 0) - glUniform3ui(location, x, y, z); - } - - void Program::Uniform4ui(const char* name, u32 x, u32 y, u32 z, u32 w) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4ui(location, x, y, z, w); - } - - void Program::Uniform1i(const char* name, s32 x) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform1i(location, x); - } - - void Program::Uniform2i(const char* name, s32 x, s32 y) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform2i(location, x, y); - } - - void Program::Uniform3i(const char* name, s32 x, s32 y, s32 z) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform3i(location, x, y, z); - } - - void Program::Uniform4i(const char* name, s32 x, s32 y, s32 z, s32 w) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4i(location, x, y, z, w); - } - - void Program::Uniform1f(const char* name, float x) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform1f(location, x); - } - - void Program::Uniform2f(const char* name, float x, float y) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform2f(location, x, y); - } - - void Program::Uniform3f(const char* name, float x, float y, float z) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform3f(location, x, y, z); - } - - void Program::Uniform4f(const char* name, float x, float y, float z, float w) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4f(location, x, y, z, w); - } - - void Program::Uniform2uiv(const char* name, const u32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform2uiv(location, 1, v); - } - - void Program::Uniform3uiv(const char* name, const u32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform3uiv(location, 1, v); - } - - void Program::Uniform4uiv(const char* name, const u32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4uiv(location, 1, v); - } - - void Program::Uniform2iv(const char* name, const s32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform2iv(location, 1, v); - } - - void Program::Uniform3iv(const char* name, const s32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform3iv(location, 1, v); - } - - void Program::Uniform4iv(const char* name, const s32* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4iv(location, 1, v); - } - - void Program::Uniform2fv(const char* name, const float* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform2fv(location, 1, v); - } - - void Program::Uniform3fv(const char* name, const float* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform3fv(location, 1, v); - } - - void Program::Uniform4fv(const char* name, const float* v) const - { - const GLint location = glGetUniformLocation(m_program_id, name); - if (location >= 0) - glUniform4fv(location, 1, v); + glUniformMatrix4fv(location, 1, GL_FALSE, v); } void Program::BindUniformBlock(const char* name, u32 index) diff --git a/common/GL/Program.h b/common/GL/Program.h index 0d1e9afe49..85a62eaf5a 100644 --- a/common/GL/Program.h +++ b/common/GL/Program.h @@ -79,27 +79,9 @@ namespace GL void Uniform3fv(int index, const float* v) const; void Uniform4fv(int index, const float* v) const; - void Uniform1ui(const char* name, u32 x) const; - void Uniform2ui(const char* name, u32 x, u32 y) const; - void Uniform3ui(const char* name, u32 x, u32 y, u32 z) const; - void Uniform4ui(const char* name, u32 x, u32 y, u32 z, u32 w) const; - void Uniform1i(const char* name, s32 x) const; - void Uniform2i(const char* name, s32 x, s32 y) const; - void Uniform3i(const char* name, s32 x, s32 y, s32 z) const; - void Uniform4i(const char* name, s32 x, s32 y, s32 z, s32 w) const; - void Uniform1f(const char* name, float x) const; - void Uniform2f(const char* name, float x, float y) const; - void Uniform3f(const char* name, float x, float y, float z) const; - void Uniform4f(const char* name, float x, float y, float z, float w) const; - void Uniform2uiv(const char* name, const u32* v) const; - void Uniform3uiv(const char* name, const u32* v) const; - void Uniform4uiv(const char* name, const u32* v) const; - void Uniform2iv(const char* name, const s32* v) const; - void Uniform3iv(const char* name, const s32* v) const; - void Uniform4iv(const char* name, const s32* v) const; - void Uniform2fv(const char* name, const float* v) const; - void Uniform3fv(const char* name, const float* v) const; - void Uniform4fv(const char* name, const float* v) const; + void UniformMatrix2fv(int index, const float* v); + void UniformMatrix3fv(int index, const float* v); + void UniformMatrix4fv(int index, const float* v); void BindUniformBlock(const char* name, u32 index); diff --git a/common/GL/ShaderCache.cpp b/common/GL/ShaderCache.cpp index c2fca6af58..f4047230fc 100644 --- a/common/GL/ShaderCache.cpp +++ b/common/GL/ShaderCache.cpp @@ -218,9 +218,17 @@ namespace GL { m_index.clear(); if (m_index_file) + { std::fclose(m_index_file); + m_index_file = nullptr; + } if (m_blob_file) + { std::fclose(m_blob_file); + m_blob_file = nullptr; + } + + m_base_path = {}; } bool ShaderCache::Recreate() diff --git a/common/GL/ShaderCache.h b/common/GL/ShaderCache.h index 31dba488bf..6cc65bd01b 100644 --- a/common/GL/ShaderCache.h +++ b/common/GL/ShaderCache.h @@ -36,6 +36,7 @@ namespace GL ~ShaderCache(); bool Open(bool is_gles, std::string_view base_path, u32 version); + void Close(); std::optional GetProgram(const std::string_view vertex_shader, const std::string_view geometry_shader, const std::string_view fragment_shader, const PreLinkCallback& callback = {}); @@ -94,7 +95,6 @@ namespace GL bool CreateNew(const std::string& index_filename, const std::string& blob_filename); bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); - void Close(); bool Recreate(); bool WriteToBlobFile(const CacheIndexKey& key, const std::vector& prog_data, u32 prog_format); diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 7d5578f5b8..7214eb7245 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -45,7 +45,7 @@ #include "pcsx2/GS.h" #include "pcsx2/GS/GS.h" #include "pcsx2/GSDumpReplayer.h" -#include "pcsx2/HostDisplay.h" +#include "pcsx2/Host.h" #include "pcsx2/HostSettings.h" #include "pcsx2/INISettingsInterface.h" #include "pcsx2/PAD/Host/PAD.h" @@ -254,36 +254,21 @@ void Host::SetRelativeMouseMode(bool enabled) { } -bool Host::AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail) +std::optional Host::AcquireRenderWindow(RenderAPI api) { - const std::optional wi(GSRunner::GetPlatformWindowInfo()); - if (!wi.has_value()) - return false; - - g_host_display = HostDisplay::CreateForAPI(api); - if (!g_host_display) - return false; - - if (!g_host_display->CreateDevice(wi.value(), Host::GetEffectiveVSyncMode()) || - !g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize()) - { - ReleaseHostDisplay(clear_state_on_fail); - return false; - } - - Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(g_host_display->GetRenderAPI())); - Console.Indent().WriteLn(g_host_display->GetDriverInfo()); - - return g_host_display.get(); + return GSRunner::GetPlatformWindowInfo(); } -void Host::ReleaseHostDisplay(bool clear_state) +std::optional Host::UpdateRenderWindow() { - ImGuiManager::Shutdown(clear_state); - g_host_display.reset(); + return GSRunner::GetPlatformWindowInfo(); } -HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip) +void Host::ReleaseRenderWindow() +{ +} + +void Host::BeginPresentFrame() { if (s_loop_number == 0 && !s_output_prefix.empty()) { @@ -294,33 +279,6 @@ HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip) std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, s_dump_frame_number)); GSQueueSnapshot(dump_path); } - - const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip); - if (result != HostDisplay::PresentResult::OK) - { - // don't render imgui - ImGuiManager::SkipFrame(); - } - - return result; -} - -void Host::EndPresentFrame() -{ - if (GSDumpReplayer::IsReplayingDump()) - GSDumpReplayer::RenderUI(); - - ImGuiManager::RenderOSD(); - g_host_display->EndPresent(); - ImGuiManager::NewFrame(); -} - -void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale) -{ -} - -void Host::UpdateHostDisplay() -{ } void Host::RequestResizeHostDisplay(s32 width, s32 height) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index bae877ffa8..3675e1cb7e 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -32,9 +32,9 @@ #include "pcsx2/CDVD/CDVDdiscReader.h" #include "pcsx2/Frontend/GameList.h" #include "pcsx2/Frontend/LogSink.h" +#include "pcsx2/GS.h" #include "pcsx2/GS/GS.h" #include "pcsx2/GSDumpReplayer.h" -#include "pcsx2/HostDisplay.h" #include "pcsx2/HostSettings.h" #include "pcsx2/PerformanceMetrics.h" #include "pcsx2/Recording/InputRecording.h" @@ -415,8 +415,8 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread) connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI); connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI); connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection); - connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection); - connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection); + connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplayWindow, Qt::BlockingQueuedConnection); + connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplayWindow, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested); connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested); @@ -1167,7 +1167,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool stoppi void MainWindow::updateDisplayRelatedActions(bool has_surface, bool render_to_main, bool fullscreen) { // rendering to main, or switched to gamelist/grid - m_ui.actionViewSystemDisplay->setEnabled((has_surface && render_to_main) || (!has_surface && g_host_display)); + m_ui.actionViewSystemDisplay->setEnabled((has_surface && render_to_main) || (!has_surface && GetMTGS().IsOpen())); m_ui.menuWindowSize->setEnabled(has_surface && !fullscreen); m_ui.actionFullscreen->setEnabled(has_surface); @@ -1279,10 +1279,10 @@ bool MainWindow::isShowingGameList() const bool MainWindow::isRenderingFullscreen() const { - if (!g_host_display || !m_display_widget) + if (!GetMTGS().IsOpen() || !m_display_widget) return false; - return getDisplayContainer()->isFullScreen() || g_host_display->IsFullscreen(); + return getDisplayContainer()->isFullScreen(); } bool MainWindow::isRenderingToMain() const @@ -2197,17 +2197,12 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr #endif -DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) +std::optional MainWindow::createDisplayWindow(bool fullscreen, bool render_to_main) { - DevCon.WriteLn("createDisplay(%u, %u)", static_cast(fullscreen), static_cast(render_to_main)); + DevCon.WriteLn( + "createDisplayWindow() fullscreen=%s render_to_main=%s", fullscreen ? "true" : "false", render_to_main ? "true" : "false"); - if (!g_host_display) - return nullptr; - - const std::string fullscreen_mode(Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); - const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen()); - - createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen); + createDisplayWidget(fullscreen, render_to_main); // we need the surface visible.. this might be able to be replaced with something else QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); @@ -2217,26 +2212,12 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) { QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); destroyDisplayWidget(true); - return nullptr; + return std::nullopt; } g_emu_thread->connectDisplaySignals(m_display_widget); - - if (!g_host_display->CreateDevice(wi.value(), Host::GetEffectiveVSyncMode())) - { - QMessageBox::critical(this, tr("Error"), - tr("Failed to create host display device. This may be due to your GPU not supporting the chosen renderer (%1), or because your " - "graphics drivers need to be updated.") - .arg(QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(EmuConfig.GS.Renderer)))); - destroyDisplayWidget(true); - return nullptr; - } - m_display_created = true; - if (is_exclusive_fullscreen) - setDisplayFullscreen(fullscreen_mode); - updateWindowTitle(); updateWindowState(); @@ -2246,34 +2227,28 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) updateDisplayWidgetCursor(); m_display_widget->setFocus(); - g_host_display->DoneCurrent(); - return m_display_widget; + return wi; } -DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless) +std::optional MainWindow::updateDisplayWindow(bool fullscreen, bool render_to_main, bool surfaceless) { - DevCon.WriteLn("updateDisplay() fullscreen=%s render_to_main=%s surfaceless=%s", fullscreen ? "true" : "false", + DevCon.WriteLn("updateDisplayWindow() fullscreen=%s render_to_main=%s surfaceless=%s", fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false"); QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); const bool is_fullscreen = isRenderingFullscreen(); const bool is_rendering_to_main = isRenderingToMain(); - const std::string fullscreen_mode(Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); - const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && g_host_display->SupportsFullscreen()); const bool changing_surfaceless = (!m_display_widget != surfaceless); if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless) - return m_display_widget; + return m_display_widget->getWindowInfo(); // 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 (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container && - !changing_surfaceless) + if (!is_rendering_to_main && !render_to_main && has_container == needs_container && !needs_container && !changing_surfaceless) { DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed")); - if (g_host_display->IsFullscreen()) - g_host_display->SetFullscreen(false, 0, 0, 0.0f); // since we don't destroy the display widget, we need to save it here if (!is_fullscreen && !is_rendering_to_main) @@ -2294,45 +2269,37 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, b updateWindowState(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - return m_display_widget; + return m_display_widget->getWindowInfo(); } - g_host_display->DestroySurface(); - destroyDisplayWidget(surfaceless); // if we're going to surfaceless, we're done here if (surfaceless) - return nullptr; + return WindowInfo(); - createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen); + createDisplayWidget(fullscreen, render_to_main); std::optional wi = m_display_widget->getWindowInfo(); if (!wi.has_value()) { QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget")); destroyDisplayWidget(true); - return nullptr; + return std::nullopt; } g_emu_thread->connectDisplaySignals(m_display_widget); - if (!g_host_display->ChangeWindow(wi.value())) - pxFailRel("Failed to recreate surface on new widget."); - - if (is_exclusive_fullscreen) - setDisplayFullscreen(fullscreen_mode); - updateWindowTitle(); updateWindowState(); updateDisplayWidgetCursor(); m_display_widget->setFocus(); - return m_display_widget; + return wi; } -void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen) +void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main) { // If we're rendering to main and were hidden (e.g. coming back from fullscreen), // make sure we're visible before trying to add ourselves. Otherwise Wayland breaks. @@ -2374,10 +2341,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool restoreDisplayWindowGeometryFromConfig(); } - if (!is_exclusive_fullscreen) - container->showFullScreen(); - else - container->showNormal(); + container->showFullScreen(); } else if (!render_to_main) { @@ -2558,23 +2522,6 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig() } } -void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) -{ - u32 width, height; - float refresh_rate; - if (HostDisplay::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate)) - { - if (g_host_display->SetFullscreen(true, width, height, refresh_rate)) - { - Host::AddOSDMessage("Acquired exclusive fullscreen.", Host::OSD_INFO_DURATION); - } - else - { - Host::AddOSDMessage("Failed to acquire exclusive fullscreen.", Host::OSD_WARNING_DURATION); - } - } -} - SettingsDialog* MainWindow::getSettingsDialog() { if (!m_settings_dialog) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 1c7ae85d6c..3b50e821a4 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -128,8 +128,8 @@ public Q_SLOTS: private Q_SLOTS: void onUpdateCheckComplete(); - DisplayWidget* createDisplay(bool fullscreen, bool render_to_main); - DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless); + std::optional createDisplayWindow(bool fullscreen, bool render_to_main); + std::optional updateDisplayWindow(bool fullscreen, bool render_to_main, bool surfaceless); void displayResizeRequested(qint32 width, qint32 height); void relativeMouseModeRequested(bool enabled); void destroyDisplay(); @@ -233,10 +233,9 @@ private: QWidget* getDisplayContainer() const; void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); - void createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen); + void createDisplayWidget(bool fullscreen, bool render_to_main); void destroyDisplayWidget(bool show_game_list); void updateDisplayWidgetCursor(); - void setDisplayFullscreen(const std::string& fullscreen_mode); SettingsDialog* getSettingsDialog(); void doSettings(const char* category = nullptr); diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 7a7269f9cf..1fd1557468 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -41,7 +41,6 @@ #include "pcsx2/GS.h" #include "pcsx2/GS/GS.h" #include "pcsx2/GSDumpReplayer.h" -#include "pcsx2/HostDisplay.h" #include "pcsx2/HostSettings.h" #include "pcsx2/INISettingsInterface.h" #include "pcsx2/PAD/Host/PAD.h" @@ -191,9 +190,11 @@ void EmuThread::startFullscreenUI(bool fullscreen) return; } - if (VMManager::HasValidVM()) + if (VMManager::HasValidVM() || GetMTGS().IsOpen()) return; + // this should just set the flag so it gets automatically started + ImGuiManager::InitializeFullscreenUI(); m_run_fullscreen_ui = true; if (fullscreen) m_is_fullscreen = true; @@ -216,13 +217,13 @@ void EmuThread::stopFullscreenUI() QMetaObject::invokeMethod(this, &EmuThread::stopFullscreenUI, Qt::QueuedConnection); // wait until the host display is gone - while (g_host_display) + while (GetMTGS().IsOpen()) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); return; } - if (!g_host_display) + if (!GetMTGS().IsOpen()) return; pxAssertRel(!VMManager::HasValidVM(), "VM is not valid at FSUI shutdown time"); @@ -590,7 +591,7 @@ void EmuThread::checkForSettingChanges(const Pcsx2Config& old_config) { QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection); - if (g_host_display) + if (GetMTGS().IsOpen()) { const bool render_to_main = shouldRenderToMain(); if (!m_is_fullscreen && m_is_rendering_to_main != render_to_main) @@ -787,7 +788,7 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget) void EmuThread::onDisplayWindowResized(int width, int height, float scale) { - if (!g_host_display) + if (!GetMTGS().IsOpen()) return; GetMTGS().ResizeDisplayWindow(width, height, scale); @@ -892,115 +893,41 @@ void EmuThread::endCapture() GetMTGS().RunOnGSThread(&GSEndCapture); } -void EmuThread::updateDisplay() +std::optional EmuThread::acquireRenderWindow() { - pxAssertRel(!isOnEmuThread(), "Not on emu thread"); - - // finished with the display for now - g_host_display->DoneCurrent(); - - // but we should get it back after this call - onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless); - if (!g_host_display->MakeCurrent()) - { - pxFailRel("Failed to recreate context after updating"); - return; - } -} - -bool EmuThread::acquireHostDisplay(RenderAPI api, bool clear_state_on_fail) -{ - pxAssertRel(!g_host_display, "Host display does not exist on create"); m_is_rendering_to_main = shouldRenderToMain(); m_is_surfaceless = false; - g_host_display = HostDisplay::CreateForAPI(api); - if (!g_host_display) - return false; - - DisplayWidget* widget = emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main); - if (!widget) - { - g_host_display.reset(); - return false; - } - - connectDisplaySignals(widget); - - if (!g_host_display->MakeCurrent()) - { - Console.Error("Failed to make render context current"); - releaseHostDisplay(clear_state_on_fail); - return false; - } - - if (!g_host_display->SetupDevice() || !ImGuiManager::Initialize()) - { - Console.Error("Failed to initialize device/imgui"); - releaseHostDisplay(clear_state_on_fail); - return false; - } - - Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(g_host_display->GetRenderAPI())); - Console.Indent().WriteLn(g_host_display->GetDriverInfo()); - - if (m_run_fullscreen_ui && !ImGuiManager::InitializeFullscreenUI()) - { - Console.Error("Failed to initialize fullscreen UI"); - releaseHostDisplay(clear_state_on_fail); - m_run_fullscreen_ui = false; - return false; - } - - return true; + return emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main); } -void EmuThread::releaseHostDisplay(bool clear_state) +std::optional EmuThread::updateRenderWindow() { - ImGuiManager::Shutdown(clear_state); + return emit onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless); +} - g_host_display.reset(); +void EmuThread::releaseRenderWindow() +{ emit onDestroyDisplayRequested(); } -bool Host::AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail) +std::optional Host::AcquireRenderWindow(RenderAPI api) { - return g_emu_thread->acquireHostDisplay(api, clear_state_on_fail); + return g_emu_thread->acquireRenderWindow(); } -void Host::ReleaseHostDisplay(bool clear_state) +std::optional Host::UpdateRenderWindow() { - g_emu_thread->releaseHostDisplay(clear_state); + return g_emu_thread->updateRenderWindow(); } -HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip) +void Host::ReleaseRenderWindow() { - const HostDisplay::PresentResult result = g_host_display->BeginPresent(frame_skip); - if (result != HostDisplay::PresentResult::OK) - { - // if we're skipping a frame, we need to reset imgui's state, since - // we won't be calling EndPresentFrame(). - ImGuiManager::SkipFrame(); - } - - return result; + return g_emu_thread->releaseRenderWindow(); } -void Host::EndPresentFrame() +void Host::BeginPresentFrame() { - if (GSDumpReplayer::IsReplayingDump()) - GSDumpReplayer::RenderUI(); - - FullscreenUI::Render(); - ImGuiManager::RenderOSD(); - g_host_display->EndPresent(); - ImGuiManager::NewFrame(); -} - -void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale) -{ - g_host_display->ResizeWindow(new_window_width, new_window_height, new_window_scale); - ImGuiManager::WindowResized(); } void Host::RequestResizeHostDisplay(s32 width, s32 height) @@ -1008,12 +935,6 @@ void Host::RequestResizeHostDisplay(s32 width, s32 height) g_emu_thread->onResizeDisplayRequested(width, height); } -void Host::UpdateHostDisplay() -{ - g_emu_thread->updateDisplay(); - ImGuiManager::WindowResized(); -} - void Host::OnVMStarting() { CommonHost::OnVMStarting(); diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index c239e0882a..38d9079a8c 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -21,7 +21,6 @@ #include #include "pcsx2/Host.h" -#include "pcsx2/HostDisplay.h" #include "pcsx2/HostSettings.h" #include "pcsx2/Frontend/InputManager.h" #include "pcsx2/VMManager.h" @@ -70,10 +69,10 @@ public: bool shouldRenderToMain() const; /// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main). - bool acquireHostDisplay(RenderAPI api, bool clear_state_on_fail); + std::optional acquireRenderWindow(); + std::optional updateRenderWindow(); void connectDisplaySignals(DisplayWidget* widget); - void releaseHostDisplay(bool clear_state); - void updateDisplay(); + void releaseRenderWindow(); void startBackgroundControllerPollTimer(); void stopBackgroundControllerPollTimer(); @@ -118,8 +117,8 @@ public Q_SLOTS: Q_SIGNALS: bool messageConfirmed(const QString& title, const QString& message); - DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main); - DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless); + std::optional onCreateDisplayRequested(bool fullscreen, bool render_to_main); + std::optional onUpdateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless); void onResizeDisplayRequested(qint32 width, qint32 height); void onDestroyDisplayRequested(); void onRelativeMouseModeRequested(bool enabled); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 4f1bab08db..a35214442b 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -26,18 +26,6 @@ #include "pcsx2/GS/GSCapture.h" #include "pcsx2/GS/GSUtil.h" -#ifdef ENABLE_VULKAN -#include "Frontend/VulkanHostDisplay.h" -#endif - -#ifdef _WIN32 -#include "Frontend/D3D11HostDisplay.h" -#include "Frontend/D3D12HostDisplay.h" -#endif -#ifdef __APPLE__ -#include "GS/Renderers/Metal/GSMetalCPPAccessible.h" -#endif - struct RendererInfo { const char* name; @@ -965,37 +953,9 @@ void GraphicsSettingsWidget::updateRendererDependentOptions() m_ui.disableFramebufferFetch->setDisabled(is_sw_dx); // populate adapters - HostDisplay::AdapterAndModeList modes; - switch (type) - { -#ifdef _WIN32 - case GSRendererType::DX11: - modes = D3D11HostDisplay::StaticGetAdapterAndModeList(); - break; - case GSRendererType::DX12: - modes = D3D12HostDisplay::StaticGetAdapterAndModeList(); - break; -#endif - -#ifdef ENABLE_VULKAN - case GSRendererType::VK: - modes = VulkanHostDisplay::StaticGetAdapterAndModeList(nullptr); - break; -#endif - -#ifdef __APPLE__ - case GSRendererType::Metal: - modes = GetMetalAdapterAndModeList(); - break; -#endif - - case GSRendererType::OGL: - case GSRendererType::SW: - case GSRendererType::Null: - case GSRendererType::Auto: - default: - break; - } + std::vector adapters; + std::vector fullscreen_modes; + GSGetAdaptersAndFullscreenModes(type, &adapters, &fullscreen_modes); // fill+select adapters { @@ -1003,7 +963,7 @@ void GraphicsSettingsWidget::updateRendererDependentOptions() std::string current_adapter = Host::GetBaseStringSettingValue("EmuCore/GS", "Adapter", ""); m_ui.adapter->clear(); - m_ui.adapter->setEnabled(!modes.adapter_names.empty()); + m_ui.adapter->setEnabled(!adapters.empty()); m_ui.adapter->addItem(tr("(Default)")); m_ui.adapter->setCurrentIndex(0); @@ -1019,7 +979,7 @@ void GraphicsSettingsWidget::updateRendererDependentOptions() } } - for (const std::string& adapter : modes.adapter_names) + for (const std::string& adapter : adapters) { m_ui.adapter->addItem(QString::fromStdString(adapter)); if (current_adapter == adapter) @@ -1047,7 +1007,7 @@ void GraphicsSettingsWidget::updateRendererDependentOptions() } } - for (const std::string& fs_mode : modes.fullscreen_modes) + for (const std::string& fs_mode : fullscreen_modes) { m_ui.fullscreenModes->addItem(QString::fromStdString(fs_mode)); if (current_mode == fs_mode) diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 3f0656ad60..15d88e7ce0 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -98,7 +98,6 @@ set(pcsx2Sources Gif_Unit.cpp GS.cpp Host.cpp - HostDisplay.cpp Hw.cpp HwRead.cpp HwWrite.cpp @@ -180,7 +179,6 @@ set(pcsx2Headers GS.h Hardware.h Host.h - HostDisplay.h Hw.h IopBios.h IopCounters.h @@ -785,42 +783,7 @@ set(pcsx2FrontendHeaders Frontend/ImGuiOverlays.h ) -if(USE_OPENGL) - list(APPEND pcsx2FrontendSources - Frontend/OpenGLHostDisplay.cpp - Frontend/imgui_impl_opengl3.cpp - ) - list(APPEND pcsx2FrontendHeaders - Frontend/OpenGLHostDisplay.h - Frontend/imgui_impl_opengl3.h - ) -endif() - -if(USE_VULKAN) - list(APPEND pcsx2FrontendSources - Frontend/VulkanHostDisplay.cpp - Frontend/imgui_impl_vulkan.cpp - ) - list(APPEND pcsx2FrontendHeaders - Frontend/imgui_impl_vulkan.h - Frontend/VulkanHostDisplay.h - ) -endif() - -if(WIN32) - list(APPEND pcsx2FrontendSources - Frontend/D3D11HostDisplay.cpp - Frontend/D3D12HostDisplay.cpp - Frontend/imgui_impl_dx11.cpp - Frontend/imgui_impl_dx12.cpp - ) - list(APPEND pcsx2FrontendHeaders - Frontend/D3D11HostDisplay.h - Frontend/D3D12HostDisplay.h - Frontend/imgui_impl_dx11.h - Frontend/imgui_impl_dx12.h - ) -elseif(APPLE) +if(APPLE) list(APPEND pcsx2GSSources GS/Renderers/Metal/GSDeviceMTL.mm GS/Renderers/Metal/GSMTLDeviceInfo.mm @@ -834,12 +797,6 @@ elseif(APPLE) GS/Renderers/Metal/GSMTLShaderCommon.h GS/Renderers/Metal/GSTextureMTL.h ) - list(APPEND pcsx2FrontendSources - Frontend/MetalHostDisplay.mm - ) - list(APPEND pcsx2FrontendHeaders - Frontend/MetalHostDisplay.h - ) endif() list(APPEND pcsx2FrontendSources diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 03c8b7bfcd..ec407e031a 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -25,13 +25,13 @@ #include "IopCounters.h" #include "GS.h" +#include "GS/GS.h" #include "VUmicro.h" #include "PerformanceMetrics.h" #include "Patch.h" #include "ps2/HwInternal.h" #include "Sio.h" -#include "HostDisplay.h" #include "SPU2/spu2.h" #include "PAD/Host/PAD.h" #include "Recording/InputRecording.h" @@ -357,7 +357,7 @@ static double AdjustToHostRefreshRate(double vertical_frequency, double frame_li } float host_refresh_rate; - if (!g_host_display->GetHostRefreshRate(&host_refresh_rate)) + if (!GSGetHostRefreshRate(&host_refresh_rate)) { Console.Warning("Cannot sync to host refresh since the query failed."); SPU2::SetDeviceSampleRateMultiplier(1.0); diff --git a/pcsx2/Frontend/CommonHost.cpp b/pcsx2/Frontend/CommonHost.cpp index 0580dd3404..5d1b95fa90 100644 --- a/pcsx2/Frontend/CommonHost.cpp +++ b/pcsx2/Frontend/CommonHost.cpp @@ -29,7 +29,6 @@ #include "GS.h" #include "GS/Renderers/HW/GSTextureReplacements.h" #include "Host.h" -#include "HostDisplay.h" #include "HostSettings.h" #include "IconsFontAwesome5.h" #include "MemoryCardFile.h" diff --git a/pcsx2/Frontend/CommonHotkeys.cpp b/pcsx2/Frontend/CommonHotkeys.cpp index 5085dff822..75aa753d1a 100644 --- a/pcsx2/Frontend/CommonHotkeys.cpp +++ b/pcsx2/Frontend/CommonHotkeys.cpp @@ -23,7 +23,6 @@ #include "Frontend/InputManager.h" #include "GS.h" #include "Host.h" -#include "HostDisplay.h" #include "IconsFontAwesome5.h" #include "Recording/InputRecording.h" #include "SPU2/spu2.h" diff --git a/pcsx2/Frontend/D3D11HostDisplay.cpp b/pcsx2/Frontend/D3D11HostDisplay.cpp deleted file mode 100644 index 06b8e219fa..0000000000 --- a/pcsx2/Frontend/D3D11HostDisplay.cpp +++ /dev/null @@ -1,740 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "Frontend/D3D11HostDisplay.h" -#include "GS/Renderers/DX11/D3D.h" - -#include "imgui.h" -#include "imgui_impl_dx11.h" -#include -#include - -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/StringUtil.h" -#include "common/RedtapeWindows.h" - -#include "Config.h" - -#pragma comment(lib, "d3d11.lib") -#pragma comment(lib, "dxgi.lib") - -class D3D11HostDisplayTexture : public HostDisplayTexture -{ -public: - D3D11HostDisplayTexture(wil::com_ptr_nothrow texture, wil::com_ptr_nothrow srv, u32 width, - u32 height, bool dynamic) - : m_texture(std::move(texture)) - , m_srv(std::move(srv)) - , m_width(width) - , m_height(height) - , m_dynamic(dynamic) - { - } - ~D3D11HostDisplayTexture() override = default; - - void* GetHandle() const override { return m_srv.get(); } - u32 GetWidth() const override { return m_width; } - u32 GetHeight() const override { return m_height; } - - __fi ID3D11Texture2D* GetD3DTexture() const { return m_texture.get(); } - __fi ID3D11ShaderResourceView* GetD3DSRV() const { return m_srv.get(); } - __fi ID3D11ShaderResourceView* const* GetD3DSRVArray() const { return m_srv.addressof(); } - __fi bool IsDynamic() const { return m_dynamic; } - -private: - wil::com_ptr_nothrow m_texture; - wil::com_ptr_nothrow m_srv; - u32 m_width; - u32 m_height; - bool m_dynamic; -}; - -D3D11HostDisplay::D3D11HostDisplay() = default; - -D3D11HostDisplay::~D3D11HostDisplay() -{ - D3D11HostDisplay::DestroySurface(); - m_context.reset(); - m_device.reset(); -} - -RenderAPI D3D11HostDisplay::GetRenderAPI() const -{ - return RenderAPI::D3D11; -} - -void* D3D11HostDisplay::GetDevice() const -{ - return m_device.get(); -} - -void* D3D11HostDisplay::GetContext() const -{ - return m_context.get(); -} - -void* D3D11HostDisplay::GetSurface() const -{ - return m_swap_chain.get(); -} - -bool D3D11HostDisplay::HasDevice() const -{ - return static_cast(m_device); -} - -bool D3D11HostDisplay::HasSurface() const -{ - return static_cast(m_swap_chain); -} - -std::unique_ptr D3D11HostDisplay::CreateTexture( - u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */) -{ - const CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_R8G8B8A8_UNORM, width, height, 1u, 1u, D3D11_BIND_SHADER_RESOURCE, - dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT, dynamic ? D3D11_CPU_ACCESS_WRITE : 0, 1, 0, 0); - const D3D11_SUBRESOURCE_DATA srd{data, data_stride, data_stride * height}; - ComPtr texture; - HRESULT hr = m_device->CreateTexture2D(&desc, data ? &srd : nullptr, texture.addressof()); - if (FAILED(hr)) - return {}; - - const CD3D11_SHADER_RESOURCE_VIEW_DESC srv_desc(D3D11_SRV_DIMENSION_TEXTURE2D, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 1, 0, 1); - ComPtr srv; - hr = m_device->CreateShaderResourceView(texture.get(), &srv_desc, srv.addressof()); - if (FAILED(hr)) - return {}; - - return std::make_unique(std::move(texture), std::move(srv), width, height, dynamic); -} - -void D3D11HostDisplay::UpdateTexture( - HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) -{ - D3D11HostDisplayTexture* d3d11_texture = static_cast(texture); - if (!d3d11_texture->IsDynamic()) - { - const CD3D11_BOX dst_box(x, y, 0, x + width, y + height, 1); - m_context->UpdateSubresource( - d3d11_texture->GetD3DTexture(), 0, &dst_box, texture_data, texture_data_stride, texture_data_stride * height); - } - else - { - D3D11_MAPPED_SUBRESOURCE sr; - HRESULT hr = m_context->Map(d3d11_texture->GetD3DTexture(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr); - pxAssertMsg(SUCCEEDED(hr), "Failed to map dynamic host display texture"); - - char* dst_ptr = static_cast(sr.pData) + (y * sr.RowPitch) + (x * sizeof(u32)); - const char* src_ptr = static_cast(texture_data); - if (sr.RowPitch == texture_data_stride) - { - std::memcpy(dst_ptr, src_ptr, texture_data_stride * height); - } - else - { - for (u32 row = 0; row < height; row++) - { - std::memcpy(dst_ptr, src_ptr, width * sizeof(u32)); - src_ptr += texture_data_stride; - dst_ptr += sr.RowPitch; - } - } - - m_context->Unmap(d3d11_texture->GetD3DTexture(), 0); - } -} - -bool D3D11HostDisplay::GetHostRefreshRate(float* refresh_rate) -{ - if (m_swap_chain && IsFullscreen()) - { - DXGI_SWAP_CHAIN_DESC desc; - if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && - desc.BufferDesc.RefreshRate.Denominator > 0) - { - DevCon.WriteLn("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); - *refresh_rate = - static_cast(desc.BufferDesc.RefreshRate.Numerator) / static_cast(desc.BufferDesc.RefreshRate.Denominator); - return true; - } - } - - return HostDisplay::GetHostRefreshRate(refresh_rate); -} - -void D3D11HostDisplay::SetVSync(VsyncMode mode) -{ - m_vsync_mode = mode; -} - -bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync) -{ - UINT create_flags = 0; - if (EmuConfig.GS.UseDebugDevice) - create_flags |= D3D11_CREATE_DEVICE_DEBUG; - - m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice); - if (!m_dxgi_factory) - return false; - - ComPtr dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), EmuConfig.GS.Adapter); - - static constexpr std::array requested_feature_levels = { - {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0}}; - - HRESULT hr = D3D11CreateDevice(dxgi_adapter.get(), dxgi_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, nullptr, - create_flags, requested_feature_levels.data(), static_cast(requested_feature_levels.size()), D3D11_SDK_VERSION, - m_device.put(), nullptr, m_context.put()); - - // we re-grab these later, see below - dxgi_adapter.reset(); - - if (FAILED(hr)) - { - Console.Error("Failed to create D3D device: 0x%08X", hr); - return false; - } - - if (EmuConfig.GS.UseDebugDevice && IsDebuggerPresent()) - { - ComPtr info; - if (m_device.try_query_to(&info)) - { - info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE); - info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, TRUE); - } - } - - ComPtr dxgi_device; - if (m_device.try_query_to(&dxgi_device) && SUCCEEDED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.put())))) - Console.WriteLn(fmt::format("D3D Adapter: {}", D3D::GetAdapterName(dxgi_adapter.get()))); - else - Console.Error("Failed to obtain D3D adapter name."); - - 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); - - m_window_info = wi; - m_vsync_mode = vsync; - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) - return false; - - return true; -} - -bool D3D11HostDisplay::SetupDevice() -{ - return true; -} - -bool D3D11HostDisplay::MakeCurrent() -{ - return true; -} - -bool D3D11HostDisplay::DoneCurrent() -{ - return true; -} - -bool D3D11HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) -{ - if (m_window_info.type != WindowInfo::Type::Win32) - return false; - - m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || fullscreen_mode; - - const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); - RECT client_rc{}; - GetClientRect(window_hwnd, &client_rc); - const u32 width = static_cast(client_rc.right - client_rc.left); - const u32 height = static_cast(client_rc.bottom - client_rc.top); - - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; - swap_chain_desc.Width = width; - swap_chain_desc.Height = height; - swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = 3; - 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 && !fullscreen_mode); - if (m_using_allow_tearing) - swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - - DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; - if (fullscreen_mode) - { - swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - swap_chain_desc.Width = fullscreen_mode->Width; - swap_chain_desc.Height = fullscreen_mode->Height; - fs_desc.RefreshRate = fullscreen_mode->RefreshRate; - fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering; - fs_desc.Scaling = fullscreen_mode->Scaling; - fs_desc.Windowed = FALSE; - } - - Console.WriteLn("Creating a %dx%d %s %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height, - m_using_flip_model_swap_chain ? "flip-discard" : "discard", fullscreen_mode ? "full-screen" : "windowed"); - - HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd( - m_device.get(), window_hwnd, &swap_chain_desc, fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); - if (FAILED(hr) && m_using_flip_model_swap_chain) - { - Console.Warning("Failed to create a flip-discard swap chain, trying discard."); - swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - swap_chain_desc.Flags = 0; - 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, fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); - if (FAILED(hr)) - { - Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr); - return false; - } - } - - hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); - if (FAILED(hr)) - Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed"); - - return CreateSwapChainRTV(); -} - -bool D3D11HostDisplay::CreateSwapChainRTV() -{ - ComPtr backbuffer; - HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.put())); - if (FAILED(hr)) - { - Console.Error("GetBuffer for RTV failed: 0x%08X", hr); - return false; - } - - D3D11_TEXTURE2D_DESC backbuffer_desc; - backbuffer->GetDesc(&backbuffer_desc); - - 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.put()); - if (FAILED(hr)) - { - Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr); - return false; - } - - m_window_info.surface_width = backbuffer_desc.Width; - m_window_info.surface_height = backbuffer_desc.Height; - DevCon.WriteLn("Swap chain buffer size: %ux%u", 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))) - { - m_window_info.surface_refresh_rate = - static_cast(desc.BufferDesc.RefreshRate.Numerator) / static_cast(desc.BufferDesc.RefreshRate.Denominator); - } - else - { - m_window_info.surface_refresh_rate = 0.0f; - } - } - - return true; -} - -bool D3D11HostDisplay::ChangeWindow(const WindowInfo& new_wi) -{ - DestroySurface(); - - m_window_info = new_wi; - return CreateSwapChain(nullptr); -} - -void D3D11HostDisplay::DestroySurface() -{ - if (IsFullscreen()) - SetFullscreen(false, 0, 0, 0.0f); - - m_swap_chain_rtv.reset(); - m_swap_chain.reset(); -} - -std::string D3D11HostDisplay::GetDriverInfo() const -{ - std::string ret = "Unknown Feature Level"; - - static constexpr std::array, 4> feature_level_names = {{ - {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"}, - {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_1"}, - {D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"}, - {D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"}, - }}; - - const D3D_FEATURE_LEVEL fl = m_device->GetFeatureLevel(); - for (size_t i = 0; i < std::size(feature_level_names); i++) - { - if (fl == std::get<0>(feature_level_names[i])) - { - ret = std::get<1>(feature_level_names[i]); - break; - } - } - - ret += "\n"; - - ComPtr dxgi_dev; - if (m_device.try_query_to(&dxgi_dev)) - { - ComPtr dxgi_adapter; - if (SUCCEEDED(dxgi_dev->GetAdapter(dxgi_adapter.put()))) - { - DXGI_ADAPTER_DESC desc; - if (SUCCEEDED(dxgi_adapter->GetDesc(&desc))) - { - ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId); - ret += StringUtil::WideStringToUTF8String(desc.Description); - ret += "\n"; - - const std::string driver_version(D3D::GetDriverVersionFromLUID(desc.AdapterLuid)); - if (!driver_version.empty()) - { - ret += "Driver Version: "; - ret += driver_version; - } - } - } - } - - return ret; -} - -void D3D11HostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (!m_swap_chain) - return; - - m_window_info.surface_scale = new_window_scale; - - if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height) - return; - - 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)) - Console.Error("ResizeBuffers() failed: 0x%08X", hr); - - if (!CreateSwapChainRTV()) - pxFailRel("Failed to recreate swap chain RTV after resize"); -} - -bool D3D11HostDisplay::SupportsFullscreen() const -{ - return true; -} - -bool D3D11HostDisplay::IsFullscreen() -{ - BOOL is_fullscreen = FALSE; - return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); -} - -bool D3D11HostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) -{ - if (!m_swap_chain) - return false; - - BOOL is_fullscreen = FALSE; - HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); - if (!fullscreen) - { - // leaving fullscreen - if (is_fullscreen) - return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); - else - return true; - } - - IDXGIOutput* output; - if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) - return false; - - DXGI_SWAP_CHAIN_DESC current_desc; - hr = m_swap_chain->GetDesc(¤t_desc); - if (FAILED(hr)) - return false; - - DXGI_MODE_DESC new_mode = current_desc.BufferDesc; - new_mode.Width = width; - new_mode.Height = height; - new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); - new_mode.RefreshRate.Denominator = 1000u; - - DXGI_MODE_DESC closest_mode; - if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || - new_mode.Format != current_desc.BufferDesc.Format) - { - Console.Error("Failed to find closest matching mode, hr=%08X", hr); - return false; - } - - if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Height && - new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && - new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) - { - DevCon.WriteLn("Fullscreen mode already set"); - return true; - } - - m_swap_chain_rtv.reset(); - m_swap_chain.reset(); - - if (!CreateSwapChain(&closest_mode)) - { - Console.Error("Failed to create a fullscreen swap chain"); - if (!CreateSwapChain(nullptr)) - pxFailRel("Failed to recreate windowed swap chain"); - - return false; - } - - return true; -} - -bool D3D11HostDisplay::CreateImGuiContext() -{ - return ImGui_ImplDX11_Init(m_device.get(), m_context.get()); -} - -void D3D11HostDisplay::DestroyImGuiContext() -{ - ImGui_ImplDX11_Shutdown(); -} - -bool D3D11HostDisplay::UpdateImGuiFontTexture() -{ - ImGui_ImplDX11_CreateFontsTexture(); - return true; -} - -HostDisplay::PresentResult D3D11HostDisplay::BeginPresent(bool frame_skip) -{ - if (frame_skip || !m_swap_chain) - return PresentResult::FrameSkipped; - - // 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 != VsyncMode::Off && m_gpu_timing_enabled) - PopTimestampQuery(); - - static constexpr std::array clear_color = {}; - m_context->ClearRenderTargetView(m_swap_chain_rtv.get(), clear_color.data()); - m_context->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr); - - const CD3D11_VIEWPORT vp(0.0f, 0.0f, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)); - const CD3D11_RECT scissor(0, 0, m_window_info.surface_width, m_window_info.surface_height); - m_context->RSSetViewports(1, &vp); - m_context->RSSetScissorRects(1, &scissor); - return PresentResult::OK; -} - -void D3D11HostDisplay::EndPresent() -{ - ImGui::Render(); - ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); - - // See note in BeginPresent() for why it's conditional on vsync-off. - const bool vsync_on = m_vsync_mode != VsyncMode::Off; - if (!vsync_on && m_gpu_timing_enabled) - PopTimestampQuery(); - - if (!vsync_on && m_using_allow_tearing) - m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); - else - m_swap_chain->Present(static_cast(vsync_on), 0); - - if (m_gpu_timing_enabled) - KickTimestampQuery(); -} - -bool D3D11HostDisplay::CreateTimestampQueries() -{ - for (u32 i = 0; i < NUM_TIMESTAMP_QUERIES; i++) - { - for (u32 j = 0; j < 3; j++) - { - const CD3D11_QUERY_DESC qdesc((j == 0) ? D3D11_QUERY_TIMESTAMP_DISJOINT : D3D11_QUERY_TIMESTAMP); - const HRESULT hr = m_device->CreateQuery(&qdesc, m_timestamp_queries[i][j].put()); - if (FAILED(hr)) - { - m_timestamp_queries = {}; - return false; - } - } - } - - KickTimestampQuery(); - return true; -} - -void D3D11HostDisplay::DestroyTimestampQueries() -{ - if (!m_timestamp_queries[0][0]) - return; - - if (m_timestamp_query_started) - m_context->End(m_timestamp_queries[m_write_timestamp_query][1].get()); - - m_timestamp_queries = {}; - m_read_timestamp_query = 0; - m_write_timestamp_query = 0; - m_waiting_timestamp_queries = 0; - m_timestamp_query_started = 0; -} - -void D3D11HostDisplay::PopTimestampQuery() -{ - while (m_waiting_timestamp_queries > 0) - { - D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; - const HRESULT disjoint_hr = m_context->GetData( - m_timestamp_queries[m_read_timestamp_query][0].get(), &disjoint, sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (disjoint_hr != S_OK) - break; - - if (disjoint.Disjoint) - { - DevCon.WriteLn("GPU timing disjoint, resetting."); - m_read_timestamp_query = 0; - m_write_timestamp_query = 0; - m_waiting_timestamp_queries = 0; - m_timestamp_query_started = 0; - } - else - { - u64 start = 0, end = 0; - const HRESULT start_hr = m_context->GetData( - m_timestamp_queries[m_read_timestamp_query][1].get(), &start, sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH); - const HRESULT end_hr = - m_context->GetData(m_timestamp_queries[m_read_timestamp_query][2].get(), &end, sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH); - if (start_hr == S_OK && end_hr == S_OK) - { - m_accumulated_gpu_time += - static_cast(static_cast(end - start) / (static_cast(disjoint.Frequency) / 1000.0)); - m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; - m_waiting_timestamp_queries--; - } - } - } - - if (m_timestamp_query_started) - { - m_context->End(m_timestamp_queries[m_write_timestamp_query][2].get()); - m_context->End(m_timestamp_queries[m_write_timestamp_query][0].get()); - m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; - m_timestamp_query_started = false; - m_waiting_timestamp_queries++; - } -} - -void D3D11HostDisplay::KickTimestampQuery() -{ - if (m_timestamp_query_started || !m_timestamp_queries[0][0] || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES) - return; - - m_context->Begin(m_timestamp_queries[m_write_timestamp_query][0].get()); - m_context->End(m_timestamp_queries[m_write_timestamp_query][1].get()); - m_timestamp_query_started = true; -} - -bool D3D11HostDisplay::SetGPUTimingEnabled(bool enabled) -{ - if (m_gpu_timing_enabled == enabled) - return true; - - m_gpu_timing_enabled = enabled; - if (m_gpu_timing_enabled) - { - return CreateTimestampQueries(); - } - else - { - DestroyTimestampQueries(); - return true; - } -} - -float D3D11HostDisplay::GetAndResetAccumulatedGPUTime() -{ - const float value = m_accumulated_gpu_time; - m_accumulated_gpu_time = 0.0f; - return value; -} - -HostDisplay::AdapterAndModeList D3D11HostDisplay::StaticGetAdapterAndModeList() -{ - auto factory = D3D::CreateFactory(false); - if (!factory) - return {}; - - return GetAdapterAndModeList(factory.get()); -} - -HostDisplay::AdapterAndModeList D3D11HostDisplay::GetAdapterAndModeList(IDXGIFactory5* dxgi_factory) -{ - AdapterAndModeList adapter_info; - adapter_info.adapter_names = D3D::GetAdapterNames(dxgi_factory); - - auto adapter = D3D::GetChosenOrFirstAdapter(dxgi_factory, EmuConfig.GS.Adapter); - if (adapter) - { - ComPtr output; - if (SUCCEEDED(adapter->EnumOutputs(0, &output))) - { - UINT num_modes = 0; - if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr))) - { - std::vector modes(num_modes); - if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, modes.data()))) - { - for (const DXGI_MODE_DESC& mode : modes) - { - adapter_info.fullscreen_modes.push_back(GetFullscreenModeString(mode.Width, mode.Height, - static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); - } - } - } - } - } - - return adapter_info; -} - -HostDisplay::AdapterAndModeList D3D11HostDisplay::GetAdapterAndModeList() -{ - return GetAdapterAndModeList(m_dxgi_factory.get()); -} diff --git a/pcsx2/Frontend/D3D11HostDisplay.h b/pcsx2/Frontend/D3D11HostDisplay.h deleted file mode 100644 index b66112f658..0000000000 --- a/pcsx2/Frontend/D3D11HostDisplay.h +++ /dev/null @@ -1,113 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once -#include "HostDisplay.h" -#include "common/RedtapeWindows.h" -#include "common/RedtapeWilCom.h" -#include "common/WindowInfo.h" -#include -#include -#include -#include -#include -#include -#include - -class D3D11HostDisplay final : public HostDisplay -{ -public: - template - using ComPtr = wil::com_ptr_nothrow; - - D3D11HostDisplay(); - ~D3D11HostDisplay(); - - RenderAPI GetRenderAPI() const override; - void* GetDevice() const override; - void* GetContext() const override; - void* GetSurface() const override; - - bool HasDevice() const override; - bool HasSurface() const override; - - bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override; - bool SetupDevice() override; - - bool MakeCurrent() override; - bool DoneCurrent() override; - - bool ChangeWindow(const WindowInfo& new_wi) override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - bool SupportsFullscreen() const override; - bool IsFullscreen() override; - bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; - AdapterAndModeList GetAdapterAndModeList() override; - void DestroySurface() override; - std::string GetDriverInfo() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - - bool GetHostRefreshRate(float* refresh_rate) override; - - void SetVSync(VsyncMode mode) override; - - PresentResult BeginPresent(bool frame_skip) override; - void EndPresent() override; - - bool SetGPUTimingEnabled(bool enabled) override; - float GetAndResetAccumulatedGPUTime() override; - - static AdapterAndModeList StaticGetAdapterAndModeList(); - -protected: - static constexpr u32 DISPLAY_CONSTANT_BUFFER_SIZE = 16; - static constexpr u8 NUM_TIMESTAMP_QUERIES = 5; - - static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory5* dxgi_factory); - - bool CreateImGuiContext() override; - void DestroyImGuiContext() override; - bool UpdateImGuiFontTexture() override; - - bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); - bool CreateSwapChainRTV(); - - bool CreateTimestampQueries(); - void DestroyTimestampQueries(); - void PopTimestampQuery(); - void KickTimestampQuery(); - - ComPtr m_device; - ComPtr m_context; - - ComPtr m_dxgi_factory; - ComPtr m_swap_chain; - ComPtr m_swap_chain_rtv; - - bool m_allow_tearing_supported = false; - bool m_using_flip_model_swap_chain = true; - bool m_using_allow_tearing = false; - - std::array, 3>, NUM_TIMESTAMP_QUERIES> m_timestamp_queries = {}; - u8 m_read_timestamp_query = 0; - u8 m_write_timestamp_query = 0; - u8 m_waiting_timestamp_queries = 0; - bool m_timestamp_query_started = false; - float m_accumulated_gpu_time = 0.0f; - bool m_gpu_timing_enabled = false; -}; - diff --git a/pcsx2/Frontend/D3D12HostDisplay.cpp b/pcsx2/Frontend/D3D12HostDisplay.cpp deleted file mode 100644 index 70c4bd9299..0000000000 --- a/pcsx2/Frontend/D3D12HostDisplay.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "Frontend/D3D12HostDisplay.h" -#include "GS/Renderers/DX11/D3D.h" - -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/D3D12/Context.h" -#include "common/D3D12/Util.h" -#include "common/StringUtil.h" - -#include "imgui.h" -#include "imgui_impl_dx12.h" -#include -#include - -class D3D12HostDisplayTexture : public HostDisplayTexture -{ -public: - explicit D3D12HostDisplayTexture(D3D12::Texture texture) - : m_texture(std::move(texture)) - { - } - ~D3D12HostDisplayTexture() override = default; - - void* GetHandle() const override { return const_cast(&m_texture); } - u32 GetWidth() const override { return m_texture.GetWidth(); } - u32 GetHeight() const override { return m_texture.GetHeight(); } - - const D3D12::Texture& GetTexture() const { return m_texture; } - D3D12::Texture& GetTexture() { return m_texture; } - -private: - D3D12::Texture m_texture; -}; - -D3D12HostDisplay::D3D12HostDisplay() = default; - -D3D12HostDisplay::~D3D12HostDisplay() -{ - if (g_d3d12_context) - { - g_d3d12_context->WaitForGPUIdle(); - D3D12HostDisplay::DestroySurface(); - g_d3d12_context->Destroy(); - } -} - -RenderAPI D3D12HostDisplay::GetRenderAPI() const -{ - return RenderAPI::D3D12; -} - -void* D3D12HostDisplay::GetDevice() const -{ - return g_d3d12_context->GetDevice(); -} - -void* D3D12HostDisplay::GetContext() const -{ - return g_d3d12_context.get(); -} - -void* D3D12HostDisplay::GetSurface() const -{ - return m_swap_chain.get(); -} - -bool D3D12HostDisplay::HasDevice() const -{ - return static_cast(g_d3d12_context); -} - -bool D3D12HostDisplay::HasSurface() const -{ - return static_cast(m_swap_chain); -} - -std::unique_ptr D3D12HostDisplay::CreateTexture( - u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */) -{ - D3D12::Texture tex; - if (!tex.Create(width, height, 1, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, - D3D12_RESOURCE_FLAG_NONE)) - { - return {}; - } - - if (data && !tex.LoadData(g_d3d12_context->GetInitCommandList(), 0, 0, 0, width, height, data, data_stride)) - return {}; - - return std::make_unique(std::move(tex)); -} - -void D3D12HostDisplay::UpdateTexture( - HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) -{ - static_cast(texture)->GetTexture().LoadData( - g_d3d12_context->GetCommandList(), 0, x, y, width, height, texture_data, texture_data_stride); -} - -bool D3D12HostDisplay::GetHostRefreshRate(float* refresh_rate) -{ - if (m_swap_chain && IsFullscreen()) - { - DXGI_SWAP_CHAIN_DESC desc; - if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && - desc.BufferDesc.RefreshRate.Denominator > 0) - { - DevCon.WriteLn("using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); - *refresh_rate = - static_cast(desc.BufferDesc.RefreshRate.Numerator) / static_cast(desc.BufferDesc.RefreshRate.Denominator); - return true; - } - } - - return HostDisplay::GetHostRefreshRate(refresh_rate); -} - -void D3D12HostDisplay::SetVSync(VsyncMode mode) -{ - m_vsync_mode = mode; -} - -bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync) -{ - m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice); - if (!m_dxgi_factory) - return false; - - ComPtr dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), EmuConfig.GS.Adapter); - - if (!D3D12::Context::Create(m_dxgi_factory.get(), dxgi_adapter.get(), EmuConfig.GS.UseDebugDevice)) - return false; - - 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_window_info = wi; - m_vsync_mode = vsync; - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) - return false; - - return true; -} - -bool D3D12HostDisplay::SetupDevice() -{ - return true; -} - -bool D3D12HostDisplay::MakeCurrent() -{ - return true; -} - -bool D3D12HostDisplay::DoneCurrent() -{ - return true; -} - -bool D3D12HostDisplay::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) -{ - if (m_window_info.type != WindowInfo::Type::Win32) - return false; - - const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); - RECT client_rc{}; - GetClientRect(window_hwnd, &client_rc); - const u32 width = static_cast(client_rc.right - client_rc.left); - const u32 height = static_cast(client_rc.bottom - client_rc.top); - - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; - swap_chain_desc.Width = width; - swap_chain_desc.Height = height; - swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = 3; - 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 && !fullscreen_mode); - if (m_using_allow_tearing) - swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - - DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; - if (fullscreen_mode) - { - swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - swap_chain_desc.Width = fullscreen_mode->Width; - swap_chain_desc.Height = fullscreen_mode->Height; - fs_desc.RefreshRate = fullscreen_mode->RefreshRate; - fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering; - fs_desc.Scaling = fullscreen_mode->Scaling; - fs_desc.Windowed = FALSE; - } - - DevCon.WriteLn( - "Creating a %dx%d %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height, fullscreen_mode ? "full-screen" : "windowed"); - - HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd, &swap_chain_desc, - fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); - if (FAILED(hr)) - { - Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr); - return false; - } - - hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); - if (FAILED(hr)) - Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed"); - - return CreateSwapChainRTV(); -} - -bool D3D12HostDisplay::CreateSwapChainRTV() -{ - DXGI_SWAP_CHAIN_DESC swap_chain_desc; - HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc); - if (FAILED(hr)) - return false; - - for (u32 i = 0; i < swap_chain_desc.BufferCount; i++) - { - ComPtr backbuffer; - hr = m_swap_chain->GetBuffer(i, IID_PPV_ARGS(backbuffer.put())); - if (FAILED(hr)) - { - Console.Error("GetBuffer for RTV failed: 0x%08X", hr); - return false; - } - - D3D12::Texture tex; - if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format, DXGI_FORMAT_UNKNOWN, - D3D12_RESOURCE_STATE_PRESENT)) - { - return false; - } - - m_swap_chain_buffers.push_back(std::move(tex)); - } - - m_window_info.surface_width = swap_chain_desc.BufferDesc.Width; - m_window_info.surface_height = swap_chain_desc.BufferDesc.Height; - DevCon.WriteLn("Swap chain buffer size: %ux%u", 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))) - { - m_window_info.surface_refresh_rate = - static_cast(desc.BufferDesc.RefreshRate.Numerator) / static_cast(desc.BufferDesc.RefreshRate.Denominator); - } - else - { - m_window_info.surface_refresh_rate = 0.0f; - } - } - - m_current_swap_chain_buffer = 0; - return true; -} - -void D3D12HostDisplay::DestroySwapChainRTVs() -{ - for (D3D12::Texture& buffer : m_swap_chain_buffers) - buffer.Destroy(false); - m_swap_chain_buffers.clear(); - m_current_swap_chain_buffer = 0; -} - -bool D3D12HostDisplay::ChangeWindow(const WindowInfo& new_wi) -{ - DestroySurface(); - - m_window_info = new_wi; - return CreateSwapChain(nullptr); -} - -void D3D12HostDisplay::DestroySurface() -{ - // For some reason if we don't execute the command list here, the swap chain is in use.. not sure where. - g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep); - - if (IsFullscreen()) - SetFullscreen(false, 0, 0, 0.0f); - - DestroySwapChainRTVs(); - m_swap_chain.reset(); -} - -std::string D3D12HostDisplay::GetDriverInfo() const -{ - std::string ret = "Unknown Feature Level"; - - static constexpr std::array, 4> feature_level_names = {{ - {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"}, - {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_1"}, - {D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"}, - {D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"}, - }}; - - const D3D_FEATURE_LEVEL fl = g_d3d12_context->GetFeatureLevel(); - for (size_t i = 0; i < std::size(feature_level_names); i++) - { - if (fl == std::get<0>(feature_level_names[i])) - { - ret = std::get<1>(feature_level_names[i]); - break; - } - } - - ret += "\n"; - - IDXGIAdapter* adapter = g_d3d12_context->GetAdapter(); - DXGI_ADAPTER_DESC desc; - if (adapter && SUCCEEDED(adapter->GetDesc(&desc))) - { - ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId); - ret += StringUtil::WideStringToUTF8String(desc.Description); - ret += "\n"; - - const std::string driver_version(D3D::GetDriverVersionFromLUID(desc.AdapterLuid)); - if (!driver_version.empty()) - { - ret += "Driver Version: "; - ret += driver_version; - } - } - - return ret; -} - -void D3D12HostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (!m_swap_chain) - return; - - m_window_info.surface_scale = new_window_scale; - - if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height) - return; - - // For some reason if we don't execute the command list here, the swap chain is in use.. not sure where. - g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep); - - DestroySwapChainRTVs(); - - 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)) - Console.Error("ResizeBuffers() failed: 0x%08X", hr); - - if (!CreateSwapChainRTV()) - pxFailRel("Failed to recreate swap chain RTV after resize"); -} - -bool D3D12HostDisplay::SupportsFullscreen() const -{ - return true; -} - -bool D3D12HostDisplay::IsFullscreen() -{ - BOOL is_fullscreen = FALSE; - return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); -} - -bool D3D12HostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) -{ - if (!m_swap_chain) - return false; - - BOOL is_fullscreen = FALSE; - HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); - if (!fullscreen) - { - // leaving fullscreen - if (is_fullscreen) - return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); - else - return true; - } - - IDXGIOutput* output; - if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) - return false; - - DXGI_SWAP_CHAIN_DESC current_desc; - hr = m_swap_chain->GetDesc(¤t_desc); - if (FAILED(hr)) - return false; - - DXGI_MODE_DESC new_mode = current_desc.BufferDesc; - new_mode.Width = width; - new_mode.Height = height; - new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); - new_mode.RefreshRate.Denominator = 1000u; - - DXGI_MODE_DESC closest_mode; - if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || - new_mode.Format != current_desc.BufferDesc.Format) - { - Console.Error("Failed to find closest matching mode, hr=%08X", hr); - return false; - } - - if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Width && - new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && - new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) - { - DevCon.WriteLn("Fullscreen mode already set"); - return true; - } - - g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep); - DestroySwapChainRTVs(); - m_swap_chain.reset(); - - if (!CreateSwapChain(&closest_mode)) - { - Console.Error("Failed to create a fullscreen swap chain"); - if (!CreateSwapChain(nullptr)) - pxFailRel("Failed to recreate windowed swap chain"); - - return false; - } - - return true; -} - -HostDisplay::AdapterAndModeList D3D12HostDisplay::GetAdapterAndModeList() -{ - return GetAdapterAndModeList(m_dxgi_factory.get()); -} - -bool D3D12HostDisplay::CreateImGuiContext() -{ - ImGui::GetIO().DisplaySize.x = static_cast(m_window_info.surface_width); - ImGui::GetIO().DisplaySize.y = static_cast(m_window_info.surface_height); - - if (!ImGui_ImplDX12_Init(DXGI_FORMAT_R8G8B8A8_UNORM)) - return false; - - return true; -} - -void D3D12HostDisplay::DestroyImGuiContext() -{ - g_d3d12_context->WaitForGPUIdle(); - - ImGui_ImplDX12_Shutdown(); -} - -bool D3D12HostDisplay::UpdateImGuiFontTexture() -{ - return ImGui_ImplDX12_CreateFontsTexture(); -} - -HostDisplay::PresentResult D3D12HostDisplay::BeginPresent(bool frame_skip) -{ - if (m_device_lost) - return HostDisplay::PresentResult::DeviceLost; - - if (frame_skip || !m_swap_chain) - return PresentResult::FrameSkipped; - - static constexpr std::array clear_color = {}; - D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; - - ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList(); - swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET); - cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), clear_color.data(), 0, nullptr); - cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetWriteDescriptor().cpu_handle, FALSE, nullptr); - - const D3D12_VIEWPORT vp{ - 0.0f, 0.0f, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height), 0.0f, 1.0f}; - const D3D12_RECT scissor{0, 0, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)}; - cmdlist->RSSetViewports(1, &vp); - cmdlist->RSSetScissorRects(1, &scissor); - return PresentResult::OK; -} - -void D3D12HostDisplay::EndPresent() -{ - ImGui::Render(); - ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData()); - - D3D12::Texture& 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(m_swap_chain_buffers.size())); - - swap_chain_buf.TransitionToState(g_d3d12_context->GetCommandList(), D3D12_RESOURCE_STATE_PRESENT); - if (!g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None)) - { - m_device_lost = true; - return; - } - - const bool vsync = static_cast(m_vsync_mode != VsyncMode::Off); - if (!vsync && m_using_allow_tearing) - m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); - else - m_swap_chain->Present(static_cast(vsync), 0); -} - -bool D3D12HostDisplay::SetGPUTimingEnabled(bool enabled) -{ - g_d3d12_context->SetEnableGPUTiming(enabled); - return true; -} - -float D3D12HostDisplay::GetAndResetAccumulatedGPUTime() -{ - return g_d3d12_context->GetAndResetAccumulatedGPUTime(); -} - -HostDisplay::AdapterAndModeList D3D12HostDisplay::StaticGetAdapterAndModeList() -{ - auto factory = D3D::CreateFactory(false); - if (!factory) - return {}; - - return GetAdapterAndModeList(factory.get()); -} - -HostDisplay::AdapterAndModeList D3D12HostDisplay::GetAdapterAndModeList(IDXGIFactory5* dxgi_factory) -{ - AdapterAndModeList adapter_info; - adapter_info.adapter_names = D3D::GetAdapterNames(dxgi_factory); - - auto adapter = D3D::GetChosenOrFirstAdapter(dxgi_factory, EmuConfig.GS.Adapter); - if (adapter) - { - ComPtr output; - if (SUCCEEDED(adapter->EnumOutputs(0, &output))) - { - UINT num_modes = 0; - if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr))) - { - std::vector modes(num_modes); - if (SUCCEEDED(output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, modes.data()))) - { - for (const DXGI_MODE_DESC& mode : modes) - { - adapter_info.fullscreen_modes.push_back(GetFullscreenModeString(mode.Width, mode.Height, - static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); - } - } - } - } - } - - return adapter_info; -} diff --git a/pcsx2/Frontend/D3D12HostDisplay.h b/pcsx2/Frontend/D3D12HostDisplay.h deleted file mode 100644 index 23bfd9455b..0000000000 --- a/pcsx2/Frontend/D3D12HostDisplay.h +++ /dev/null @@ -1,101 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "common/Pcsx2Defs.h" -#include "common/RedtapeWindows.h" - -#include "common/D3D12/DescriptorHeapManager.h" -#include "common/D3D12/Texture.h" -#include "common/WindowInfo.h" - -#include "HostDisplay.h" - -#include -#include -#include -#include -#include -#include -#include - -class D3D12HostDisplay : public HostDisplay -{ -public: - template - using ComPtr = wil::com_ptr_nothrow; - - D3D12HostDisplay(); - ~D3D12HostDisplay(); - - RenderAPI GetRenderAPI() const override; - void* GetDevice() const override; - void* GetContext() const override; - void* GetSurface() const override; - - bool HasDevice() const override; - bool HasSurface() const override; - - bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override; - bool SetupDevice() override; - - bool MakeCurrent() override; - bool DoneCurrent() override; - - bool ChangeWindow(const WindowInfo& new_wi) override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - bool SupportsFullscreen() const override; - bool IsFullscreen() override; - bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; - AdapterAndModeList GetAdapterAndModeList() override; - void DestroySurface() override; - std::string GetDriverInfo() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - - bool GetHostRefreshRate(float* refresh_rate) override; - - void SetVSync(VsyncMode mode) override; - - PresentResult BeginPresent(bool frame_skip) override; - void EndPresent() override; - - bool SetGPUTimingEnabled(bool enabled) override; - float GetAndResetAccumulatedGPUTime() override; - - static AdapterAndModeList StaticGetAdapterAndModeList(); - -protected: - static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory5* dxgi_factory); - - bool CreateImGuiContext() override; - void DestroyImGuiContext() override; - bool UpdateImGuiFontTexture() override; - - bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); - bool CreateSwapChainRTV(); - void DestroySwapChainRTVs(); - - ComPtr m_dxgi_factory; - ComPtr m_swap_chain; - std::vector 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_device_lost = false; -}; diff --git a/pcsx2/Frontend/FullscreenUI.cpp b/pcsx2/Frontend/FullscreenUI.cpp index 6b0360b019..a07dae86ca 100644 --- a/pcsx2/Frontend/FullscreenUI.cpp +++ b/pcsx2/Frontend/FullscreenUI.cpp @@ -37,8 +37,9 @@ #include "CDVD/CDVDcommon.h" #include "CDVD/CDVDdiscReader.h" #include "GS.h" +#include "GS/Renderers/Common/GSDevice.h" +#include "GS/Renderers/Common/GSTexture.h" #include "Host.h" -#include "HostDisplay.h" #include "HostSettings.h" #include "INISettingsInterface.h" #include "MemoryCardFile.h" @@ -196,6 +197,7 @@ namespace FullscreenUI ////////////////////////////////////////////////////////////////////////// // Utility ////////////////////////////////////////////////////////////////////////// + static void ReleaseTexture(std::unique_ptr& tex); static std::string TimeToPrintableString(time_t t); static void StartAsyncOp(std::function callback, std::string name); static void AsyncOpThreadEntryPoint(std::function callback, FullscreenUI::ProgressCallback* progress); @@ -244,12 +246,12 @@ namespace FullscreenUI static bool LoadResources(); static void DestroyResources(); - static std::shared_ptr s_app_icon_texture; - static std::array, static_cast(GameDatabaseSchema::Compatibility::Perfect)> + static std::shared_ptr s_app_icon_texture; + static std::array, static_cast(GameDatabaseSchema::Compatibility::Perfect)> s_game_compatibility_textures; - static std::shared_ptr s_fallback_disc_texture; - static std::shared_ptr s_fallback_exe_texture; - static std::vector> s_cleanup_textures; + static std::shared_ptr s_fallback_disc_texture; + static std::shared_ptr s_fallback_exe_texture; + static std::vector> s_cleanup_textures; ////////////////////////////////////////////////////////////////////////// // Landing @@ -397,7 +399,7 @@ namespace FullscreenUI std::string title; std::string summary; std::string path; - std::unique_ptr preview_texture; + std::unique_ptr preview_texture; time_t timestamp; s32 slot; }; @@ -433,9 +435,9 @@ namespace FullscreenUI static void DrawGameListSettingsPage(const ImVec2& heading_size); static void SwitchToGameList(); static void PopulateGameListEntryList(); - static HostDisplayTexture* GetTextureForGameListEntryType(GameList::EntryType type); - static HostDisplayTexture* GetGameListCover(const GameList::Entry* entry); - static HostDisplayTexture* GetCoverForCurrentGame(); + static GSTexture* GetTextureForGameListEntryType(GameList::EntryType type); + static GSTexture* GetGameListCover(const GameList::Entry* entry); + static GSTexture* GetCoverForCurrentGame(); // Lazily populated cover images. static std::unordered_map s_cover_image_map; @@ -465,6 +467,12 @@ namespace FullscreenUI // Utility ////////////////////////////////////////////////////////////////////////// +void FullscreenUI::ReleaseTexture(std::unique_ptr& tex) +{ + if (tex) + g_gs_device->Recycle(tex.release()); +} + std::string FullscreenUI::TimeToPrintableString(time_t t) { struct tm lt = {}; @@ -765,8 +773,8 @@ void FullscreenUI::Render() if (!s_initialized) return; - for (std::unique_ptr& tex : s_cleanup_textures) - tex.reset(); + for (std::unique_ptr& tex : s_cleanup_textures) + g_gs_device->Recycle(tex.release()); s_cleanup_textures.clear(); ImGuiFullscreen::UploadAsyncTextures(); @@ -883,7 +891,7 @@ void FullscreenUI::DestroyResources() for (auto& tex : s_game_compatibility_textures) tex.reset(); for (auto& tex : s_cleanup_textures) - tex.reset(); + g_gs_device->Recycle(tex.release()); } ////////////////////////////////////////////////////////////////////////// @@ -1089,7 +1097,7 @@ void FullscreenUI::DrawLandingWindow() const float image_size = LayoutScale(380.f); ImGui::SetCursorPos( ImVec2((ImGui::GetWindowWidth() * 0.5f) - (image_size * 0.5f), (ImGui::GetWindowHeight() * 0.5f) - (image_size * 0.5f))); - ImGui::Image(s_app_icon_texture->GetHandle(), ImVec2(image_size, image_size)); + ImGui::Image(s_app_icon_texture->GetNativeHandle(), ImVec2(image_size, image_size)); } EndFullscreenColumnWindow(); @@ -2360,10 +2368,7 @@ void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry) void FullscreenUI::PopulateGraphicsAdapterList() { - HostDisplay::AdapterAndModeList ml(g_host_display->GetAdapterAndModeList()); - s_graphics_adapter_list_cache = std::move(ml.adapter_names); - s_fullscreen_mode_list_cache = std::move(ml.fullscreen_modes); - s_fullscreen_mode_list_cache.insert(s_fullscreen_mode_list_cache.begin(), "Borderless Fullscreen"); + GSGetAdaptersAndFullscreenModes(GSConfig.Renderer, &s_graphics_adapter_list_cache, &s_fullscreen_mode_list_cache); } void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si) @@ -3179,7 +3184,8 @@ void FullscreenUI::DrawGraphicsSettingsPage() static constexpr const char* s_round_sprite_options[] = {"Off (Default)", "Half", "Full"}; DrawIntListSetting(bsi, "CRC Fix Level", "Applies manual fixes to difficult-to-emulate effects in the hardware renderers.", - "EmuCore/GS", "crc_hack_level", static_cast(CRCHackLevel::Automatic), s_crc_fix_options, std::size(s_crc_fix_options), -1); + "EmuCore/GS", "crc_hack_level", static_cast(CRCHackLevel::Automatic), s_crc_fix_options, std::size(s_crc_fix_options), + -1); DrawIntListSetting(bsi, "Half-Bottom Override", "Control the half-screen fix detection on texture shuffling.", "EmuCore/GS", "UserHacks_Half_Bottom_Override", -1, s_generic_options, std::size(s_generic_options), -1); DrawIntListSetting(bsi, "CPU Sprite Render Size", "Uses software renderer to draw texture decompression-like sprites.", @@ -3310,7 +3316,8 @@ void FullscreenUI::DrawGraphicsSettingsPage() "EmuCore/GS", "SkipDuplicateFrames", false); DrawToggleSetting(bsi, "Disable Threaded Presentation", "Presents frames on a worker thread, instead of on the GS thread. Can improve frame times on some systems, at the cost of " - "potentially worse frame pacing.", "EmuCore/GS", "DisableThreadedPresentation", false); + "potentially worse frame pacing.", + "EmuCore/GS", "DisableThreadedPresentation", false); if (hw_fixes_visible) { DrawIntListSetting(bsi, "Hardware Download Mode", "Changes synchronization behavior for GS downloads.", "EmuCore/GS", @@ -3743,9 +3750,8 @@ void FullscreenUI::DrawControllerSettingsPage() DrawToggleSetting(bsi, ICON_FA_WIFI " SDL DualShock 4 / DualSense Enhanced Mode", "Provides vibration and LED control support over Bluetooth.", "InputSources", "SDLControllerEnhancedMode", false, bsi->GetBoolValue("InputSources", "SDL", true), false); - DrawToggleSetting(bsi, ICON_FA_COG " SDL Raw Input", - "Allow SDL to use raw access to input devices.", "InputSources", "SDLRawInput", false, - bsi->GetBoolValue("InputSources", "SDL", true), false); + DrawToggleSetting(bsi, ICON_FA_COG " SDL Raw Input", "Allow SDL to use raw access to input devices.", "InputSources", "SDLRawInput", + false, bsi->GetBoolValue("InputSources", "SDL", true), false); #endif #ifdef _WIN32 DrawToggleSetting(bsi, ICON_FA_COG " Enable XInput Input Source", @@ -4317,13 +4323,13 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) #endif - HostDisplayTexture* const cover = GetCoverForCurrentGame(); + GSTexture* const cover = GetCoverForCurrentGame(); const ImVec2 image_min( display_size.x - LayoutScale(10.0f + image_width) - rp_height, display_size.y - LayoutScale(10.0f + image_height) - rp_height); const ImVec2 image_max(image_min.x + LayoutScale(image_width) + rp_height, image_min.y + LayoutScale(image_height) + rp_height); const ImRect image_rect(CenterImage( ImRect(image_min, image_max), ImVec2(static_cast(cover->GetWidth()), static_cast(cover->GetHeight())))); - dl->AddImage(cover->GetHandle(), image_rect.Min, image_rect.Max); + dl->AddImage(cover->GetNativeHandle(), image_rect.Min, image_rect.Max); } // current time / play time @@ -4549,10 +4555,14 @@ bool FullscreenUI::InitializeSaveStateListEntry( std::vector screenshot_pixels; if (SaveState_ReadScreenshot(li->path, &screenshot_width, &screenshot_height, &screenshot_pixels)) { - li->preview_texture = g_host_display->CreateTexture( - screenshot_width, screenshot_height, screenshot_pixels.data(), sizeof(u32) * screenshot_width, false); - if (!li->preview_texture) + li->preview_texture = + std::unique_ptr(g_gs_device->CreateTexture(screenshot_width, screenshot_height, 1, GSTexture::Format::Color)); + if (!li->preview_texture || !li->preview_texture->Update(GSVector4i(0, 0, screenshot_width, screenshot_height), + screenshot_pixels.data(), sizeof(u32) * screenshot_width)) + { Console.Error("Failed to upload save state image to GPU"); + ReleaseTexture(li->preview_texture); + } } return true; @@ -4849,12 +4859,11 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading) bb.Min += style.FramePadding; bb.Max -= style.FramePadding; - const HostDisplayTexture* const screenshot = - entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); + const GSTexture* const screenshot = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast(screenshot->GetWidth()), static_cast(screenshot->GetHeight())))); - ImGui::GetWindowDrawList()->AddImage(screenshot->GetHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), + ImGui::GetWindowDrawList()->AddImage(screenshot->GetNativeHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); const ImVec2 title_pos(bb.Min.x, bb.Min.y + image_height + title_spacing); @@ -4956,14 +4965,14 @@ void FullscreenUI::DrawResumeStateSelector() ImGui::TextWrapped("A resume save state created at %s was found.\n\nDo you want to load this save and continue?", TimeToPrintableString(entry.timestamp).c_str()); - const HostDisplayTexture* image = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); + const GSTexture* image = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); const float image_height = LayoutScale(250.0f); const float image_width = image_height * (static_cast(image->GetWidth()) / static_cast(image->GetHeight())); const ImVec2 pos(ImGui::GetCursorScreenPos() + ImVec2((ImGui::GetCurrentWindow()->WorkRect.GetWidth() - image_width) * 0.5f, LayoutScale(20.0f))); const ImRect image_bb(pos, pos + ImVec2(image_width, image_height)); - ImGui::GetWindowDrawList()->AddImage( - static_cast(entry.preview_texture ? entry.preview_texture->GetHandle() : GetPlaceholderTexture()->GetHandle()), + ImGui::GetWindowDrawList()->AddImage(static_cast(entry.preview_texture ? entry.preview_texture->GetNativeHandle() : + GetPlaceholderTexture()->GetNativeHandle()), image_bb.Min, image_bb.Max); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + image_height + LayoutScale(40.0f)); @@ -5226,7 +5235,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (!visible) continue; - HostDisplayTexture* cover_texture = GetGameListCover(entry); + GSTexture* cover_texture = GetGameListCover(entry); summary.clear(); if (entry->serial.empty()) @@ -5240,7 +5249,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); - ImGui::GetWindowDrawList()->AddImage(cover_texture->GetHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), + ImGui::GetWindowDrawList()->AddImage(cover_texture->GetNativeHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f); @@ -5280,7 +5289,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (BeginFullscreenColumnWindow(-530.0f, 0.0f, "game_list_info", UIPrimaryDarkColor)) { - const HostDisplayTexture* cover_texture = + const GSTexture* cover_texture = selected_entry ? GetGameListCover(selected_entry) : GetTextureForGameListEntryType(GameList::EntryType::Count); if (cover_texture) { @@ -5288,8 +5297,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); ImGui::SetCursorPos(LayoutScale(ImVec2(128.0f, 20.0f)) + image_rect.Min); - ImGui::Image(selected_entry ? GetGameListCover(selected_entry)->GetHandle() : - GetTextureForGameListEntryType(GameList::EntryType::Count)->GetHandle(), + ImGui::Image(selected_entry ? GetGameListCover(selected_entry)->GetNativeHandle() : + GetTextureForGameListEntryType(GameList::EntryType::Count)->GetNativeHandle(), image_rect.GetSize()); } @@ -5337,7 +5346,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) std::string flag_texture(fmt::format("icons/flags/{}.png", GameList::RegionToString(selected_entry->region))); ImGui::TextUnformatted("Region: "); ImGui::SameLine(); - ImGui::Image(GetCachedTextureAsync(flag_texture.c_str())->GetHandle(), LayoutScale(23.0f, 16.0f)); + ImGui::Image(GetCachedTextureAsync(flag_texture.c_str())->GetNativeHandle(), LayoutScale(23.0f, 16.0f)); ImGui::SameLine(); ImGui::Text(" (%s)", GameList::RegionToString(selected_entry->region)); } @@ -5347,7 +5356,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImGui::SameLine(); if (selected_entry->compatibility_rating != GameDatabaseSchema::Compatibility::Unknown) { - ImGui::Image(s_game_compatibility_textures[static_cast(selected_entry->compatibility_rating) - 1]->GetHandle(), + ImGui::Image(s_game_compatibility_textures[static_cast(selected_entry->compatibility_rating) - 1]->GetNativeHandle(), LayoutScale(64.0f, 16.0f)); ImGui::SameLine(); } @@ -5451,11 +5460,11 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) bb.Min += style.FramePadding; bb.Max -= style.FramePadding; - const HostDisplayTexture* const cover_texture = GetGameListCover(entry); + const GSTexture* const cover_texture = GetGameListCover(entry); const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size), ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); - ImGui::GetWindowDrawList()->AddImage(cover_texture->GetHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), + ImGui::GetWindowDrawList()->AddImage(cover_texture->GetNativeHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); @@ -5777,7 +5786,7 @@ void FullscreenUI::SwitchToGameList() QueueResetFocus(); } -HostDisplayTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry) +GSTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry) { // lookup and grab cover image auto cover_it = s_cover_image_map.find(entry->path); @@ -5787,11 +5796,11 @@ HostDisplayTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry) cover_it = s_cover_image_map.emplace(entry->path, std::move(cover_path)).first; } - HostDisplayTexture* tex = (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr; + GSTexture* tex = (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr; return tex ? tex : GetTextureForGameListEntryType(entry->type); } -HostDisplayTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type) +GSTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type) { switch (type) { @@ -5805,7 +5814,7 @@ HostDisplayTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::Entry } } -HostDisplayTexture* FullscreenUI::GetCoverForCurrentGame() +GSTexture* FullscreenUI::GetCoverForCurrentGame() { auto lock = GameList::GetLock(); @@ -6064,11 +6073,11 @@ void FullscreenUI::DrawAchievement(const Achievements::Achievement& cheevo) const std::string& badge_path = Achievements::GetAchievementBadgePath(cheevo); if (!badge_path.empty()) { - HostDisplayTexture* badge = GetCachedTextureAsync(badge_path.c_str()); + GSTexture* badge = GetCachedTextureAsync(badge_path.c_str()); if (badge) { - ImGui::GetWindowDrawList()->AddImage( - badge->GetHandle(), bb.Min, bb.Min + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + ImGui::GetWindowDrawList()->AddImage(badge->GetNativeHandle(), bb.Min, bb.Min + image_size, ImVec2(0.0f, 0.0f), + ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); } } @@ -6183,11 +6192,11 @@ void FullscreenUI::DrawAchievementsWindow() const std::string& icon_path = Achievements::GetGameIcon(); if (!icon_path.empty()) { - HostDisplayTexture* badge = GetCachedTexture(icon_path.c_str()); + GSTexture* badge = GetCachedTexture(icon_path.c_str()); if (badge) { ImGui::GetWindowDrawList()->AddImage( - badge->GetHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + badge->GetNativeHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); } } @@ -6318,12 +6327,12 @@ void FullscreenUI::DrawPrimedAchievementsIcons() if (badge_path.empty()) return true; - HostDisplayTexture* badge = GetCachedTextureAsync(badge_path.c_str()); + GSTexture* badge = GetCachedTextureAsync(badge_path.c_str()); if (!badge) return true; ImDrawList* dl = ImGui::GetBackgroundDrawList(); - dl->AddImage(badge->GetHandle(), position, position + image_size); + dl->AddImage(badge->GetNativeHandle(), position, position + image_size); position.x -= x_advance; return true; }); @@ -6367,12 +6376,12 @@ void FullscreenUI::DrawPrimedAchievementsList() if (badge_path.empty()) return true; - HostDisplayTexture* badge = GetCachedTextureAsync(badge_path.c_str()); + GSTexture* badge = GetCachedTextureAsync(badge_path.c_str()); if (!badge) return true; ImDrawList* dl = ImGui::GetBackgroundDrawList(); - dl->AddImage(badge->GetHandle(), position, position + image_size); + dl->AddImage(badge->GetNativeHandle(), position, position + image_size); const char* achievement_title = achievement.title.c_str(); const char* achievement_tile_end = achievement_title + achievement.title.length(); @@ -6580,11 +6589,11 @@ void FullscreenUI::DrawLeaderboardsWindow() const std::string& icon_path = Achievements::GetGameIcon(); if (!icon_path.empty()) { - HostDisplayTexture* badge = GetCachedTexture(icon_path.c_str()); + GSTexture* badge = GetCachedTexture(icon_path.c_str()); if (badge) { ImGui::GetWindowDrawList()->AddImage( - badge->GetHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + badge->GetNativeHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); } } diff --git a/pcsx2/Frontend/FullscreenUI.h b/pcsx2/Frontend/FullscreenUI.h index 4dd708a4b9..47362406f6 100644 --- a/pcsx2/Frontend/FullscreenUI.h +++ b/pcsx2/Frontend/FullscreenUI.h @@ -19,8 +19,6 @@ #include #include -class HostDisplayTexture; - struct Pcsx2Config; namespace FullscreenUI diff --git a/pcsx2/Frontend/ImGuiFullscreen.cpp b/pcsx2/Frontend/ImGuiFullscreen.cpp index ab783ab91a..2ccc1c4dd1 100644 --- a/pcsx2/Frontend/ImGuiFullscreen.cpp +++ b/pcsx2/Frontend/ImGuiFullscreen.cpp @@ -29,7 +29,9 @@ #include "common/Threading.h" #include "common/Timer.h" #include "fmt/core.h" -#include "HostDisplay.h" +#include "Host.h" +#include "GS/Renderers/Common/GSDevice.h" +#include "GS/Renderers/Common/GSTexture.h" #include "imgui_internal.h" #include "imgui_stdlib.h" #include @@ -43,7 +45,7 @@ namespace ImGuiFullscreen using MessageDialogCallbackVariant = std::variant; static std::optional LoadTextureImage(const char* path); - static std::shared_ptr UploadTexture(const char* path, const Common::RGBA8Image& image); + static std::shared_ptr UploadTexture(const char* path, const Common::RGBA8Image& image); static void TextureLoaderThread(); static void DrawFileSelector(); @@ -89,8 +91,8 @@ namespace ImGuiFullscreen static u32 s_close_button_state = 0; static bool s_focus_reset_queued = false; - static LRUCache> s_texture_cache(128, true); - static std::shared_ptr s_placeholder_texture; + static LRUCache> s_texture_cache(128, true); + static std::shared_ptr s_placeholder_texture; static std::atomic_bool s_texture_load_thread_quit{false}; static std::mutex s_texture_load_mutex; static std::condition_variable s_texture_load_cv; @@ -247,7 +249,7 @@ void ImGuiFullscreen::Shutdown(bool clear_state) } } -const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() +const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() { return s_placeholder_texture; } @@ -278,26 +280,32 @@ std::optional ImGuiFullscreen::LoadTextureImage(const char* return image; } -std::shared_ptr ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image) +std::shared_ptr ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image) { - std::unique_ptr texture = - g_host_display->CreateTexture(image.GetWidth(), image.GetHeight(), image.GetPixels(), image.GetByteStride()); + GSTexture* texture = g_gs_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, GSTexture::Format::Color); if (!texture) { Console.Error("failed to create %ux%u texture for resource", image.GetWidth(), image.GetHeight()); return {}; } + if (!texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetByteStride())) + { + Console.Error("Failed to upload %ux%u texture for resource", image.GetWidth(), image.GetHeight()); + g_gs_device->Recycle(texture); + return {}; + } + DevCon.WriteLn("Uploaded texture resource '%s' (%ux%u)", path, image.GetWidth(), image.GetHeight()); - return std::shared_ptr(std::move(texture)); + return std::shared_ptr(texture, [](GSTexture* tex) { g_gs_device->Recycle(tex); }); } -std::shared_ptr ImGuiFullscreen::LoadTexture(const char* path) +std::shared_ptr ImGuiFullscreen::LoadTexture(const char* path) { std::optional image(LoadTextureImage(path)); if (image.has_value()) { - std::shared_ptr ret(UploadTexture(path, image.value())); + std::shared_ptr ret(UploadTexture(path, image.value())); if (ret) return ret; } @@ -305,21 +313,21 @@ std::shared_ptr ImGuiFullscreen::LoadTexture(const char* pat return s_placeholder_texture; } -HostDisplayTexture* ImGuiFullscreen::GetCachedTexture(const char* name) +GSTexture* ImGuiFullscreen::GetCachedTexture(const char* name) { - std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); if (!tex_ptr) { - std::shared_ptr tex(LoadTexture(name)); + std::shared_ptr tex(LoadTexture(name)); tex_ptr = s_texture_cache.Insert(name, std::move(tex)); } return tex_ptr->get(); } -HostDisplayTexture* ImGuiFullscreen::GetCachedTextureAsync(const char* name) +GSTexture* ImGuiFullscreen::GetCachedTextureAsync(const char* name) { - std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); if (!tex_ptr) { // insert the placeholder @@ -348,7 +356,7 @@ void ImGuiFullscreen::UploadAsyncTextures() s_texture_upload_queue.pop_front(); lock.unlock(); - std::shared_ptr tex = UploadTexture(it.first.c_str(), it.second); + std::shared_ptr tex = UploadTexture(it.first.c_str(), it.second); if (tex) s_texture_cache.Insert(std::move(it.first), std::move(tex)); @@ -2378,9 +2386,9 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size); if (!notif.badge_path.empty()) { - HostDisplayTexture* tex = GetCachedTexture(notif.badge_path.c_str()); + GSTexture* tex = GetCachedTexture(notif.badge_path.c_str()); if (tex) - dl->AddImage(static_cast(tex->GetHandle()), badge_min, badge_max); + dl->AddImage(tex->GetNativeHandle(), badge_min, badge_max); } const ImVec2 title_min(badge_max.x + horizontal_spacing, box_min.y + vertical_padding); diff --git a/pcsx2/Frontend/ImGuiFullscreen.h b/pcsx2/Frontend/ImGuiFullscreen.h index 5b8788e60b..fb43371db7 100644 --- a/pcsx2/Frontend/ImGuiFullscreen.h +++ b/pcsx2/Frontend/ImGuiFullscreen.h @@ -25,7 +25,7 @@ #include #include -class HostDisplayTexture; +class GSTexture; namespace ImGuiFullscreen { @@ -116,10 +116,10 @@ namespace ImGuiFullscreen void Shutdown(bool clear_state); /// Texture cache. - const std::shared_ptr& GetPlaceholderTexture(); - std::shared_ptr LoadTexture(const char* path); - HostDisplayTexture* GetCachedTexture(const char* name); - HostDisplayTexture* GetCachedTextureAsync(const char* name); + const std::shared_ptr& GetPlaceholderTexture(); + std::shared_ptr LoadTexture(const char* path); + GSTexture* GetCachedTexture(const char* name); + GSTexture* GetCachedTextureAsync(const char* name); bool InvalidateCachedTexture(const std::string& path); void UploadAsyncTextures(); diff --git a/pcsx2/Frontend/ImGuiManager.cpp b/pcsx2/Frontend/ImGuiManager.cpp index 309dae6d47..6daf5a26b3 100644 --- a/pcsx2/Frontend/ImGuiManager.cpp +++ b/pcsx2/Frontend/ImGuiManager.cpp @@ -37,8 +37,8 @@ #include "Frontend/InputManager.h" #include "GS.h" #include "GS/GS.h" +#include "GS/Renderers/Common/GSDevice.h" #include "Host.h" -#include "HostDisplay.h" #include "IconsFontAwesome5.h" #include "PerformanceMetrics.h" #include "Recording/InputRecording.h" @@ -90,40 +90,32 @@ bool ImGuiManager::Initialize() return false; } - s_global_scale = std::max(0.5f, g_host_display->GetWindowScale() * (EmuConfig.GS.OsdScale / 100.0f)); + s_global_scale = std::max(0.5f, g_gs_device->GetWindowScale() * (GSConfig.OsdScale / 100.0f)); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.IniFilename = nullptr; - io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_HasGamepad; io.BackendUsingLegacyKeyArrays = 0; io.BackendUsingLegacyNavInputArray = 0; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling - io.DisplaySize.x = static_cast(g_host_display->GetWindowWidth()); - io.DisplaySize.y = static_cast(g_host_display->GetWindowHeight()); + io.DisplaySize.x = static_cast(g_gs_device->GetWindowWidth()); + io.DisplaySize.y = static_cast(g_gs_device->GetWindowHeight()); SetKeyMap(); SetStyle(); const bool add_fullscreen_fonts = s_fullscreen_ui_was_initialized; pxAssertRel(!FullscreenUI::IsInitialized(), "Fullscreen UI is not initialized on ImGui init"); + if (add_fullscreen_fonts) + ImGuiFullscreen::UpdateLayoutScale(); - if (!g_host_display->CreateImGuiContext()) + if (!AddImGuiFonts(add_fullscreen_fonts) || !g_gs_device->UpdateImGuiFontTexture()) { - pxFailRel("Failed to create ImGui device context"); - g_host_display->DestroyImGuiContext(); - ImGui::DestroyContext(); - UnloadFontData(); - return false; - } - - if (!AddImGuiFonts(add_fullscreen_fonts) || !g_host_display->UpdateImGuiFontTexture()) - { - pxFailRel("Failed to create ImGui font text"); - g_host_display->DestroyImGuiContext(); + Host::ReportErrorAsync("ImGuiManager", "Failed to create ImGui font text"); ImGui::DestroyContext(); UnloadFontData(); return false; @@ -143,7 +135,7 @@ bool ImGuiManager::Initialize() bool ImGuiManager::InitializeFullscreenUI() { - s_fullscreen_ui_was_initialized = FullscreenUI::Initialize(); + s_fullscreen_ui_was_initialized = !ImGui::GetCurrentContext() || FullscreenUI::Initialize(); return s_fullscreen_ui_was_initialized; } @@ -154,8 +146,6 @@ void ImGuiManager::Shutdown(bool clear_state) if (clear_state) s_fullscreen_ui_was_initialized = false; - if (g_host_display) - g_host_display->DestroyImGuiContext(); if (ImGui::GetCurrentContext()) ImGui::DestroyContext(); @@ -170,8 +160,8 @@ void ImGuiManager::Shutdown(bool clear_state) void ImGuiManager::WindowResized() { - const u32 new_width = g_host_display ? g_host_display->GetWindowWidth() : 0; - const u32 new_height = g_host_display ? g_host_display->GetWindowHeight() : 0; + const u32 new_width = g_gs_device ? g_gs_device->GetWindowWidth() : 0; + const u32 new_height = g_gs_device ? g_gs_device->GetWindowHeight() : 0; ImGui::GetIO().DisplaySize = ImVec2(static_cast(new_width), static_cast(new_height)); @@ -184,7 +174,7 @@ void ImGuiManager::WindowResized() void ImGuiManager::UpdateScale() { - const float window_scale = g_host_display ? g_host_display->GetWindowScale() : 1.0f; + const float window_scale = g_gs_device ? g_gs_device->GetWindowScale() : 1.0f; const float scale = std::max(window_scale * (EmuConfig.GS.OsdScale / 100.0f), 0.5f); if (scale == s_global_scale && (!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale())) @@ -199,7 +189,7 @@ void ImGuiManager::UpdateScale() if (!AddImGuiFonts(HasFullscreenFonts())) pxFailRel("Failed to create ImGui font text"); - if (!g_host_display->UpdateImGuiFontTexture()) + if (!g_gs_device->UpdateImGuiFontTexture()) pxFailRel("Failed to recreate font texture after scale+resize"); NewFrame(); @@ -483,7 +473,7 @@ bool ImGuiManager::AddFullscreenFontsIfMissing() AddImGuiFonts(false); } - g_host_display->UpdateImGuiFontTexture(); + g_gs_device->UpdateImGuiFontTexture(); NewFrame(); return HasFullscreenFonts(); diff --git a/pcsx2/Frontend/ImGuiOverlays.cpp b/pcsx2/Frontend/ImGuiOverlays.cpp index 1ec8309d41..752ccb2da2 100644 --- a/pcsx2/Frontend/ImGuiOverlays.cpp +++ b/pcsx2/Frontend/ImGuiOverlays.cpp @@ -43,7 +43,6 @@ #include "GS/GSCapture.h" #include "GS/GSVector.h" #include "Host.h" -#include "HostDisplay.h" #include "IconsFontAwesome5.h" #include "PerformanceMetrics.h" #include "PAD/Host/PAD.h" diff --git a/pcsx2/Frontend/MetalHostDisplay.h b/pcsx2/Frontend/MetalHostDisplay.h deleted file mode 100644 index 27e010675c..0000000000 --- a/pcsx2/Frontend/MetalHostDisplay.h +++ /dev/null @@ -1,99 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "HostDisplay.h" - -#ifndef __OBJC__ - #error "This header is for use with Objective-C++ only. -#endif - -#ifdef __APPLE__ - -#include "GS/Renderers/Metal/GSMTLDeviceInfo.h" -#include -#include -#include -#include - -class MetalHostDisplay final : public HostDisplay -{ - enum class UsePresentDrawable : u8 - { - Never = 0, - Always = 1, - IfVsync = 2, - }; - MRCOwned m_view; - MRCOwned m_layer; - GSMTLDevice m_dev; - MRCOwned> m_queue; - MRCOwned> m_font_tex; - MRCOwned> m_current_drawable; - MRCOwned m_pass_desc; - u32 m_capture_start_frame; - UsePresentDrawable m_use_present_drawable; - bool m_gpu_timing_enabled = false; - double m_accumulated_gpu_time = 0; - double m_last_gpu_time_end = 0; - std::mutex m_mtx; - - void AttachSurfaceOnMainThread(); - void DetachSurfaceOnMainThread(); - -public: - MetalHostDisplay(); - ~MetalHostDisplay(); - RenderAPI GetRenderAPI() const override; - void* GetDevice() const override; - void* GetContext() const override; - void* GetSurface() const override; - - bool HasDevice() const override; - bool HasSurface() const override; - bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override; - bool SetupDevice() override; - bool MakeCurrent() override; - bool DoneCurrent() override; - void DestroySurface() override; - bool ChangeWindow(const WindowInfo& wi) override; - bool SupportsFullscreen() const override; - bool IsFullscreen() override; - bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; - AdapterAndModeList GetAdapterAndModeList() override; - std::string GetDriverInfo() const override; - - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override; - void UpdateTexture(id texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride); - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override; - PresentResult BeginPresent(bool frame_skip) override; - void EndPresent() override; - void SetVSync(VsyncMode mode) override; - - bool CreateImGuiContext() override; - void DestroyImGuiContext() override; - bool UpdateImGuiFontTexture() override; - - bool GetHostRefreshRate(float* refresh_rate) override; - - bool SetGPUTimingEnabled(bool enabled) override; - float GetAndResetAccumulatedGPUTime() override; - void AccumulateCommandBufferTime(id buffer); -}; - -#endif diff --git a/pcsx2/Frontend/MetalHostDisplay.mm b/pcsx2/Frontend/MetalHostDisplay.mm deleted file mode 100644 index 4bacd7a9d7..0000000000 --- a/pcsx2/Frontend/MetalHostDisplay.mm +++ /dev/null @@ -1,467 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2022 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" -#include "MetalHostDisplay.h" -#include "GS/Renderers/Metal/GSMetalCPPAccessible.h" -#include "GS/Renderers/Metal/GSDeviceMTL.h" -#include - -#ifdef __APPLE__ - -class MetalHostDisplayTexture final : public HostDisplayTexture -{ - MRCOwned> m_tex; - u32 m_width, m_height; -public: - MetalHostDisplayTexture(MRCOwned> tex, u32 width, u32 height) - : m_tex(std::move(tex)) - , m_width(width) - , m_height(height) - { - } - - void* GetHandle() const override { return (__bridge void*)m_tex; }; - u32 GetWidth() const override { return m_width; } - u32 GetHeight() const override { return m_height; } -}; - -HostDisplay* MakeMetalHostDisplay() -{ - return new MetalHostDisplay(); -} - -MetalHostDisplay::MetalHostDisplay() -{ -} - -MetalHostDisplay::~MetalHostDisplay() -{ - MetalHostDisplay::DestroySurface(); - m_queue = nullptr; - m_dev.Reset(); -} - -HostDisplay::AdapterAndModeList GetMetalAdapterAndModeList() -{ @autoreleasepool { - HostDisplay::AdapterAndModeList list; - auto devs = MRCTransfer(MTLCopyAllDevices()); - for (id dev in devs.Get()) - list.adapter_names.push_back([[dev name] UTF8String]); - return list; -}} - -template -static void OnMainThread(Fn&& fn) -{ - if ([NSThread isMainThread]) - fn(); - else - dispatch_sync(dispatch_get_main_queue(), fn); -} - -RenderAPI MetalHostDisplay::GetRenderAPI() const -{ - return RenderAPI::Metal; -} - -void* MetalHostDisplay::GetDevice() const { return const_cast(static_cast(&m_dev)); } -void* MetalHostDisplay::GetContext() const { return (__bridge void*)m_queue; } -void* MetalHostDisplay::GetSurface() const { return (__bridge void*)m_layer; } -bool MetalHostDisplay::HasDevice() const { return m_dev.IsOk(); } -bool MetalHostDisplay::HasSurface() const { return static_cast(m_layer);} - -void MetalHostDisplay::AttachSurfaceOnMainThread() -{ - ASSERT([NSThread isMainThread]); - m_layer = MRCRetain([CAMetalLayer layer]); - [m_layer setDrawableSize:CGSizeMake(m_window_info.surface_width, m_window_info.surface_height)]; - [m_layer setDevice:m_dev.dev]; - m_view = MRCRetain((__bridge NSView*)m_window_info.window_handle); - [m_view setWantsLayer:YES]; - [m_view setLayer:m_layer]; -} - -void MetalHostDisplay::DetachSurfaceOnMainThread() -{ - ASSERT([NSThread isMainThread]); - [m_view setLayer:nullptr]; - [m_view setWantsLayer:NO]; - m_view = nullptr; - m_layer = nullptr; -} - -bool MetalHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync) -{ @autoreleasepool { - m_window_info = wi; - pxAssertRel(!m_dev.dev, "Device already created!"); - NSString* ns_adapter_name = [NSString stringWithUTF8String:EmuConfig.GS.Adapter.c_str()]; - auto devs = MRCTransfer(MTLCopyAllDevices()); - for (id dev in devs.Get()) - { - if ([[dev name] isEqualToString:ns_adapter_name]) - m_dev = GSMTLDevice(MRCRetain(dev)); - } - if (!m_dev.dev) - { - if (!EmuConfig.GS.Adapter.empty()) - Console.Warning("Metal: Couldn't find adapter %s, using default", EmuConfig.GS.Adapter.c_str()); - m_dev = GSMTLDevice(MRCTransfer(MTLCreateSystemDefaultDevice())); - if (!m_dev.dev) - Host::ReportErrorAsync("No Metal Devices Available", "No Metal-supporting GPUs were found. PCSX2 requires a Metal GPU (available on all macs from 2012 onwards)."); - } - m_queue = MRCTransfer([m_dev.dev newCommandQueue]); - - m_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]); - [m_pass_desc colorAttachments][0].loadAction = MTLLoadActionClear; - [m_pass_desc colorAttachments][0].clearColor = MTLClearColorMake(0, 0, 0, 0); - [m_pass_desc colorAttachments][0].storeAction = MTLStoreActionStore; - - if (char* env = getenv("MTL_USE_PRESENT_DRAWABLE")) - m_use_present_drawable = static_cast(atoi(env)); - else if (@available(macOS 13.0, *)) - m_use_present_drawable = UsePresentDrawable::Always; - else // Before Ventura, presentDrawable acts like vsync is on when windowed - m_use_present_drawable = UsePresentDrawable::IfVsync; - - m_capture_start_frame = 0; - if (char* env = getenv("MTL_CAPTURE")) - { - m_capture_start_frame = atoi(env); - } - if (m_capture_start_frame) - { - Console.WriteLn("Metal will capture frame %u", m_capture_start_frame); - } - - if (m_dev.IsOk() && m_queue) - { - OnMainThread([this] - { - AttachSurfaceOnMainThread(); - }); - SetVSync(vsync); - return true; - } - else - return false; -}} - -bool MetalHostDisplay::SetupDevice() -{ - return true; -} - -bool MetalHostDisplay::MakeCurrent() { return true; } -bool MetalHostDisplay::DoneCurrent() { return true; } - -void MetalHostDisplay::DestroySurface() -{ - if (!m_layer) - return; - OnMainThread([this]{ DetachSurfaceOnMainThread(); }); - m_layer = nullptr; -} - -bool MetalHostDisplay::ChangeWindow(const WindowInfo& wi) -{ - OnMainThread([this, &wi] - { - DetachSurfaceOnMainThread(); - m_window_info = wi; - AttachSurfaceOnMainThread(); - }); - return true; -} - -bool MetalHostDisplay::SupportsFullscreen() const { return false; } -bool MetalHostDisplay::IsFullscreen() { return false; } -bool MetalHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) { return false; } - -HostDisplay::AdapterAndModeList MetalHostDisplay::GetAdapterAndModeList() -{ - return GetMetalAdapterAndModeList(); -} - -std::string MetalHostDisplay::GetDriverInfo() const -{ @autoreleasepool { - std::string desc([[m_dev.dev description] UTF8String]); - desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported"); - desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported"); - desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported"); - desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported"); - desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version)); - desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize); - return desc; -}} - -void MetalHostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - m_window_info.surface_scale = new_window_scale; - if (m_window_info.surface_width == static_cast(new_window_width) && m_window_info.surface_height == static_cast(new_window_height)) - return; - m_window_info.surface_width = new_window_width; - m_window_info.surface_height = new_window_height; - @autoreleasepool - { - [m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)]; - } -} - -std::unique_ptr MetalHostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic) -{ @autoreleasepool { - MTLTextureDescriptor* desc = [MTLTextureDescriptor - texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm - width:width - height:height - mipmapped:false]; - [desc setUsage:MTLTextureUsageShaderRead]; - [desc setStorageMode:MTLStorageModePrivate]; - MRCOwned> tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]); - if (!tex) - return nullptr; // Something broke yay - [tex setLabel:@"MetalHostDisplay Texture"]; - if (data) - UpdateTexture(tex, 0, 0, width, height, data, data_stride); - return std::make_unique(std::move(tex), width, height); -}} - -void MetalHostDisplay::UpdateTexture(id texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) -{ - id cmdbuf = [m_queue commandBuffer]; - id enc = [cmdbuf blitCommandEncoder]; - size_t bytes = data_stride * height; - MRCOwned> buf = MRCTransfer([m_dev.dev newBufferWithLength:bytes options:MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined]); - memcpy([buf contents], data, bytes); - [enc copyFromBuffer:buf - sourceOffset:0 - sourceBytesPerRow:data_stride - sourceBytesPerImage:bytes - sourceSize:MTLSizeMake(width, height, 1) - toTexture:texture - destinationSlice:0 - destinationLevel:0 - destinationOrigin:MTLOriginMake(0, 0, 0)]; - [enc endEncoding]; - [cmdbuf commit]; -} - -void MetalHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) -{ @autoreleasepool { - UpdateTexture((__bridge id)texture->GetHandle(), x, y, width, height, data, data_stride); -}} - -static bool s_capture_next = false; - -HostDisplay::PresentResult MetalHostDisplay::BeginPresent(bool frame_skip) -{ @autoreleasepool { - GSDeviceMTL* dev = static_cast(g_gs_device.get()); - if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame) - s_capture_next = true; - if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device) - { - ImGui::EndFrame(); - return PresentResult::FrameSkipped; - } - id buf = dev->GetRenderCmdBuf(); - m_current_drawable = MRCRetain([m_layer nextDrawable]); - dev->EndRenderPass(); - if (!m_current_drawable) - { - [buf pushDebugGroup:@"Present Skipped"]; - [buf popDebugGroup]; - dev->FlushEncoders(); - ImGui::EndFrame(); - return PresentResult::FrameSkipped; - } - [m_pass_desc colorAttachments][0].texture = [m_current_drawable texture]; - id enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc]; - [enc setLabel:@"Present"]; - dev->m_current_render.encoder = MRCRetain(enc); - return PresentResult::OK; -}} - -void MetalHostDisplay::EndPresent() -{ @autoreleasepool { - GSDeviceMTL* dev = static_cast(g_gs_device.get()); - pxAssertDev(dev && dev->m_current_render.encoder && dev->m_current_render_cmdbuf, "BeginPresent cmdbuf was destroyed"); - ImGui::Render(); - dev->RenderImGui(ImGui::GetDrawData()); - dev->EndRenderPass(); - if (m_current_drawable) - { - const bool use_present_drawable = m_use_present_drawable == UsePresentDrawable::Always || - (m_use_present_drawable == UsePresentDrawable::IfVsync && m_vsync_mode != VsyncMode::Off); - - if (use_present_drawable) - [dev->m_current_render_cmdbuf presentDrawable:m_current_drawable]; - else - [dev->m_current_render_cmdbuf addScheduledHandler:[drawable = std::move(m_current_drawable)](id){ - [drawable present]; - }]; - } - dev->FlushEncoders(); - dev->FrameCompleted(); - m_current_drawable = nullptr; - if (m_capture_start_frame) - { - if (@available(macOS 10.15, iOS 13, *)) - { - static NSString* const path = @"/tmp/PCSX2MTLCapture.gputrace"; - static u32 frames; - if (frames) - { - --frames; - if (!frames) - { - [[MTLCaptureManager sharedCaptureManager] stopCapture]; - Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace finished"); - [[NSWorkspace sharedWorkspace] selectFile:path - inFileViewerRootedAtPath:@"/tmp/"]; - } - } - else if (s_capture_next) - { - s_capture_next = false; - MTLCaptureManager* mgr = [MTLCaptureManager sharedCaptureManager]; - if ([mgr supportsDestination:MTLCaptureDestinationGPUTraceDocument]) - { - MTLCaptureDescriptor* desc = [[MTLCaptureDescriptor new] autorelease]; - [desc setCaptureObject:m_dev.dev]; - if ([[NSFileManager defaultManager] fileExistsAtPath:path]) - [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; - [desc setOutputURL:[NSURL fileURLWithPath:path]]; - [desc setDestination:MTLCaptureDestinationGPUTraceDocument]; - NSError* err = nullptr; - [mgr startCaptureWithDescriptor:desc error:&err]; - if (err) - { - Console.Error("Metal Trace Capture failed: %s", [[err localizedDescription] UTF8String]); - } - else - { - Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace started"); - frames = 2; - } - } - else - { - Console.Error("Metal Trace Capture Failed: MTLCaptureManager doesn't support GPU trace documents! (Did you forget to run with METAL_CAPTURE_ENABLED=1?)"); - } - } - } - } -}} - -void MetalHostDisplay::SetVSync(VsyncMode mode) -{ - [m_layer setDisplaySyncEnabled:mode != VsyncMode::Off]; - m_vsync_mode = mode; -} - -bool MetalHostDisplay::CreateImGuiContext() -{ - ImGuiIO& io = ImGui::GetIO(); - io.BackendRendererName = "pcsx2_imgui_metal"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - return true; -} - -void MetalHostDisplay::DestroyImGuiContext() -{ - ImGui::GetIO().Fonts->SetTexID(nullptr); -} - -bool MetalHostDisplay::UpdateImGuiFontTexture() -{ @autoreleasepool { - u8* data; - int width, height; - ImFontAtlas* fonts = ImGui::GetIO().Fonts; - fonts->GetTexDataAsAlpha8(&data, &width, &height); - MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatA8Unorm width:width height:height mipmapped:false]; - [desc setUsage:MTLTextureUsageShaderRead]; - [desc setStorageMode:MTLStorageModePrivate]; - if (@available(macOS 10.15, *)) - if (m_dev.features.texture_swizzle) - [desc setSwizzle:MTLTextureSwizzleChannelsMake(MTLTextureSwizzleOne, MTLTextureSwizzleOne, MTLTextureSwizzleOne, MTLTextureSwizzleAlpha)]; - m_font_tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]); - [m_font_tex setLabel:@"ImGui Font"]; - UpdateTexture(m_font_tex, 0, 0, width, height, data, width); - fonts->SetTexID((__bridge void*)m_font_tex); - return static_cast(m_font_tex); -}} - -bool MetalHostDisplay::GetHostRefreshRate(float* refresh_rate) -{ - OnMainThread([this, refresh_rate] - { - u32 did = [[[[[m_view window] screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue]; - if (CGDisplayModeRef mode = CGDisplayCopyDisplayMode(did)) - { - *refresh_rate = CGDisplayModeGetRefreshRate(mode); - CGDisplayModeRelease(mode); - } - else - { - *refresh_rate = 0; - } - }); - return *refresh_rate != 0; -} - -bool MetalHostDisplay::SetGPUTimingEnabled(bool enabled) -{ - if (enabled == m_gpu_timing_enabled) - return true; - if (@available(macOS 10.15, iOS 10.3, *)) - { - std::lock_guard l(m_mtx); - m_gpu_timing_enabled = enabled; - m_accumulated_gpu_time = 0; - m_last_gpu_time_end = 0; - return true; - } - return false; -} - -float MetalHostDisplay::GetAndResetAccumulatedGPUTime() -{ - std::lock_guard l(m_mtx); - float time = m_accumulated_gpu_time * 1000; - m_accumulated_gpu_time = 0; - return time; -} - -void MetalHostDisplay::AccumulateCommandBufferTime(id buffer) -{ - std::lock_guard l(m_mtx); - if (!m_gpu_timing_enabled) - return; - // We do the check before enabling m_gpu_timing_enabled -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - // It's unlikely, but command buffers can overlap or run out of order - // This doesn't handle every case (fully out of order), but it should at least handle overlapping - double begin = std::max(m_last_gpu_time_end, [buffer GPUStartTime]); - double end = [buffer GPUEndTime]; - if (end > begin) - { - m_accumulated_gpu_time += end - begin; - m_last_gpu_time_end = end; - } -#pragma clang diagnostic pop -} - -#endif // __APPLE__ diff --git a/pcsx2/Frontend/OpenGLHostDisplay.cpp b/pcsx2/Frontend/OpenGLHostDisplay.cpp deleted file mode 100644 index 4ce0a8c15b..0000000000 --- a/pcsx2/Frontend/OpenGLHostDisplay.cpp +++ /dev/null @@ -1,487 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "OpenGLHostDisplay.h" -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/ScopedGuard.h" -#include "common/StringUtil.h" -#include "common/GL/Program.h" -#include "imgui.h" -#include "imgui_impl_opengl3.h" -#include -#include - -class OpenGLHostDisplayTexture : public HostDisplayTexture -{ -public: - OpenGLHostDisplayTexture(GLuint texture, u32 width, u32 height) - : m_texture(texture) - , m_width(width) - , m_height(height) - { - } - ~OpenGLHostDisplayTexture() override = default; - - void* GetHandle() const override { return reinterpret_cast(static_cast(m_texture)); } - u32 GetWidth() const override { return m_width; } - u32 GetHeight() const override { return m_height; } - - GLuint GetGLID() const { return m_texture; } - -private: - GLuint m_texture; - u32 m_width; - u32 m_height; -}; - -OpenGLHostDisplay::OpenGLHostDisplay() = default; - -OpenGLHostDisplay::~OpenGLHostDisplay() -{ - if (m_gl_context) - { - m_gl_context->DoneCurrent(); - m_gl_context.reset(); - } -} - -RenderAPI OpenGLHostDisplay::GetRenderAPI() const -{ - return m_gl_context->IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL; -} - -void* OpenGLHostDisplay::GetDevice() const -{ - return nullptr; -} - -void* OpenGLHostDisplay::GetContext() const -{ - return m_gl_context.get(); -} - -void* OpenGLHostDisplay::GetSurface() const -{ - return nullptr; -} - -std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */) -{ - // clear error - glGetError(); - - // don't worry, I'm planning on removing this eventually - we'll use GSTexture instead. - glActiveTexture(GL_TEXTURE7); - - GLuint id; - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - - if (GLAD_GL_ARB_texture_storage || GLAD_GL_ES_VERSION_3_0) - { - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); - } - else - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - } - - glActiveTexture(GL_TEXTURE0); - - GLenum error = glGetError(); - if (error != GL_NO_ERROR) - { - Console.Error("Failed to create texture: 0x%X", error); - glDeleteTextures(1, &id); - return nullptr; - } - - return std::make_unique(id, width, height); -} - -void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) -{ - glActiveTexture(GL_TEXTURE7); - - glPixelStorei(GL_UNPACK_ROW_LENGTH, texture_data_stride / sizeof(u32)); - - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA8, GL_UNSIGNED_BYTE, texture_data); - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - glActiveTexture(GL_TEXTURE0); -} - -void OpenGLHostDisplay::SetVSync(VsyncMode mode) -{ - if (m_vsync_mode == mode || m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless) - return; - - // Window framebuffer has to be bound to call SetSwapInterval. - GLint current_fbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) - m_gl_context->SetSwapInterval(static_cast(mode != VsyncMode::Off)); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - m_vsync_mode = mode; -} - -const char* OpenGLHostDisplay::GetGLSLVersionString() const -{ - if (GetRenderAPI() == RenderAPI::OpenGLES) - { - if (GLAD_GL_ES_VERSION_3_0) - return "#version 300 es"; - else - return "#version 100"; - } - else - { - if (GLAD_GL_VERSION_3_3) - return "#version 330"; - else - return "#version 130"; - } -} - -std::string OpenGLHostDisplay::GetGLSLVersionHeader() const -{ - std::string header = GetGLSLVersionString(); - header += "\n\n"; - if (GetRenderAPI() == RenderAPI::OpenGLES) - { - header += "precision highp float;\n"; - header += "precision highp int;\n\n"; - } - - return header; -} - -bool OpenGLHostDisplay::HasDevice() const -{ - return static_cast(m_gl_context); -} - -bool OpenGLHostDisplay::HasSurface() const -{ - return m_window_info.type != WindowInfo::Type::Surfaceless; -} - -bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync) -{ - m_gl_context = GL::Context::Create(wi); - if (!m_gl_context) - { - Console.Error("Failed to create any GL context"); - m_gl_context.reset(); - return false; - } - - m_window_info = m_gl_context->GetWindowInfo(); - m_vsync_mode = vsync; - return true; -} - -bool OpenGLHostDisplay::SetupDevice() -{ - // We do use 8-bit formats, and higher alignment for 32-bit formats won't hurt anything. - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - SetSwapInterval(); - GL::Program::ResetLastProgram(); - return true; -} - -void OpenGLHostDisplay::SetSwapInterval() -{ - const int interval = ((m_vsync_mode == VsyncMode::Adaptive) ? -1 : ((m_vsync_mode == VsyncMode::On) ? 1 : 0)); - m_gl_context->SetSwapInterval(interval); -} - -bool OpenGLHostDisplay::MakeCurrent() -{ - if (!m_gl_context->MakeCurrent()) - { - Console.Error("Failed to make GL context current"); - return false; - } - - SetSwapInterval(); - return true; -} - -bool OpenGLHostDisplay::DoneCurrent() -{ - return m_gl_context->DoneCurrent(); -} - -bool OpenGLHostDisplay::ChangeWindow(const WindowInfo& new_wi) -{ - pxAssert(m_gl_context); - - if (!m_gl_context->ChangeSurface(new_wi)) - { - Console.Error("Failed to change surface"); - return false; - } - - m_window_info = m_gl_context->GetWindowInfo(); - - if (new_wi.type != WindowInfo::Type::Surfaceless) - { - // reset vsync rate, since it (usually) gets lost - if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) - m_gl_context->SetSwapInterval(static_cast(m_vsync_mode != VsyncMode::Off)); - } - - return true; -} - -void OpenGLHostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (!m_gl_context) - return; - - m_window_info.surface_scale = new_window_scale; - if (m_window_info.surface_width == static_cast(new_window_width) && - m_window_info.surface_height == static_cast(new_window_height)) - { - return; - } - - m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); - m_window_info = m_gl_context->GetWindowInfo(); -} - -bool OpenGLHostDisplay::SupportsFullscreen() const -{ - return false; -} - -bool OpenGLHostDisplay::IsFullscreen() -{ - return false; -} - -bool OpenGLHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) -{ - return false; -} - -HostDisplay::AdapterAndModeList OpenGLHostDisplay::GetAdapterAndModeList() -{ - AdapterAndModeList aml; - - if (m_gl_context) - { - for (const GL::Context::FullscreenModeInfo& fmi : m_gl_context->EnumerateFullscreenModes()) - aml.fullscreen_modes.push_back(GetFullscreenModeString(fmi.width, fmi.height, fmi.refresh_rate)); - } - - return aml; -} - -void OpenGLHostDisplay::DestroySurface() -{ - if (!m_gl_context) - return; - - m_window_info = {}; - if (!m_gl_context->ChangeSurface(m_window_info)) - Console.Error("Failed to switch to surfaceless"); -} - -std::string OpenGLHostDisplay::GetDriverInfo() const -{ - const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); - const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); - const char* gl_version = reinterpret_cast(glGetString(GL_VERSION)); - return StringUtil::StdStringFromFormat( - "%s Context:\n%s\n%s %s", m_gl_context->IsGLES() ? "OpenGL ES" : "OpenGL", gl_version, gl_vendor, gl_renderer); -} - -bool OpenGLHostDisplay::CreateImGuiContext() -{ - return ImGui_ImplOpenGL3_Init(GetGLSLVersionString()); -} - -void OpenGLHostDisplay::DestroyImGuiContext() -{ - ImGui_ImplOpenGL3_Shutdown(); -} - -bool OpenGLHostDisplay::UpdateImGuiFontTexture() -{ - return ImGui_ImplOpenGL3_CreateFontsTexture(); -} - -HostDisplay::PresentResult OpenGLHostDisplay::BeginPresent(bool frame_skip) -{ - if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless) - return PresentResult::FrameSkipped; - - glDisable(GL_SCISSOR_TEST); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(0, 0, m_window_info.surface_width, m_window_info.surface_height); - - return PresentResult::OK; -} - -void OpenGLHostDisplay::EndPresent() -{ - glDisable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - GL::Program::ResetLastProgram(); - - if (m_gpu_timing_enabled) - PopTimestampQuery(); - - m_gl_context->SwapBuffers(); - - if (m_gpu_timing_enabled) - KickTimestampQuery(); -} - -void OpenGLHostDisplay::CreateTimestampQueries() -{ - const bool gles = m_gl_context->IsGLES(); - const auto GenQueries = gles ? glGenQueriesEXT : glGenQueries; - - GenQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); - KickTimestampQuery(); -} - -void OpenGLHostDisplay::DestroyTimestampQueries() -{ - if (m_timestamp_queries[0] == 0) - return; - - const bool gles = m_gl_context->IsGLES(); - const auto DeleteQueries = gles ? glDeleteQueriesEXT : glDeleteQueries; - - if (m_timestamp_query_started) - { - const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; - EndQuery(GL_TIME_ELAPSED); - } - - DeleteQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); - m_timestamp_queries.fill(0); - m_read_timestamp_query = 0; - m_write_timestamp_query = 0; - m_waiting_timestamp_queries = 0; - m_timestamp_query_started = false; -} - -void OpenGLHostDisplay::PopTimestampQuery() -{ - const bool gles = m_gl_context->IsGLES(); - - if (gles) - { - GLint disjoint = 0; - glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint); - if (disjoint) - { - DevCon.WriteLn("GPU timing disjoint, resetting."); - if (m_timestamp_query_started) - glEndQueryEXT(GL_TIME_ELAPSED); - - m_read_timestamp_query = 0; - m_write_timestamp_query = 0; - m_waiting_timestamp_queries = 0; - m_timestamp_query_started = false; - } - } - - while (m_waiting_timestamp_queries > 0) - { - const auto GetQueryObjectiv = gles ? glGetQueryObjectivEXT : glGetQueryObjectiv; - const auto GetQueryObjectui64v = gles ? glGetQueryObjectui64vEXT : glGetQueryObjectui64v; - - GLint available = 0; - GetQueryObjectiv(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT_AVAILABLE, &available); - - if (!available) - break; - - u64 result = 0; - GetQueryObjectui64v(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT, &result); - m_accumulated_gpu_time += static_cast(static_cast(result) / 1000000.0); - m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; - m_waiting_timestamp_queries--; - } - - if (m_timestamp_query_started) - { - const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; - EndQuery(GL_TIME_ELAPSED); - - m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; - m_timestamp_query_started = false; - m_waiting_timestamp_queries++; - } -} - -void OpenGLHostDisplay::KickTimestampQuery() -{ - if (m_timestamp_query_started || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES) - return; - - const bool gles = m_gl_context->IsGLES(); - const auto BeginQuery = gles ? glBeginQueryEXT : glBeginQuery; - - BeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]); - m_timestamp_query_started = true; -} - -bool OpenGLHostDisplay::SetGPUTimingEnabled(bool enabled) -{ - if (m_gpu_timing_enabled == enabled) - return true; - - if (enabled && m_gl_context->IsGLES() && !GLAD_GL_EXT_disjoint_timer_query) - return false; - - m_gpu_timing_enabled = enabled; - if (m_gpu_timing_enabled) - CreateTimestampQueries(); - else - DestroyTimestampQueries(); - - return true; -} - -float OpenGLHostDisplay::GetAndResetAccumulatedGPUTime() -{ - const float value = m_accumulated_gpu_time; - m_accumulated_gpu_time = 0.0f; - return value; -} - diff --git a/pcsx2/Frontend/OpenGLHostDisplay.h b/pcsx2/Frontend/OpenGLHostDisplay.h deleted file mode 100644 index 07f5efb17f..0000000000 --- a/pcsx2/Frontend/OpenGLHostDisplay.h +++ /dev/null @@ -1,94 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include - -#include "HostDisplay.h" -#include "common/GL/Context.h" -#include "common/WindowInfo.h" -#include -#include -#include - -class OpenGLHostDisplay final : public HostDisplay -{ -public: - OpenGLHostDisplay(); - ~OpenGLHostDisplay(); - - RenderAPI GetRenderAPI() const override; - void* GetDevice() const override; - void* GetContext() const override; - void* GetSurface() const override; - - bool HasDevice() const override; - bool HasSurface() const override; - - bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override; - bool SetupDevice() override; - - bool MakeCurrent() override; - bool DoneCurrent() override; - - bool ChangeWindow(const WindowInfo& new_wi) override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - bool SupportsFullscreen() const override; - bool IsFullscreen() override; - bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; - AdapterAndModeList GetAdapterAndModeList() override; - void DestroySurface() override; - std::string GetDriverInfo() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - - void SetVSync(VsyncMode mode) override; - - PresentResult BeginPresent(bool frame_skip) override; - void EndPresent() override; - - bool SetGPUTimingEnabled(bool enabled) override; - float GetAndResetAccumulatedGPUTime() override; - -protected: - static constexpr u8 NUM_TIMESTAMP_QUERIES = 5; - - const char* GetGLSLVersionString() const; - std::string GetGLSLVersionHeader() const; - - bool CreateImGuiContext() override; - void DestroyImGuiContext() override; - bool UpdateImGuiFontTexture() override; - - void SetSwapInterval(); - - void CreateTimestampQueries(); - void DestroyTimestampQueries(); - void PopTimestampQuery(); - void KickTimestampQuery(); - - std::unique_ptr m_gl_context; - - std::array m_timestamp_queries = {}; - u8 m_read_timestamp_query = 0; - u8 m_write_timestamp_query = 0; - u8 m_waiting_timestamp_queries = 0; - bool m_timestamp_query_started = false; - float m_accumulated_gpu_time = 0.0f; - bool m_gpu_timing_enabled = false; -}; - diff --git a/pcsx2/Frontend/VulkanHostDisplay.cpp b/pcsx2/Frontend/VulkanHostDisplay.cpp deleted file mode 100644 index 7499e27684..0000000000 --- a/pcsx2/Frontend/VulkanHostDisplay.cpp +++ /dev/null @@ -1,470 +0,0 @@ -#include "PrecompiledHeader.h" - -#include "VulkanHostDisplay.h" -#include "ShaderCacheVersion.h" -#include "common/Align.h" -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/ScopedGuard.h" -#include "common/Vulkan/Builders.h" -#include "common/Vulkan/Context.h" -#include "common/Vulkan/ShaderCache.h" -#include "common/Vulkan/StreamBuffer.h" -#include "common/Vulkan/SwapChain.h" -#include "common/Vulkan/Util.h" -#include "imgui.h" -#include "imgui_impl_vulkan.h" -#include - -class VulkanHostDisplayTexture : public HostDisplayTexture -{ -public: - explicit VulkanHostDisplayTexture(Vulkan::Texture texture) - : m_texture(std::move(texture)) - { - } - ~VulkanHostDisplayTexture() override = default; - - void* GetHandle() const override { return const_cast(&m_texture); } - u32 GetWidth() const override { return m_texture.GetWidth(); } - u32 GetHeight() const override { return m_texture.GetHeight(); } - - const Vulkan::Texture& GetTexture() const { return m_texture; } - Vulkan::Texture& GetTexture() { return m_texture; } - -private: - Vulkan::Texture m_texture; -}; - -static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(VsyncMode mode) -{ - if (mode == VsyncMode::On) - return VK_PRESENT_MODE_FIFO_KHR; - else if (mode == VsyncMode::Adaptive) - return VK_PRESENT_MODE_FIFO_RELAXED_KHR; - else - return VK_PRESENT_MODE_IMMEDIATE_KHR; -} - -VulkanHostDisplay::VulkanHostDisplay() = default; - -VulkanHostDisplay::~VulkanHostDisplay() -{ - if (g_vulkan_context) - { - g_vulkan_context->WaitForGPUIdle(); - m_swap_chain.reset(); - - Vulkan::ShaderCache::Destroy(); - Vulkan::Context::Destroy(); - } -} - -RenderAPI VulkanHostDisplay::GetRenderAPI() const -{ - return RenderAPI::Vulkan; -} - -void* VulkanHostDisplay::GetDevice() const -{ - return nullptr; -} - -void* VulkanHostDisplay::GetContext() const -{ - return nullptr; -} - -void* VulkanHostDisplay::GetSurface() const -{ - return m_swap_chain.get(); -} - -bool VulkanHostDisplay::ChangeWindow(const WindowInfo& new_wi) -{ - g_vulkan_context->WaitForGPUIdle(); - - if (new_wi.type == WindowInfo::Type::Surfaceless) - { - g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::Sleep); - m_swap_chain.reset(); - m_window_info = new_wi; - return true; - } - - // recreate surface in existing swap chain if it already exists - if (m_swap_chain) - { - if (m_swap_chain->RecreateSurface(new_wi)) - { - m_window_info = m_swap_chain->GetWindowInfo(); - return true; - } - - m_swap_chain.reset(); - } - - WindowInfo wi_copy(new_wi); - VkSurfaceKHR surface = - Vulkan::SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), &wi_copy); - if (surface == VK_NULL_HANDLE) - { - Console.Error("Failed to create new surface for swap chain"); - return false; - } - - m_swap_chain = Vulkan::SwapChain::Create(wi_copy, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode)); - if (!m_swap_chain) - { - Console.Error("Failed to create swap chain"); - Vulkan::SwapChain::DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), &wi_copy, surface); - return false; - } - - m_window_info = m_swap_chain->GetWindowInfo(); - return true; -} - -void VulkanHostDisplay::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (m_swap_chain->GetWidth() == static_cast(new_window_width) && m_swap_chain->GetHeight() == static_cast(new_window_height)) - { - // skip unnecessary resizes - m_window_info.surface_scale = new_window_scale; - return; - } - - g_vulkan_context->WaitForGPUIdle(); - - if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale)) - { - // AcquireNextImage() will fail, and we'll recreate the surface. - Console.Error("Failed to resize swap chain. Next present will fail."); - return; - } - - m_window_info = m_swap_chain->GetWindowInfo(); -} - -bool VulkanHostDisplay::SupportsFullscreen() const -{ - return false; -} - -bool VulkanHostDisplay::IsFullscreen() -{ - return false; -} - -bool VulkanHostDisplay::SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) -{ - return false; -} - -HostDisplay::AdapterAndModeList VulkanHostDisplay::GetAdapterAndModeList() -{ - return StaticGetAdapterAndModeList(m_window_info.type != WindowInfo::Type::Surfaceless ? &m_window_info : nullptr); -} - -void VulkanHostDisplay::DestroySurface() -{ - g_vulkan_context->WaitForGPUIdle(); - m_swap_chain.reset(); -} - -std::string VulkanHostDisplay::GetDriverInfo() const -{ - std::string ret; - const u32 api_version = g_vulkan_context->GetDeviceProperties().apiVersion; - const u32 driver_version = g_vulkan_context->GetDeviceProperties().driverVersion; - if (g_vulkan_context->GetOptionalExtensions().vk_khr_driver_properties) - { - const VkPhysicalDeviceDriverProperties& props = g_vulkan_context->GetDeviceDriverProperties(); - ret = StringUtil::StdStringFromFormat("Driver %u.%u.%u\nVulkan %u.%u.%u\nConformance Version %u.%u.%u.%u\n%s\n%s\n%s", - VK_VERSION_MAJOR(driver_version), VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version), - VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version), - props.conformanceVersion.major, props.conformanceVersion.minor, props.conformanceVersion.subminor, - props.conformanceVersion.patch, props.driverInfo, props.driverName, g_vulkan_context->GetDeviceProperties().deviceName); - } - else - { - ret = StringUtil::StdStringFromFormat("Driver %u.%u.%u\nVulkan %u.%u.%u\n%s", VK_VERSION_MAJOR(driver_version), - VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version), VK_API_VERSION_MAJOR(api_version), - VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version), g_vulkan_context->GetDeviceProperties().deviceName); - } - - return ret; -} - -static bool UploadBufferToTexture( - Vulkan::Texture* texture, VkCommandBuffer cmdbuf, u32 width, u32 height, const void* data, u32 data_stride) -{ - const u32 texel_size = Vulkan::Util::GetTexelSize(texture->GetFormat()); - const u32 row_size = texel_size * width; - const u32 upload_stride = Common::AlignUpPow2(row_size, g_vulkan_context->GetBufferCopyRowPitchAlignment()); - const u32 upload_size = upload_stride * height; - pxAssert(row_size <= data_stride); - - Vulkan::StreamBuffer& buf = g_vulkan_context->GetTextureUploadBuffer(); - if (!buf.ReserveMemory(upload_size, g_vulkan_context->GetBufferCopyOffsetAlignment())) - { - Console.WriteLn("Executing command buffer for UploadBufferToTexture()"); - g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None); - if (!buf.ReserveMemory(upload_size, g_vulkan_context->GetBufferCopyOffsetAlignment())) - { - Console.WriteLn("Failed to allocate %u bytes in stream buffer for UploadBufferToTexture()", upload_size); - return false; - } - cmdbuf = g_vulkan_context->GetCurrentInitCommandBuffer(); - } - - const u32 buf_offset = buf.GetCurrentOffset(); - StringUtil::StrideMemCpy(buf.GetCurrentHostPointer(), upload_stride, data, data_stride, row_size, height); - buf.CommitMemory(upload_size); - - texture->UpdateFromBuffer(cmdbuf, 0, 0, 0, 0, width, height, height, upload_stride / texel_size, buf.GetBuffer(), buf_offset); - return true; -} - -std::unique_ptr VulkanHostDisplay::CreateTexture( - u32 width, u32 height, const void* data, u32 data_stride, bool dynamic /* = false */) -{ - static constexpr VkFormat vk_format = VK_FORMAT_R8G8B8A8_UNORM; - static constexpr VkImageUsageFlags usage = - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - - Vulkan::Texture texture; - if (!texture.Create(width, height, 1, 1, vk_format, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage)) - return {}; - - texture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - if (data) - { - if (!UploadBufferToTexture(&texture, g_vulkan_context->GetCurrentInitCommandBuffer(), width, height, data, data_stride)) - return {}; - } - else - { - // clear it instead so we don't read uninitialized data (and keep the validation layer happy!) - static constexpr VkClearColorValue ccv = {}; - static constexpr VkImageSubresourceRange isr = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}; - vkCmdClearColorImage(g_vulkan_context->GetCurrentInitCommandBuffer(), texture.GetImage(), texture.GetLayout(), &ccv, 1u, &isr); - } - - texture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - return std::make_unique(std::move(texture)); -} - -void VulkanHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) -{ - UploadBufferToTexture(&static_cast(texture)->GetTexture(), g_vulkan_context->GetCurrentCommandBuffer(), - width, height, data, data_stride); -} - -void VulkanHostDisplay::SetVSync(VsyncMode mode) -{ - if (!m_swap_chain || m_vsync_mode == mode) - return; - - // This swap chain should not be used by the current buffer, thus safe to destroy. - g_vulkan_context->WaitForGPUIdle(); - m_swap_chain->SetVSync(GetPreferredPresentModeForVsyncMode(mode)); - m_vsync_mode = mode; -} - -bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync) -{ - WindowInfo local_wi(wi); - const bool debug_device = EmuConfig.GS.UseDebugDevice; - if (!Vulkan::Context::Create(EmuConfig.GS.Adapter, &local_wi, &m_swap_chain, GetPreferredPresentModeForVsyncMode(vsync), - !EmuConfig.GS.DisableThreadedPresentation, debug_device, debug_device)) - { - Console.Error("Failed to create Vulkan context"); - m_window_info = {}; - return false; - } - - // NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal). - m_window_info = m_swap_chain ? m_swap_chain->GetWindowInfo() : local_wi; - m_vsync_mode = vsync; - return true; -} - -bool VulkanHostDisplay::SetupDevice() -{ - Vulkan::ShaderCache::Create(EmuConfig.GS.DisableShaderCache ? std::string_view() : std::string_view(EmuFolders::Cache), - SHADER_CACHE_VERSION, EmuConfig.GS.UseDebugDevice); - return true; -} - -bool VulkanHostDisplay::HasDevice() const -{ - return static_cast(g_vulkan_context); -} - -bool VulkanHostDisplay::HasSurface() const -{ - return static_cast(m_swap_chain); -} - -bool VulkanHostDisplay::CreateImGuiContext() -{ - const VkRenderPass render_pass = - m_swap_chain ? m_swap_chain->GetClearRenderPass() : g_vulkan_context->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED); - if (render_pass == VK_NULL_HANDLE) - return false; - - return ImGui_ImplVulkan_Init(render_pass); -} - -void VulkanHostDisplay::DestroyImGuiContext() -{ - g_vulkan_context->WaitForGPUIdle(); - ImGui_ImplVulkan_Shutdown(); -} - -bool VulkanHostDisplay::UpdateImGuiFontTexture() -{ - return ImGui_ImplVulkan_CreateFontsTexture(); -} - -bool VulkanHostDisplay::MakeCurrent() -{ - return true; -} - -bool VulkanHostDisplay::DoneCurrent() -{ - return true; -} - -HostDisplay::PresentResult VulkanHostDisplay::BeginPresent(bool frame_skip) -{ - if (frame_skip || !m_swap_chain) - return PresentResult::FrameSkipped; - - // Previous frame needs to be presented before we can acquire the swap chain. - g_vulkan_context->WaitForPresentComplete(); - - // Check if the device was lost. - if (g_vulkan_context->CheckLastSubmitFail()) - return PresentResult::DeviceLost; - - VkResult res = m_swap_chain->AcquireNextImage(); - if (res != VK_SUCCESS) - { - m_swap_chain->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(); - } - else if (res == VK_ERROR_SURFACE_LOST_KHR) - { - Console.Warning("Surface lost, attempting to recreate"); - if (!m_swap_chain->RecreateSurface(m_window_info)) - { - Console.Error("Failed to recreate surface after loss"); - g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None); - return PresentResult::FrameSkipped; - } - - res = m_swap_chain->AcquireNextImage(); - } - - // This can happen when multiple resize events happen in quick succession. - // In this case, just wait until the next frame to try again. - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - { - // Still submit the command buffer, otherwise we'll end up with several frames waiting. - LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); - g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None); - return PresentResult::FrameSkipped; - } - } - - VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); - - // Swap chain images start in undefined - Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture(); - swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); - swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - - const VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; - const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_swap_chain->GetClearRenderPass(), - m_swap_chain->GetCurrentFramebuffer(), {{0, 0}, {swap_chain_texture.GetWidth(), swap_chain_texture.GetHeight()}}, 1u, &clear_value}; - vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); - - const VkViewport vp{ - 0.0f, 0.0f, static_cast(swap_chain_texture.GetWidth()), static_cast(swap_chain_texture.GetHeight()), 0.0f, 1.0f}; - const VkRect2D scissor{{0, 0}, {static_cast(swap_chain_texture.GetWidth()), static_cast(swap_chain_texture.GetHeight())}}; - vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp); - vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor); - return PresentResult::OK; -} - -void VulkanHostDisplay::EndPresent() -{ - ImGui::Render(); - ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData()); - - VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); - vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer()); - m_swap_chain->GetCurrentTexture().TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); - - g_vulkan_context->SubmitCommandBuffer(m_swap_chain.get(), !m_swap_chain->IsPresentModeSynchronizing()); - g_vulkan_context->MoveToNextCommandBuffer(); -} - -bool VulkanHostDisplay::SetGPUTimingEnabled(bool enabled) -{ - return g_vulkan_context->SetEnableGPUTiming(enabled); -} - -float VulkanHostDisplay::GetAndResetAccumulatedGPUTime() -{ - return g_vulkan_context->GetAndResetAccumulatedGPUTime(); -} - -HostDisplay::AdapterAndModeList VulkanHostDisplay::StaticGetAdapterAndModeList(const WindowInfo* wi) -{ - AdapterAndModeList ret; - std::vector fsmodes; - - if (g_vulkan_context) - { - ret.adapter_names = Vulkan::Context::EnumerateGPUNames(g_vulkan_context->GetVulkanInstance()); - if (wi) - { - fsmodes = Vulkan::SwapChain::GetSurfaceFullscreenModes( - g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), *wi); - } - } - else if (Vulkan::LoadVulkanLibrary()) - { - ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); }); - - VkInstance instance = Vulkan::Context::CreateVulkanInstance(nullptr, false, false); - if (instance != VK_NULL_HANDLE) - { - ScopedGuard instance_guard([&instance]() { vkDestroyInstance(instance, nullptr); }); - - if (Vulkan::LoadVulkanInstanceFunctions(instance)) - ret.adapter_names = Vulkan::Context::EnumerateGPUNames(instance); - } - } - - if (!fsmodes.empty()) - { - ret.fullscreen_modes.reserve(fsmodes.size()); - for (const Vulkan::SwapChain::FullscreenModeInfo& fmi : fsmodes) - { - ret.fullscreen_modes.push_back(GetFullscreenModeString(fmi.width, fmi.height, fmi.refresh_rate)); - } - } - - return ret; -} diff --git a/pcsx2/Frontend/VulkanHostDisplay.h b/pcsx2/Frontend/VulkanHostDisplay.h deleted file mode 100644 index 607183d902..0000000000 --- a/pcsx2/Frontend/VulkanHostDisplay.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once -#include "common/Vulkan/Loader.h" -#include "common/Vulkan/StreamBuffer.h" -#include "common/Vulkan/SwapChain.h" -#include "common/WindowInfo.h" -#include "pcsx2/HostDisplay.h" -#include -#include - -namespace Vulkan -{ - class StreamBuffer; - class SwapChain; -} // namespace Vulkan - -class VulkanHostDisplay final : public HostDisplay -{ -public: - VulkanHostDisplay(); - ~VulkanHostDisplay(); - - RenderAPI GetRenderAPI() const override; - void* GetDevice() const override; - void* GetContext() const override; - void* GetSurface() const override; - - bool HasDevice() const override; - bool HasSurface() const override; - - bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override; - bool SetupDevice() override; - - bool MakeCurrent() override; - bool DoneCurrent() override; - - bool ChangeWindow(const WindowInfo& new_wi) override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - bool SupportsFullscreen() const override; - bool IsFullscreen() override; - bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; - AdapterAndModeList GetAdapterAndModeList() override; - void DestroySurface() override; - std::string GetDriverInfo() const override; - - std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override; - void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - - void SetVSync(VsyncMode mode) override; - - PresentResult BeginPresent(bool frame_skip) override; - void EndPresent() override; - - bool SetGPUTimingEnabled(bool enabled) override; - float GetAndResetAccumulatedGPUTime() override; - - static AdapterAndModeList StaticGetAdapterAndModeList(const WindowInfo* wi); - -protected: - bool CreateImGuiContext() override; - void DestroyImGuiContext() override; - bool UpdateImGuiFontTexture() override; - - std::unique_ptr m_swap_chain; -}; diff --git a/pcsx2/Frontend/imgui_impl_dx11.cpp b/pcsx2/Frontend/imgui_impl_dx11.cpp deleted file mode 100644 index 44a59299dc..0000000000 --- a/pcsx2/Frontend/imgui_impl_dx11.cpp +++ /dev/null @@ -1,531 +0,0 @@ -// dear imgui: Renderer Backend for DirectX11 -// This needs to be used along with a Platform Backend (e.g. Win32) - -// Implemented features: -// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -// CHANGELOG -// (minor and older changes stripped away, please see git history for details) -// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). -// 2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) -// 2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer. -// 2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled). -// 2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX10_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore. -// 2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile(). -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility. -// 2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions. -// 2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example. -// 2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. -// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself. -// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. -// 2016-05-07: DirectX11: Disabling depth-write. - -#include "PrecompiledHeader.h" - -#include "imgui.h" -#include "imgui_impl_dx11.h" - -// DirectX -#include -#include -#include -#ifdef _MSC_VER -#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. -#endif - -// DirectX11 data -struct ImGui_ImplDX11_Data -{ - ID3D11Device* pd3dDevice; - ID3D11DeviceContext* pd3dDeviceContext; - IDXGIFactory* pFactory; - ID3D11Buffer* pVB; - ID3D11Buffer* pIB; - ID3D11VertexShader* pVertexShader; - ID3D11InputLayout* pInputLayout; - ID3D11Buffer* pVertexConstantBuffer; - ID3D11PixelShader* pPixelShader; - ID3D11SamplerState* pFontSampler; - ID3D11ShaderResourceView* pFontTextureView; - ID3D11RasterizerState* pRasterizerState; - ID3D11BlendState* pBlendState; - ID3D11DepthStencilState* pDepthStencilState; - int VertexBufferSize; - int IndexBufferSize; - - ImGui_ImplDX11_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } -}; - -struct VERTEX_CONSTANT_BUFFER -{ - float mvp[4][4]; -}; - -// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts -// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. -static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData() -{ - return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : NULL; -} - -// Functions -static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* ctx) -{ - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - - // Setup viewport - D3D11_VIEWPORT vp; - memset(&vp, 0, sizeof(D3D11_VIEWPORT)); - vp.Width = draw_data->DisplaySize.x; - vp.Height = draw_data->DisplaySize.y; - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; - vp.TopLeftX = vp.TopLeftY = 0; - ctx->RSSetViewports(1, &vp); - - // Setup shader and vertex buffers - unsigned int stride = sizeof(ImDrawVert); - unsigned int offset = 0; - ctx->IASetInputLayout(bd->pInputLayout); - ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset); - ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0); - ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ctx->VSSetShader(bd->pVertexShader, NULL, 0); - ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer); - ctx->PSSetShader(bd->pPixelShader, NULL, 0); - ctx->PSSetSamplers(0, 1, &bd->pFontSampler); - ctx->GSSetShader(NULL, NULL, 0); - ctx->HSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used.. - ctx->DSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used.. - ctx->CSSetShader(NULL, NULL, 0); // In theory we should backup and restore this as well.. very infrequently used.. - - // Setup blend state - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); - ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0); - ctx->RSSetState(bd->pRasterizerState); -} - -// Render function -void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) -{ - // Avoid rendering when minimized - if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) - return; - - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - ID3D11DeviceContext* ctx = bd->pd3dDeviceContext; - - // Create and grow vertex/index buffers if needed - if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) - { - if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } - bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; - D3D11_BUFFER_DESC desc; - memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert); - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - desc.MiscFlags = 0; - if (bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pVB) < 0) - return; - } - if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) - { - if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } - bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; - D3D11_BUFFER_DESC desc; - memset(&desc, 0, sizeof(D3D11_BUFFER_DESC)); - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx); - desc.BindFlags = D3D11_BIND_INDEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - if (bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pIB) < 0) - return; - } - - // Upload vertex/index data into a single contiguous GPU buffer - D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource; - if (ctx->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK) - return; - if (ctx->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK) - return; - ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData; - ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - vtx_dst += cmd_list->VtxBuffer.Size; - idx_dst += cmd_list->IdxBuffer.Size; - } - ctx->Unmap(bd->pVB, 0); - ctx->Unmap(bd->pIB, 0); - - // Setup orthographic projection matrix into our constant buffer - // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - { - D3D11_MAPPED_SUBRESOURCE mapped_resource; - if (ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) != S_OK) - return; - VERTEX_CONSTANT_BUFFER* constant_buffer = (VERTEX_CONSTANT_BUFFER*)mapped_resource.pData; - float L = draw_data->DisplayPos.x; - float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; - float T = draw_data->DisplayPos.y; - float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; - float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.5f, 0.0f }, - { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, - }; - memcpy(&constant_buffer->mvp, mvp, sizeof(mvp)); - ctx->Unmap(bd->pVertexConstantBuffer, 0); - } - - // Setup desired DX state - ImGui_ImplDX11_SetupRenderState(draw_data, ctx); - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - int global_idx_offset = 0; - int global_vtx_offset = 0; - ImVec2 clip_off = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback != NULL) - { - // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplDX11_SetupRenderState(draw_data, ctx); - else - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); - ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // Apply scissor/clipping rectangle - const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y }; - ctx->RSSetScissorRects(1, &r); - - // Bind texture, Draw - ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID(); - ctx->PSSetShaderResources(0, 1, &texture_srv); - ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset); - } - } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; - } -} - -void ImGui_ImplDX11_CreateFontsTexture() -{ - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - if (bd->pFontTextureView) - bd->pFontTextureView->Release(); - - // Upload texture to graphics system - { - D3D11_TEXTURE2D_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Width = width; - desc.Height = height; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc.Count = 1; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - desc.CPUAccessFlags = 0; - - ID3D11Texture2D* pTexture = NULL; - D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = pixels; - subResource.SysMemPitch = desc.Width * 4; - subResource.SysMemSlicePitch = 0; - bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - IM_ASSERT(pTexture != NULL); - - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); - pTexture->Release(); - } - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); - - // Create texture sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - if (!bd->pFontSampler) - { - D3D11_SAMPLER_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.MipLODBias = 0.f; - desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - desc.MinLOD = 0.f; - desc.MaxLOD = 0.f; - bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); - } -} - -bool ImGui_ImplDX11_CreateDeviceObjects() -{ - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - if (!bd->pd3dDevice) - return false; - if (bd->pFontSampler) - ImGui_ImplDX11_InvalidateDeviceObjects(); - - // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) - // If you would like to use this DX11 sample code but remove this dependency you can: - // 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution] - // 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL. - // See https://github.com/ocornut/imgui/pull/638 for sources and details. - - // Create the vertex shader - { - static const char* vertexShader = - "cbuffer vertexBuffer : register(b0) \ - {\ - float4x4 ProjectionMatrix; \ - };\ - struct VS_INPUT\ - {\ - float2 pos : POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - \ - struct PS_INPUT\ - {\ - float4 pos : SV_POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - \ - PS_INPUT main(VS_INPUT input)\ - {\ - PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ - output.col = input.col;\ - output.uv = input.uv;\ - return output;\ - }"; - - ID3DBlob* vertexShaderBlob; - if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vertexShaderBlob, NULL))) - return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), NULL, &bd->pVertexShader) != S_OK) - { - vertexShaderBlob->Release(); - return false; - } - - // Create the input layout - D3D11_INPUT_ELEMENT_DESC local_layout[] = - { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, - }; - if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK) - { - vertexShaderBlob->Release(); - return false; - } - vertexShaderBlob->Release(); - - // Create the constant buffer - { - D3D11_BUFFER_DESC desc; - desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER); - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - desc.MiscFlags = 0; - bd->pd3dDevice->CreateBuffer(&desc, NULL, &bd->pVertexConstantBuffer); - } - } - - // Create the pixel shader - { - static const char* pixelShader = - "struct PS_INPUT\ - {\ - float4 pos : SV_POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - sampler sampler0;\ - Texture2D texture0;\ - \ - float4 main(PS_INPUT input) : SV_Target\ - {\ - float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \ - return out_col; \ - }"; - - ID3DBlob* pixelShaderBlob; - if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &pixelShaderBlob, NULL))) - return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), NULL, &bd->pPixelShader) != S_OK) - { - pixelShaderBlob->Release(); - return false; - } - pixelShaderBlob->Release(); - } - - // Create the blending setup - { - D3D11_BLEND_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.AlphaToCoverageEnable = false; - desc.RenderTarget[0].BlendEnable = true; - desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; - desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; - desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState); - } - - // Create the rasterizer state - { - D3D11_RASTERIZER_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.FillMode = D3D11_FILL_SOLID; - desc.CullMode = D3D11_CULL_NONE; - desc.ScissorEnable = true; - desc.DepthClipEnable = true; - bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState); - } - - // Create depth-stencil State - { - D3D11_DEPTH_STENCIL_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.DepthEnable = false; - desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; - desc.DepthFunc = D3D11_COMPARISON_ALWAYS; - desc.StencilEnable = false; - desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; - desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; - desc.BackFace = desc.FrontFace; - bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState); - } - - ImGui_ImplDX11_CreateFontsTexture(); - - return true; -} - -void ImGui_ImplDX11_InvalidateDeviceObjects() -{ - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - if (!bd->pd3dDevice) - return; - - if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = NULL; } - if (bd->pFontTextureView) { bd->pFontTextureView->Release(); bd->pFontTextureView = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. - if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } - if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } - if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = NULL; } - if (bd->pDepthStencilState) { bd->pDepthStencilState->Release(); bd->pDepthStencilState = NULL; } - if (bd->pRasterizerState) { bd->pRasterizerState->Release(); bd->pRasterizerState = NULL; } - if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = NULL; } - if (bd->pVertexConstantBuffer) { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = NULL; } - if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = NULL; } - if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = NULL; } -} - -bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context) -{ - ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_dx11"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - - // Get factory from device - IDXGIDevice* pDXGIDevice = NULL; - IDXGIAdapter* pDXGIAdapter = NULL; - IDXGIFactory* pFactory = NULL; - - if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK) - if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK) - if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK) - { - bd->pd3dDevice = device; - bd->pd3dDeviceContext = device_context; - bd->pFactory = pFactory; - } - if (pDXGIDevice) pDXGIDevice->Release(); - if (pDXGIAdapter) pDXGIAdapter->Release(); - bd->pd3dDevice->AddRef(); - bd->pd3dDeviceContext->AddRef(); - - return ImGui_ImplDX11_CreateDeviceObjects(); -} - -void ImGui_ImplDX11_Shutdown() -{ - ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); - if (bd == NULL) - return; - - ImGui_ImplDX11_InvalidateDeviceObjects(); - if (bd->pFactory) { bd->pFactory->Release(); } - if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } - if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); } - - ImGuiIO& io = ImGui::GetIO(); - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; - IM_DELETE(bd); -} diff --git a/pcsx2/Frontend/imgui_impl_dx11.h b/pcsx2/Frontend/imgui_impl_dx11.h deleted file mode 100644 index ae5c967407..0000000000 --- a/pcsx2/Frontend/imgui_impl_dx11.h +++ /dev/null @@ -1,17 +0,0 @@ -// dear imgui: Renderer Backend for DirectX11 -// This needs to be used along with a Platform Backend (e.g. Win32) - -#pragma once -#include "imgui.h" // IMGUI_IMPL_API - -struct ID3D11Device; -struct ID3D11DeviceContext; - -bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); -void ImGui_ImplDX11_Shutdown(); -void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); - -// Use if you want to reset your rendering device without losing Dear ImGui state. -void ImGui_ImplDX11_InvalidateDeviceObjects(); -bool ImGui_ImplDX11_CreateDeviceObjects(); -void ImGui_ImplDX11_CreateFontsTexture(); diff --git a/pcsx2/Frontend/imgui_impl_dx12.cpp b/pcsx2/Frontend/imgui_impl_dx12.cpp deleted file mode 100644 index 68bfef66fc..0000000000 --- a/pcsx2/Frontend/imgui_impl_dx12.cpp +++ /dev/null @@ -1,536 +0,0 @@ -// dear imgui: Renderer Backend for DirectX12 -// This needs to be used along with a Platform Backend (e.g. Win32) - -// Implemented features: -// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. - -// Important: to compile on 32-bit systems, this backend requires code to be compiled with '#define ImTextureID ImU64'. -// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. -// To build this on 32-bit systems: -// - [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in the 'example_win32_direct12/example_win32_direct12.vcxproj' project file) -// - [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. -// - [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) -// - [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in the example_win32_direct12/build_win32.bat file) - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -// CHANGELOG -// (minor and older changes stripped away, please see git history for details) -// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). -// 2021-05-19: DirectX12: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) -// 2021-02-18: DirectX12: Change blending equation to preserve alpha in output buffer. -// 2021-01-11: DirectX12: Improve Windows 7 compatibility (for D3D12On7) by loading d3d12.dll dynamically. -// 2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning. -// 2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID. -// 2019-10-18: DirectX12: *BREAKING CHANGE* Added extra ID3D12DescriptorHeap parameter to ImGui_ImplDX12_Init() function. -// 2019-05-29: DirectX12: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: DirectX12: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2019-03-29: Misc: Various minor tidying up. -// 2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile(). -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-06-12: DirectX12: Moved the ID3D12GraphicsCommandList* parameter from NewFrame() to RenderDrawData(). -// 2018-06-08: Misc: Extracted imgui_impl_dx12.cpp/.h away from the old combined DX12+Win32 example. -// 2018-06-08: DirectX12: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle (to ease support for future multi-viewport). -// 2018-02-22: Merged into master with all Win32 code synchronized to other examples. - -#include "PrecompiledHeader.h" - -#include "common/Assertions.h" -#include "common/D3D12/Context.h" -#include "common/D3D12/Texture.h" -#include "common/D3D12/StreamBuffer.h" -#include "common/RedtapeWindows.h" - -#include "imgui.h" -#include "imgui_impl_dx12.h" - -// DirectX -#include -#include -#include -#ifdef _MSC_VER -#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. -#endif - -// If we're doing more than this... wtf? -static constexpr u32 VERTEX_BUFFER_SIZE = 8 * 1024 * 1024; -static constexpr u32 INDEX_BUFFER_SIZE = 4 * 1024 * 1024; - -struct ImGui_ImplDX12_Data -{ - D3D12::StreamBuffer VertexStreamBuffer; - D3D12::StreamBuffer IndexStreamBuffer; - D3D12::Texture FontTexture; - ID3D12RootSignature* pRootSignature = nullptr; - ID3D12PipelineState* pPipelineState = nullptr; - DXGI_FORMAT RTVFormat = DXGI_FORMAT_UNKNOWN; -}; - -struct VERTEX_CONSTANT_BUFFER -{ - float mvp[4][4]; -}; - -// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts -// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. -static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData() -{ - return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : NULL; -} - -// Functions -static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12GraphicsCommandList* ctx) -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - - // Setup orthographic projection matrix into our constant buffer - // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). - VERTEX_CONSTANT_BUFFER vertex_constant_buffer; - { - float L = draw_data->DisplayPos.x; - float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; - float T = draw_data->DisplayPos.y; - float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; - float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, 0.5f, 0.0f }, - { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, - }; - memcpy(&vertex_constant_buffer.mvp, mvp, sizeof(mvp)); - } - - // Setup viewport - D3D12_VIEWPORT vp; - memset(&vp, 0, sizeof(D3D12_VIEWPORT)); - vp.Width = draw_data->DisplaySize.x; - vp.Height = draw_data->DisplaySize.y; - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; - vp.TopLeftX = vp.TopLeftY = 0.0f; - ctx->RSSetViewports(1, &vp); - - // Bind shader and vertex buffers - unsigned int stride = sizeof(ImDrawVert); - D3D12_VERTEX_BUFFER_VIEW vbv; - memset(&vbv, 0, sizeof(D3D12_VERTEX_BUFFER_VIEW)); - vbv.BufferLocation = bd->VertexStreamBuffer.GetCurrentGPUPointer(); - vbv.SizeInBytes = bd->VertexStreamBuffer.GetCurrentSpace(); - vbv.StrideInBytes = stride; - ctx->IASetVertexBuffers(0, 1, &vbv); - D3D12_INDEX_BUFFER_VIEW ibv; - memset(&ibv, 0, sizeof(D3D12_INDEX_BUFFER_VIEW)); - ibv.BufferLocation = bd->IndexStreamBuffer.GetCurrentGPUPointer(); - ibv.SizeInBytes = bd->IndexStreamBuffer.GetCurrentSpace(); - ibv.Format = sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; - ctx->IASetIndexBuffer(&ibv); - ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - ctx->SetPipelineState(bd->pPipelineState); - ctx->SetGraphicsRootSignature(bd->pRootSignature); - ctx->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0); - - // Setup blend factor - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; - ctx->OMSetBlendFactor(blend_factor); -} - -template -static inline void SafeRelease(T*& res) -{ - if (res) - res->Release(); - res = NULL; -} - -// Render function -void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data) -{ - // Avoid rendering when minimized - if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) - return; - - // FIXME: I'm assuming that this only gets called once per frame! - // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator. - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - - const u32 needed_vb = draw_data->TotalVtxCount * sizeof(ImDrawVert); - const u32 needed_ib = draw_data->TotalIdxCount * sizeof(ImDrawIdx); - - if (!bd->VertexStreamBuffer.ReserveMemory(needed_vb, sizeof(ImDrawVert)) || - !bd->IndexStreamBuffer.ReserveMemory(needed_ib, sizeof(ImDrawIdx))) - { - g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None); - if (!bd->VertexStreamBuffer.ReserveMemory(needed_vb, sizeof(ImDrawVert)) || - !bd->IndexStreamBuffer.ReserveMemory(needed_ib, sizeof(ImDrawIdx))) - { - pxFailRel("Failed to allocate space for imgui vertices/indices"); - } - } - - // Upload vertex/index data into a single contiguous GPU buffer - ImDrawVert* vtx_dst = (ImDrawVert*)bd->VertexStreamBuffer.GetCurrentHostPointer(); - ImDrawIdx* idx_dst = (ImDrawIdx*)bd->IndexStreamBuffer.GetCurrentHostPointer(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - vtx_dst += cmd_list->VtxBuffer.Size; - idx_dst += cmd_list->IdxBuffer.Size; - } - - // Setup desired DX state (must happen before commit, because it uses the offsets) - ID3D12GraphicsCommandList* ctx = g_d3d12_context->GetCommandList(); - ImGui_ImplDX12_SetupRenderState(draw_data, ctx); - bd->VertexStreamBuffer.CommitMemory(needed_vb); - bd->IndexStreamBuffer.CommitMemory(needed_ib); - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - int global_vtx_offset = 0; - int global_idx_offset = 0; - ImVec2 clip_off = draw_data->DisplayPos; - const D3D12::Texture* last_texture = nullptr; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback != NULL) - { - // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplDX12_SetupRenderState(draw_data, ctx); - else - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); - ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // Apply Scissor/clipping rectangle, Bind texture, Draw - const D3D12_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y }; - - const D3D12::Texture* tex = (D3D12::Texture*)pcmd->GetTexID(); - if (tex && last_texture != tex) - { - D3D12::DescriptorHandle handle; - if (!g_d3d12_context->GetDescriptorAllocator().Allocate(1, &handle)) - { - // ugh. - g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None); - ctx = g_d3d12_context->GetCommandList(); - ImGui_ImplDX12_SetupRenderState(draw_data, ctx); - if (!g_d3d12_context->GetDescriptorAllocator().Allocate(1, &handle)) - pxFailRel("Failed to allocate descriptor after cmdlist kick"); - } - - g_d3d12_context->GetDevice()->CopyDescriptorsSimple(1, handle, tex->GetSRVDescriptor(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); - ctx->SetGraphicsRootDescriptorTable(1, handle); - last_texture = tex; - } - - ctx->RSSetScissorRects(1, &r); - ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); - } - } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; - } -} - -bool ImGui_ImplDX12_CreateFontsTexture() -{ - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // Upload texture to graphics system - if (bd->FontTexture.GetWidth() != width || bd->FontTexture.GetHeight() != height) - { - if (!bd->FontTexture.Create(width, height, 1, DXGI_FORMAT_R8G8B8A8_UNORM, - DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, - D3D12_RESOURCE_FLAG_NONE)) - { - return false; - } - } - - if (!bd->FontTexture.LoadData(g_d3d12_context->GetInitCommandList(), 0, 0, 0, width, height, pixels, width * sizeof(u32))) - return false; - - io.Fonts->SetTexID((ImTextureID)&bd->FontTexture); - return true; -} - -bool ImGui_ImplDX12_CreateDeviceObjects() -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - if (bd->pPipelineState) - ImGui_ImplDX12_DestroyDeviceObjects(); - - // Create the root signature - { - D3D12_DESCRIPTOR_RANGE descRange = {}; - descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; - descRange.NumDescriptors = 1; - descRange.BaseShaderRegister = 0; - descRange.RegisterSpace = 0; - descRange.OffsetInDescriptorsFromTableStart = 0; - - D3D12_ROOT_PARAMETER param[2] = {}; - - param[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; - param[0].Constants.ShaderRegister = 0; - param[0].Constants.RegisterSpace = 0; - param[0].Constants.Num32BitValues = 16; - param[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; - - param[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; - param[1].DescriptorTable.NumDescriptorRanges = 1; - param[1].DescriptorTable.pDescriptorRanges = &descRange; - param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. - D3D12_STATIC_SAMPLER_DESC staticSampler = {}; - staticSampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - staticSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSampler.MipLODBias = 0.f; - staticSampler.MaxAnisotropy = 0; - staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; - staticSampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; - staticSampler.MinLOD = 0.f; - staticSampler.MaxLOD = 0.f; - staticSampler.ShaderRegister = 0; - staticSampler.RegisterSpace = 0; - staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - - D3D12_ROOT_SIGNATURE_DESC desc = {}; - desc.NumParameters = _countof(param); - desc.pParameters = param; - desc.NumStaticSamplers = 1; - desc.pStaticSamplers = &staticSampler; - desc.Flags = - D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | - D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | - D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS; - - auto blob = g_d3d12_context->SerializeRootSignature(&desc); - if (!blob) - return false; - - g_d3d12_context->GetDevice()->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(&bd->pRootSignature)); - } - - // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) - // If you would like to use this DX12 sample code but remove this dependency you can: - // 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution] - // 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL. - // See https://github.com/ocornut/imgui/pull/638 for sources and details. - - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc; - memset(&psoDesc, 0, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC)); - psoDesc.NodeMask = 1; - psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; - psoDesc.pRootSignature = bd->pRootSignature; - psoDesc.SampleMask = UINT_MAX; - psoDesc.NumRenderTargets = 1; - psoDesc.RTVFormats[0] = bd->RTVFormat; - psoDesc.SampleDesc.Count = 1; - psoDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE; - - ID3DBlob* vertexShaderBlob; - ID3DBlob* pixelShaderBlob; - - // Create the vertex shader - { - static const char* vertexShader = - "cbuffer vertexBuffer : register(b0) \ - {\ - float4x4 ProjectionMatrix; \ - };\ - struct VS_INPUT\ - {\ - float2 pos : POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - \ - struct PS_INPUT\ - {\ - float4 pos : SV_POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - \ - PS_INPUT main(VS_INPUT input)\ - {\ - PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ - output.col = input.col;\ - output.uv = input.uv;\ - return output;\ - }"; - - if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_5_0", 0, 0, &vertexShaderBlob, NULL))) - return false; // NB: Pass ID3D10Blob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - psoDesc.VS = { vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize() }; - - // Create the input layout - static D3D12_INPUT_ELEMENT_DESC local_layout[] = - { - { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, - }; - psoDesc.InputLayout = { local_layout, 3 }; - } - - // Create the pixel shader - { - static const char* pixelShader = - "struct PS_INPUT\ - {\ - float4 pos : SV_POSITION;\ - float4 col : COLOR0;\ - float2 uv : TEXCOORD0;\ - };\ - SamplerState sampler0 : register(s0);\ - Texture2D texture0 : register(t0);\ - \ - float4 main(PS_INPUT input) : SV_Target\ - {\ - float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \ - return out_col; \ - }"; - - if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_5_0", 0, 0, &pixelShaderBlob, NULL))) - { - vertexShaderBlob->Release(); - return false; // NB: Pass ID3D10Blob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob! - } - psoDesc.PS = { pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize() }; - } - - // Create the blending setup - { - D3D12_BLEND_DESC& desc = psoDesc.BlendState; - desc.AlphaToCoverageEnable = false; - desc.RenderTarget[0].BlendEnable = true; - desc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; - desc.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE; - desc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; - } - - // Create the rasterizer state - { - D3D12_RASTERIZER_DESC& desc = psoDesc.RasterizerState; - desc.FillMode = D3D12_FILL_MODE_SOLID; - desc.CullMode = D3D12_CULL_MODE_NONE; - desc.FrontCounterClockwise = FALSE; - desc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; - desc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; - desc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; - desc.DepthClipEnable = true; - desc.MultisampleEnable = FALSE; - desc.AntialiasedLineEnable = FALSE; - desc.ForcedSampleCount = 0; - desc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; - } - - // Create depth-stencil State - { - D3D12_DEPTH_STENCIL_DESC& desc = psoDesc.DepthStencilState; - desc.DepthEnable = false; - desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; - desc.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; - desc.StencilEnable = false; - desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; - desc.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; - desc.BackFace = desc.FrontFace; - } - - HRESULT result_pipeline_state = g_d3d12_context->GetDevice()->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&bd->pPipelineState)); - vertexShaderBlob->Release(); - pixelShaderBlob->Release(); - if (result_pipeline_state != S_OK) - return false; - - return true; -} - -void ImGui_ImplDX12_DestroyDeviceObjects() -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - if (!bd) - return; - ImGuiIO& io = ImGui::GetIO(); - - SafeRelease(bd->pRootSignature); - SafeRelease(bd->pPipelineState); - bd->FontTexture.Destroy(false); - bd->VertexStreamBuffer.Destroy(false); - bd->IndexStreamBuffer.Destroy(false); - io.Fonts->SetTexID(NULL); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. -} - -bool ImGui_ImplDX12_Init(DXGI_FORMAT rtv_format) -{ - ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplDX12_Data* bd = IM_NEW(ImGui_ImplDX12_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_dx12"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - - bd->RTVFormat = rtv_format; - - if (!bd->VertexStreamBuffer.Create(VERTEX_BUFFER_SIZE) || !bd->IndexStreamBuffer.Create(INDEX_BUFFER_SIZE)) - return false; - - return ImGui_ImplDX12_CreateDeviceObjects(); -} - -void ImGui_ImplDX12_Shutdown() -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - - ImGui_ImplDX12_DestroyDeviceObjects(); - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; - IM_DELETE(bd); -} - -void ImGui_ImplDX12_NewFrame() -{ - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - IM_ASSERT(bd != NULL && "Did you call ImGui_ImplDX12_Init()?"); - - if (!bd->pPipelineState) - ImGui_ImplDX12_CreateDeviceObjects(); -} diff --git a/pcsx2/Frontend/imgui_impl_dx12.h b/pcsx2/Frontend/imgui_impl_dx12.h deleted file mode 100644 index 6776da3740..0000000000 --- a/pcsx2/Frontend/imgui_impl_dx12.h +++ /dev/null @@ -1,14 +0,0 @@ -// dear imgui: Renderer Backend for DirectX12 -// This needs to be used along with a Platform Backend (e.g. Win32) - -#pragma once -#include "imgui.h" // IMGUI_IMPL_API - -bool ImGui_ImplDX12_Init(DXGI_FORMAT rtv_format); -void ImGui_ImplDX12_Shutdown(); -void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data); - -// Use if you want to reset your rendering device without losing Dear ImGui state. -void ImGui_ImplDX12_DestroyDeviceObjects(); -bool ImGui_ImplDX12_CreateDeviceObjects(); -bool ImGui_ImplDX12_CreateFontsTexture(); diff --git a/pcsx2/Frontend/imgui_impl_opengl3.cpp b/pcsx2/Frontend/imgui_impl_opengl3.cpp deleted file mode 100644 index fef9abc86b..0000000000 --- a/pcsx2/Frontend/imgui_impl_opengl3.cpp +++ /dev/null @@ -1,577 +0,0 @@ -// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline -// - Desktop GL: 2.x 3.x 4.x -// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) -// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) - -// Implemented features: -// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! -// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -// CHANGELOG -// (minor and older changes stripped away, please see git history for details) -// 2022-05-13: OpenGL: Fix state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states. -// 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers. -// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal precision floats to avoid wobbly rendering at HD resolutions. -// 2021-08-19: OpenGL: Embed and use our own minimal GL loader (imgui_impl_opengl3_loader.h), removing requirement and support for third-party loader. -// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). -// 2021-06-25: OpenGL: Use OES_vertex_array extension on Emscripten + backup/restore current state. -// 2021-06-21: OpenGL: Destroy individual vertex/fragment shader objects right after they are linked into the main shader. -// 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 version. -// 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) -// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 or greater. -// 2021-02-18: OpenGL: Change blending equation to preserve alpha in output buffer. -// 2021-01-03: OpenGL: Backup, setup and restore GL_STENCIL_TEST state. -// 2020-10-23: OpenGL: Backup, setup and restore GL_PRIMITIVE_RESTART state. -// 2020-10-15: OpenGL: Use glGetString(GL_VERSION) instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero (e.g. Desktop GL 2.x) -// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader. -// 2020-07-10: OpenGL: Added support for glad2 OpenGL loader. -// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. -// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix. -// 2020-04-12: OpenGL: Fixed context version check mistakenly testing for 4.0+ instead of 3.2+ to enable ImGuiBackendFlags_RendererHasVtxOffset. -// 2020-03-24: OpenGL: Added support for glbinding 2.x OpenGL loader. -// 2020-01-07: OpenGL: Added support for glbinding 3.x OpenGL loader. -// 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. -// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. -// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. -// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. -// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. -// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). -// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. -// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. -// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. -// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. -// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". -// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. -// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. -// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. -// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. -// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. -// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. -// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". -// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. -// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. -// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. -// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. -// 2017-05-01: OpenGL: Fixed save and restore of current blend func state. -// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. -// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. -// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) - -//---------------------------------------- -// OpenGL GLSL GLSL -// version version string -//---------------------------------------- -// 2.0 110 "#version 110" -// 2.1 120 "#version 120" -// 3.0 130 "#version 130" -// 3.1 140 "#version 140" -// 3.2 150 "#version 150" -// 3.3 330 "#version 330 core" -// 4.0 400 "#version 400 core" -// 4.1 410 "#version 410 core" -// 4.2 420 "#version 410 core" -// 4.3 430 "#version 430 core" -// ES 2.0 100 "#version 100" = WebGL 1.0 -// ES 3.0 300 "#version 300 es" = WebGL 2.0 -//---------------------------------------- - -#include "PrecompiledHeader.h" - -#include "imgui.h" -#include "imgui_impl_opengl3.h" -#include -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -// Clang warnings with -Weverything -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast -#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness -#if __has_warning("-Wzero-as-null-pointer-constant") -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif -#endif - -// GL includes -#include - -// OpenGL Data -struct ImGui_ImplOpenGL3_Data -{ - GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) - char GlslVersionString[32]; // Specified by user or detected based on compile time GL settings. - GLuint FontTexture; - GLuint ShaderHandle; - GLint AttribLocationTex; // Uniforms location - GLint AttribLocationProjMtx; - GLuint AttribLocationVtxPos; // Vertex attributes location - GLuint AttribLocationVtxUV; - GLuint AttribLocationVtxColor; - unsigned int VboHandle, ElementsHandle, VaoHandle, SamplerHandle; - GLsizeiptr VertexBufferSize; - GLsizeiptr IndexBufferSize; - - ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } -}; - -// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts -// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. -static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() -{ - return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : NULL; -} - -// Functions -bool ImGui_ImplOpenGL3_Init(const char* glsl_version) -{ - ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_opengl3"; - - // Query for GL version (e.g. 320 for GL 3.2) - GLint major = 0; - GLint minor = 0; - glGetIntegerv(GL_MAJOR_VERSION, &major); - glGetIntegerv(GL_MINOR_VERSION, &minor); - if (major == 0 && minor == 0) - { - // Query GL_VERSION in desktop GL 2.x, the string will start with "." - const char* gl_version = (const char*)glGetString(GL_VERSION); - sscanf(gl_version, "%d.%d", &major, &minor); - } - bd->GlVersion = (GLuint)(major * 100 + minor * 10); - - if (bd->GlVersion >= 320) - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - - // Store GLSL version string so we can refer to it later in case we recreate shaders. - // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. - if (glsl_version == NULL) - glsl_version = "#version 130"; - - IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(bd->GlslVersionString)); - strcpy(bd->GlslVersionString, glsl_version); - strcat(bd->GlslVersionString, "\n"); - return ImGui_ImplOpenGL3_CreateDeviceObjects(); -} - -void ImGui_ImplOpenGL3_Shutdown() -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - - ImGui_ImplOpenGL3_DestroyDeviceObjects(); - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; - IM_DELETE(bd); -} - -static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill - glEnable(GL_BLEND); - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glEnable(GL_SCISSOR_TEST); - if (glPolygonMode) - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - // Setup viewport, orthographic projection matrix - // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); - float L = draw_data->DisplayPos.x; - float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; - float T = draw_data->DisplayPos.y; - float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; - 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 }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, - }; - glUseProgram(bd->ShaderHandle); - glUniform1i(bd->AttribLocationTex, 0); - glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); - glBindSampler(0, bd->SamplerHandle); - - // Bind vertex/index buffers and setup attributes for ImDrawVert - glBindVertexArray(bd->VaoHandle); - glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle); - glEnableVertexAttribArray(bd->AttribLocationVtxPos); - glEnableVertexAttribArray(bd->AttribLocationVtxUV); - glEnableVertexAttribArray(bd->AttribLocationVtxColor); - glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); - glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); - glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); -} - -// OpenGL3 Render function. -// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. -// This is in order to be able to run within an OpenGL engine that doesn't do so. -void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) -{ - // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); - if (fb_width <= 0 || fb_height <= 0) - return; - - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - - // Setup desired GL state - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height); - - // Will project scissor/clipping rectangles into framebuffer space - ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) - - // Render command lists - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - - // Upload vertex/index buffers - GLsizeiptr vtx_buffer_size = (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); - GLsizeiptr idx_buffer_size = (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); - if (bd->VertexBufferSize < vtx_buffer_size) - { - bd->VertexBufferSize = vtx_buffer_size; - glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, NULL, GL_STREAM_DRAW); - } - if (bd->IndexBufferSize < idx_buffer_size) - { - bd->IndexBufferSize = idx_buffer_size; - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, NULL, GL_STREAM_DRAW); - } - glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data); - - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback != NULL) - { - // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height); - else - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); - ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // Apply scissor/clipping rectangle (Y is inverted in OpenGL) - glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)); - - // Bind texture, Draw - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); - glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); - } - } - } - - glBindVertexArray(0); -} - -bool ImGui_ImplOpenGL3_CreateFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - - // Build texture atlas - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) - if (bd->FontTexture == 0) - glGenTextures(1, &bd->FontTexture); - - glBindTexture(GL_TEXTURE_2D, bd->FontTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); - return true; -} - -void ImGui_ImplOpenGL3_DestroyFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->FontTexture) - { - glDeleteTextures(1, &bd->FontTexture); - io.Fonts->SetTexID(0); - bd->FontTexture = 0; - } -} - -// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. -static bool CheckShader(GLuint handle, const char* desc) -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - GLint status = 0, log_length = 0; - glGetShaderiv(handle, GL_COMPILE_STATUS, &status); - glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); - if ((GLboolean)status == GL_FALSE) - fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s! With GLSL: %s\n", desc, bd->GlslVersionString); - if (log_length > 1) - { - ImVector buf; - buf.resize((int)(log_length + 1)); - glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); - fprintf(stderr, "%s\n", buf.begin()); - } - return (GLboolean)status == GL_TRUE; -} - -// If you get an error please report on GitHub. You may try different GL context version or GLSL version. -static bool CheckProgram(GLuint handle, const char* desc) -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - GLint status = 0, log_length = 0; - glGetProgramiv(handle, GL_LINK_STATUS, &status); - glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); - if ((GLboolean)status == GL_FALSE) - fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! With GLSL %s\n", desc, bd->GlslVersionString); - if (log_length > 1) - { - ImVector buf; - buf.resize((int)(log_length + 1)); - glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); - fprintf(stderr, "%s\n", buf.begin()); - } - return (GLboolean)status == GL_TRUE; -} - -bool ImGui_ImplOpenGL3_CreateDeviceObjects() -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - - // Parse GLSL version string - int glsl_version = 130; - sscanf(bd->GlslVersionString, "#version %d", &glsl_version); - - const GLchar* vertex_shader_glsl_120 = - "uniform mat4 ProjMtx;\n" - "attribute vec2 Position;\n" - "attribute vec2 UV;\n" - "attribute vec4 Color;\n" - "varying vec2 Frag_UV;\n" - "varying vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" - "}\n"; - - const GLchar* vertex_shader_glsl_130 = - "uniform mat4 ProjMtx;\n" - "in vec2 Position;\n" - "in vec2 UV;\n" - "in vec4 Color;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" - "}\n"; - - const GLchar* vertex_shader_glsl_300_es = - "precision highp float;\n" - "layout (location = 0) in vec2 Position;\n" - "layout (location = 1) in vec2 UV;\n" - "layout (location = 2) in vec4 Color;\n" - "uniform mat4 ProjMtx;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" - "}\n"; - - const GLchar* vertex_shader_glsl_410_core = - "layout (location = 0) in vec2 Position;\n" - "layout (location = 1) in vec2 UV;\n" - "layout (location = 2) in vec4 Color;\n" - "uniform mat4 ProjMtx;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" - "}\n"; - - const GLchar* fragment_shader_glsl_120 = - "#ifdef GL_ES\n" - " precision mediump float;\n" - "#endif\n" - "uniform sampler2D Texture;\n" - "varying vec2 Frag_UV;\n" - "varying vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" - "}\n"; - - const GLchar* fragment_shader_glsl_130 = - "uniform sampler2D Texture;\n" - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "out vec4 Out_Color;\n" - "void main()\n" - "{\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" - "}\n"; - - const GLchar* fragment_shader_glsl_300_es = - "precision mediump float;\n" - "uniform sampler2D Texture;\n" - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "layout (location = 0) out vec4 Out_Color;\n" - "void main()\n" - "{\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" - "}\n"; - - const GLchar* fragment_shader_glsl_410_core = - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "uniform sampler2D Texture;\n" - "layout (location = 0) out vec4 Out_Color;\n" - "void main()\n" - "{\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" - "}\n"; - - // Select shaders matching our GLSL versions - const GLchar* vertex_shader = NULL; - const GLchar* fragment_shader = NULL; - if (glsl_version < 130) - { - vertex_shader = vertex_shader_glsl_120; - fragment_shader = fragment_shader_glsl_120; - } - else if (glsl_version >= 410) - { - vertex_shader = vertex_shader_glsl_410_core; - fragment_shader = fragment_shader_glsl_410_core; - } - else if (glsl_version == 300) - { - vertex_shader = vertex_shader_glsl_300_es; - fragment_shader = fragment_shader_glsl_300_es; - } - else - { - vertex_shader = vertex_shader_glsl_130; - fragment_shader = fragment_shader_glsl_130; - } - - // Create shaders - const GLchar* vertex_shader_with_version[2] = { bd->GlslVersionString, vertex_shader }; - GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vert_handle, 2, vertex_shader_with_version, NULL); - glCompileShader(vert_handle); - CheckShader(vert_handle, "vertex shader"); - - const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader }; - GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(frag_handle, 2, fragment_shader_with_version, NULL); - glCompileShader(frag_handle); - CheckShader(frag_handle, "fragment shader"); - - // Link - bd->ShaderHandle = glCreateProgram(); - glAttachShader(bd->ShaderHandle, vert_handle); - glAttachShader(bd->ShaderHandle, frag_handle); - glLinkProgram(bd->ShaderHandle); - CheckProgram(bd->ShaderHandle, "shader program"); - - glDetachShader(bd->ShaderHandle, vert_handle); - glDetachShader(bd->ShaderHandle, frag_handle); - glDeleteShader(vert_handle); - glDeleteShader(frag_handle); - - bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture"); - bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx"); - bd->AttribLocationVtxPos = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position"); - bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV"); - bd->AttribLocationVtxColor = (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color"); - - // Create buffers - glGenBuffers(1, &bd->VboHandle); - glGenBuffers(1, &bd->ElementsHandle); - glGenVertexArrays(1, &bd->VaoHandle); - - // Create sampler - if (bd->SamplerHandle == 0) - { - glGenSamplers(1, &bd->SamplerHandle); - glSamplerParameteri(bd->SamplerHandle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glSamplerParameteri(bd->SamplerHandle, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glSamplerParameteri(bd->SamplerHandle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glSamplerParameteri(bd->SamplerHandle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - - return true; -} - -void ImGui_ImplOpenGL3_DestroyDeviceObjects() -{ - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->VaoHandle) { glDeleteVertexArrays(1, &bd->VaoHandle); bd->VaoHandle = 0; } - if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } - if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } - if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } - if (bd->SamplerHandle) { glDeleteSamplers(1, &bd->SamplerHandle); bd->SamplerHandle = 0; } - ImGui_ImplOpenGL3_DestroyFontsTexture(); -} - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif diff --git a/pcsx2/Frontend/imgui_impl_opengl3.h b/pcsx2/Frontend/imgui_impl_opengl3.h deleted file mode 100644 index 0549fe3cab..0000000000 --- a/pcsx2/Frontend/imgui_impl_opengl3.h +++ /dev/null @@ -1,16 +0,0 @@ -// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline -// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) - -#pragma once -#include "imgui.h" // IMGUI_IMPL_API - -// Backend API -bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); -void ImGui_ImplOpenGL3_Shutdown(); -void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); - -// (Optional) Called by Init/NewFrame/Shutdown -bool ImGui_ImplOpenGL3_CreateFontsTexture(); -void ImGui_ImplOpenGL3_DestroyFontsTexture(); -bool ImGui_ImplOpenGL3_CreateDeviceObjects(); -void ImGui_ImplOpenGL3_DestroyDeviceObjects(); diff --git a/pcsx2/Frontend/imgui_impl_vulkan.cpp b/pcsx2/Frontend/imgui_impl_vulkan.cpp deleted file mode 100644 index 17689e4a45..0000000000 --- a/pcsx2/Frontend/imgui_impl_vulkan.cpp +++ /dev/null @@ -1,706 +0,0 @@ -// dear imgui: Renderer Backend for Vulkan -// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) - -// Implemented features: -// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. - -// Important: on 32-bit systems, user texture binding is only supported if your imconfig file has '#define ImTextureID ImU64'. -// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. -// To build this on 32-bit systems and support texture changes: -// - [Solution 1] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'ImTextureID=ImU64' (this is what we do in our .vcxproj files) -// - [Solution 2] IDE/msbuild: in "Properties/C++/Preprocessor Definitions" add 'IMGUI_USER_CONFIG="my_imgui_config.h"' and inside 'my_imgui_config.h' add '#define ImTextureID ImU64' and as many other options as you like. -// - [Solution 3] IDE/msbuild: edit imconfig.h and add '#define ImTextureID ImU64' (prefer solution 2 to create your own config file!) -// - [Solution 4] command-line: add '/D ImTextureID=ImU64' to your cl.exe command-line (this is what we do in our batch files) - -// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. -// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. -// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. -// Read online: https://github.com/ocornut/imgui/tree/master/docs - -// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. -// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ - -// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. -// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. -// You will use those if you want to use this rendering backend in your engine/app. -// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by -// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. -// Read comments in imgui_impl_vulkan.h. - -// CHANGELOG -// (minor and older changes stripped away, please see git history for details) -// 2021-10-15: Vulkan: Call vkCmdSetScissor() at the end of render a full-viewport to reduce likehood of issues with people using VK_DYNAMIC_STATE_SCISSOR in their app without calling vkCmdSetScissor() explicitly every frame. -// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). -// 2021-03-22: Vulkan: Fix mapped memory validation error when buffer sizes are not multiple of VkPhysicalDeviceLimits::nonCoherentAtomSize. -// 2021-02-18: Vulkan: Change blending equation to preserve alpha in output buffer. -// 2021-01-27: Vulkan: Added support for custom function load and IMGUI_IMPL_VULKAN_NO_PROTOTYPES by using ImGui_ImplVulkan_LoadFunctions(). -// 2020-11-11: Vulkan: Added support for specifying which subpass to reference during VkPipeline creation. -// 2020-09-07: Vulkan: Added VkPipeline parameter to ImGui_ImplVulkan_RenderDrawData (default to one passed to ImGui_ImplVulkan_Init). -// 2020-05-04: Vulkan: Fixed crash if initial frame has no vertices. -// 2020-04-26: Vulkan: Fixed edge case where render callbacks wouldn't be called if the ImDrawData didn't have vertices. -// 2019-08-01: Vulkan: Added support for specifying multisample count. Set ImGui_ImplVulkan_InitInfo::MSAASamples to one of the VkSampleCountFlagBits values to use, default is non-multisampled as before. -// 2019-05-29: Vulkan: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: Vulkan: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2019-04-04: *BREAKING CHANGE*: Vulkan: Added ImageCount/MinImageCount fields in ImGui_ImplVulkan_InitInfo, required for initialization (was previously a hard #define IMGUI_VK_QUEUED_FRAMES 2). Added ImGui_ImplVulkan_SetMinImageCount(). -// 2019-04-04: Vulkan: Added VkInstance argument to ImGui_ImplVulkanH_CreateWindow() optional helper. -// 2019-04-04: Vulkan: Avoid passing negative coordinates to vkCmdSetScissor, which debug validation layers do not like. -// 2019-04-01: Vulkan: Support for 32-bit index buffer (#define ImDrawIdx unsigned int). -// 2019-02-16: Vulkan: Viewport and clipping rectangles correctly using draw_data->FramebufferScale to allow retina display. -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-08-25: Vulkan: Fixed mishandled VkSurfaceCapabilitiesKHR::maxImageCount=0 case. -// 2018-06-22: Inverted the parameters to ImGui_ImplVulkan_RenderDrawData() to be consistent with other backends. -// 2018-06-08: Misc: Extracted imgui_impl_vulkan.cpp/.h away from the old combined GLFW+Vulkan example. -// 2018-06-08: Vulkan: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. -// 2018-03-03: Vulkan: Various refactor, created a couple of ImGui_ImplVulkanH_XXX helper that the example can use and that viewport support will use. -// 2018-03-01: Vulkan: Renamed ImGui_ImplVulkan_Init_Info to ImGui_ImplVulkan_InitInfo and fields to match more closely Vulkan terminology. -// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback, ImGui_ImplVulkan_Render() calls ImGui_ImplVulkan_RenderDrawData() itself. -// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. -// 2017-05-15: Vulkan: Fix scissor offset being negative. Fix new Vulkan validation warnings. Set required depth member for buffer image copy. -// 2016-11-13: Vulkan: Fix validation layer warnings and errors and redeclare gl_PerVertex. -// 2016-10-18: Vulkan: Add location decorators & change to use structs as in/out in glsl, update embedded spv (produced with glslangValidator -x). Null the released resources. -// 2016-08-27: Vulkan: Fix Vulkan example for use when a depth buffer is active. - -#include "imgui_impl_vulkan.h" - -#include "common/Vulkan/Builders.h" -#include "common/Vulkan/Context.h" -#include "common/Vulkan/Texture.h" -#include "common/Vulkan/StreamBuffer.h" -#include "common/Vulkan/Util.h" - -#include -#include - -// Visual Studio warnings -#ifdef _MSC_VER -#pragma warning (disable: 4127) // condition expression is constant -#endif - -// If we're doing more than this... wtf? -static constexpr u32 VERTEX_BUFFER_SIZE = 8 * 1024 * 1024; -static constexpr u32 INDEX_BUFFER_SIZE = 4 * 1024 * 1024; - -// Vulkan data -struct ImGui_ImplVulkan_Data -{ - VkRenderPass RenderPass = VK_NULL_HANDLE; - VkPipelineCreateFlags PipelineCreateFlags = 0; - VkDescriptorSetLayout DescriptorSetLayout = VK_NULL_HANDLE; - VkPipelineLayout PipelineLayout = VK_NULL_HANDLE; - VkPipeline Pipeline = VK_NULL_HANDLE; - VkShaderModule ShaderModuleVert = VK_NULL_HANDLE; - VkShaderModule ShaderModuleFrag = VK_NULL_HANDLE; - - VkSampler FontSampler = VK_NULL_HANDLE; - - Vulkan::StreamBuffer VertexStreamBuffer; - Vulkan::StreamBuffer IndexStreamBuffer; - Vulkan::Texture FontTexture; -}; - -// Forward Declarations -static bool ImGui_ImplVulkan_CreateDeviceObjects(); -static void ImGui_ImplVulkan_DestroyDeviceObjects(); - -//----------------------------------------------------------------------------- -// SHADERS -//----------------------------------------------------------------------------- - -// glsl_shader.vert, compiled with: -// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert -/* -#version 450 core -layout(location = 0) in vec2 aPos; -layout(location = 1) in vec2 aUV; -layout(location = 2) in vec4 aColor; -layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; - -out gl_PerVertex { vec4 gl_Position; }; -layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; - -void main() -{ - Out.Color = aColor; - Out.UV = aUV; - gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); -} -*/ -static uint32_t __glsl_shader_vert_spv[] = -{ - 0x07230203,0x00010000,0x00080001,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, - 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, - 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, - 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, - 0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43, - 0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f, - 0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005, - 0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000, - 0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00040005,0x0000001c, - 0x736f5061,0x00000000,0x00060005,0x0000001e,0x73755075,0x6e6f4368,0x6e617473,0x00000074, - 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, - 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, - 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, - 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, - 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, - 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, - 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, - 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, - 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, - 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, - 0x0000000c,0x00000020,0x00000001,0x0004002b,0x0000000c,0x0000000d,0x00000000,0x00040020, - 0x0000000e,0x00000001,0x00000007,0x0004003b,0x0000000e,0x0000000f,0x00000001,0x00040020, - 0x00000011,0x00000003,0x00000007,0x0004002b,0x0000000c,0x00000013,0x00000001,0x00040020, - 0x00000014,0x00000001,0x00000008,0x0004003b,0x00000014,0x00000015,0x00000001,0x00040020, - 0x00000017,0x00000003,0x00000008,0x0003001e,0x00000019,0x00000007,0x00040020,0x0000001a, - 0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004003b,0x00000014, - 0x0000001c,0x00000001,0x0004001e,0x0000001e,0x00000008,0x00000008,0x00040020,0x0000001f, - 0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040020,0x00000021, - 0x00000009,0x00000008,0x0004002b,0x00000006,0x00000028,0x00000000,0x0004002b,0x00000006, - 0x00000029,0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, - 0x00000005,0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012, - 0x0000000b,0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016, - 0x00000015,0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018, - 0x00000016,0x0004003d,0x00000008,0x0000001d,0x0000001c,0x00050041,0x00000021,0x00000022, - 0x00000020,0x0000000d,0x0004003d,0x00000008,0x00000023,0x00000022,0x00050085,0x00000008, - 0x00000024,0x0000001d,0x00000023,0x00050041,0x00000021,0x00000025,0x00000020,0x00000013, - 0x0004003d,0x00000008,0x00000026,0x00000025,0x00050081,0x00000008,0x00000027,0x00000024, - 0x00000026,0x00050051,0x00000006,0x0000002a,0x00000027,0x00000000,0x00050051,0x00000006, - 0x0000002b,0x00000027,0x00000001,0x00070050,0x00000007,0x0000002c,0x0000002a,0x0000002b, - 0x00000028,0x00000029,0x00050041,0x00000011,0x0000002d,0x0000001b,0x0000000d,0x0003003e, - 0x0000002d,0x0000002c,0x000100fd,0x00010038 -}; - -// glsl_shader.frag, compiled with: -// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag -/* -#version 450 core -layout(location = 0) out vec4 fColor; -layout(set=0, binding=0) uniform sampler2D sTexture; -layout(location = 0) in struct { vec4 Color; vec2 UV; } In; -void main() -{ - fColor = In.Color * texture(sTexture, In.UV.st); -} -*/ -static uint32_t __glsl_shader_frag_spv[] = -{ - 0x07230203,0x00010000,0x00080001,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, - 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, - 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, - 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, - 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, - 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, - 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, - 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, - 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, - 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, - 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, - 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, - 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, - 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, - 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, - 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, - 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, - 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, - 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, - 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, - 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, - 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, - 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, - 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, - 0x00010038 -}; - -//----------------------------------------------------------------------------- -// FUNCTIONS -//----------------------------------------------------------------------------- - -// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts -// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts. -// FIXME: multi-context support is not tested and probably dysfunctional in this backend. -static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() -{ - return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : NULL; -} - -static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pipeline, VkCommandBuffer command_buffer, int fb_width, int fb_height) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - - // Bind pipeline: - { - vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - } - - // Bind Vertex And Index Buffer: - if (draw_data->TotalVtxCount > 0) - { - VkBuffer vertex_buffers[1] = { bd->VertexStreamBuffer.GetBuffer() }; - VkDeviceSize vertex_offset[1] = { bd->VertexStreamBuffer.GetCurrentOffset() }; - vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, vertex_offset); - vkCmdBindIndexBuffer(command_buffer, bd->IndexStreamBuffer.GetBuffer(), bd->IndexStreamBuffer.GetCurrentOffset(), sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); - } - - // Setup viewport: - { - VkViewport viewport; - viewport.x = 0; - viewport.y = 0; - viewport.width = (float)fb_width; - viewport.height = (float)fb_height; - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - vkCmdSetViewport(command_buffer, 0, 1, &viewport); - } - - // Setup scale and translation: - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - { - float scale[2]; - scale[0] = 2.0f / draw_data->DisplaySize.x; - scale[1] = 2.0f / draw_data->DisplaySize.y; - float translate[2]; - translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; - translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); - } -} - -// Render function -void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data) -{ - // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); - if (fb_width <= 0 || fb_height <= 0) - return; - - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (draw_data->TotalVtxCount > 0) - { - // Create or resize the vertex/index buffers - const size_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); - const size_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); - if (!bd->VertexStreamBuffer.ReserveMemory(vertex_size, sizeof(ImDrawVert)) || - !bd->IndexStreamBuffer.ReserveMemory(index_size, sizeof(ImDrawIdx))) - { - // this is annoying, because we can't restart the render pass... - return; - } - - // Upload vertex/index data into a single contiguous GPU buffer - ImDrawVert* vtx_dst = (ImDrawVert*)bd->VertexStreamBuffer.GetCurrentHostPointer(); - ImDrawIdx* idx_dst = (ImDrawIdx*)bd->IndexStreamBuffer.GetCurrentHostPointer(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); - memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - vtx_dst += cmd_list->VtxBuffer.Size; - idx_dst += cmd_list->IdxBuffer.Size; - } - - // Setup desired Vulkan state (must come before buffer commit) - ImGui_ImplVulkan_SetupRenderState(draw_data, bd->Pipeline, g_vulkan_context->GetCurrentCommandBuffer(), fb_width, fb_height); - bd->VertexStreamBuffer.CommitMemory(vertex_size); - bd->IndexStreamBuffer.CommitMemory(index_size); - } - - // Will project scissor/clipping rectangles into framebuffer space - ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - int global_vtx_offset = 0; - int global_idx_offset = 0; - const Vulkan::Texture* last_texture = nullptr; - VkCommandBuffer command_buffer = g_vulkan_context->GetCurrentCommandBuffer(); - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback != NULL) - { - // User callback, registered via ImDrawList::AddCallback() - // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) - if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) - ImGui_ImplVulkan_SetupRenderState(draw_data, bd->Pipeline, command_buffer, fb_width, fb_height); - else - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); - ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); - - // Clamp to viewport as vkCmdSetScissor() won't accept values that are off bounds - if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } - if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } - if (clip_max.x > fb_width) { clip_max.x = (float)fb_width; } - if (clip_max.y > fb_height) { clip_max.y = (float)fb_height; } - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // Apply scissor/clipping rectangle - VkRect2D scissor; - scissor.offset.x = (int32_t)(clip_min.x); - scissor.offset.y = (int32_t)(clip_min.y); - scissor.extent.width = (uint32_t)(clip_max.x - clip_min.x); - scissor.extent.height = (uint32_t)(clip_max.y - clip_min.y); - vkCmdSetScissor(command_buffer, 0, 1, &scissor); - - // Bind DescriptorSet with font or user texture - const Vulkan::Texture* tex = (const Vulkan::Texture*)pcmd->TextureId; - if (tex && last_texture != tex) - { - // if we can't get a descriptor set, we'll we're in trouble, since we can't restart the render pass from here. - VkDescriptorSet ds = g_vulkan_context->AllocateDescriptorSet(bd->DescriptorSetLayout); - if (ds == VK_NULL_HANDLE) - { - continue; - } - - Vulkan::DescriptorSetUpdateBuilder dsb; - dsb.AddCombinedImageSamplerDescriptorWrite(ds, 0, tex->GetView(), bd->FontSampler); - dsb.Update(g_vulkan_context->GetDevice()); - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &ds, 0, nullptr); - last_texture = tex; - } - - // Draw - vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); - } - } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; - } - - // Note: at this point both vkCmdSetViewport() and vkCmdSetScissor() have been called. - // Our last values will leak into user/application rendering IF: - // - Your app uses a pipeline with VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR dynamic state - // - And you forgot to call vkCmdSetViewport() and vkCmdSetScissor() yourself to explicitely set that state. - // If you use VK_DYNAMIC_STATE_VIEWPORT or VK_DYNAMIC_STATE_SCISSOR you are responsible for setting the values before rendering. - // In theory we should aim to backup/restore those values but I am not sure this is possible. - // We perform a call to vkCmdSetScissor() to set back a full viewport which is likely to fix things for 99% users but technically this is not perfect. (See github #4644) - VkRect2D scissor = { { 0, 0 }, { (uint32_t)fb_width, (uint32_t)fb_height } }; - vkCmdSetScissor(command_buffer, 0, 1, &scissor); -} - -bool ImGui_ImplVulkan_CreateFontsTexture() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - if (bd->FontTexture.GetWidth() != static_cast(width) || bd->FontTexture.GetHeight() != static_cast(height)) - { - if (!bd->FontTexture.Create(width, height, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, - VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)) - { - return false; - } - } - - const size_t upload_size = width * height * 4 * sizeof(unsigned char); - const VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, 0, - static_cast(upload_size), VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_SHARING_MODE_EXCLUSIVE, 0, nullptr}; - VmaAllocationCreateInfo aci = {}; - aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; - aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; - - VmaAllocationInfo ai; - VkBuffer buffer; - VmaAllocation allocation; - VkResult res = vmaCreateBuffer(g_vulkan_context->GetAllocator(), &bci, &aci, &buffer, &allocation, &ai); - if (res != VK_SUCCESS) - return false; - - std::memcpy(ai.pMappedData, pixels, upload_size); - vmaFlushAllocation(g_vulkan_context->GetAllocator(), allocation, 0, upload_size); - bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - bd->FontTexture.UpdateFromBuffer(g_vulkan_context->GetCurrentInitCommandBuffer(), 0, 0, 0, 0, width, height, height, width, buffer, 0); - bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentInitCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - // Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy. - g_vulkan_context->DeferBufferDestruction(buffer, allocation); - - // Store our identifier - io.Fonts->SetTexID((ImTextureID)&bd->FontTexture); - return true; -} - -static bool ImGui_ImplVulkan_CreateShaderModules(VkDevice device) -{ - // Create the shader modules - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->ShaderModuleVert == VK_NULL_HANDLE) - { - VkShaderModuleCreateInfo vert_info = {}; - vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - vert_info.codeSize = sizeof(__glsl_shader_vert_spv); - vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; - VkResult err = vkCreateShaderModule(device, &vert_info, nullptr, &bd->ShaderModuleVert); - if (err != VK_SUCCESS) - return false; - } - if (bd->ShaderModuleFrag == VK_NULL_HANDLE) - { - VkShaderModuleCreateInfo frag_info = {}; - frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - frag_info.codeSize = sizeof(__glsl_shader_frag_spv); - frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; - VkResult err = vkCreateShaderModule(device, &frag_info, nullptr, &bd->ShaderModuleFrag); - if (err != VK_SUCCESS) - return false; - } - - return true; -} - -static bool ImGui_ImplVulkan_CreateFontSampler(VkDevice device) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->FontSampler) - return true; - - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. - VkSamplerCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - info.magFilter = VK_FILTER_LINEAR; - info.minFilter = VK_FILTER_LINEAR; - info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; - info.minLod = -1000; - info.maxLod = 1000; - info.maxAnisotropy = 1.0f; - VkResult err = vkCreateSampler(device, &info, nullptr, &bd->FontSampler); - return (err == VK_SUCCESS); -} - -static bool ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDevice device) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->DescriptorSetLayout) - return true; - - if (!ImGui_ImplVulkan_CreateFontSampler(device)) - return false; - - VkSampler sampler[1] = { bd->FontSampler }; - VkDescriptorSetLayoutBinding binding[1] = {}; - binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - binding[0].descriptorCount = 1; - binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - binding[0].pImmutableSamplers = sampler; - VkDescriptorSetLayoutCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - info.bindingCount = 1; - info.pBindings = binding; - VkResult err = vkCreateDescriptorSetLayout(device, &info, nullptr, &bd->DescriptorSetLayout); - return (err == VK_SUCCESS); -} - -static bool ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (bd->PipelineLayout) - return true; - - // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix - ImGui_ImplVulkan_CreateDescriptorSetLayout(device); - VkPushConstantRange push_constants[1] = {}; - push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - push_constants[0].offset = sizeof(float) * 0; - push_constants[0].size = sizeof(float) * 4; - VkDescriptorSetLayout set_layout[1] = { bd->DescriptorSetLayout }; - VkPipelineLayoutCreateInfo layout_info = {}; - layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layout_info.setLayoutCount = 1; - layout_info.pSetLayouts = set_layout; - layout_info.pushConstantRangeCount = 1; - layout_info.pPushConstantRanges = push_constants; - VkResult err = vkCreatePipelineLayout(device, &layout_info, nullptr, &bd->PipelineLayout); - return (err == VK_SUCCESS); -} - -static bool ImGui_ImplVulkan_CreatePipeline(VkDevice device, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkPipeline* pipeline) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - if (!ImGui_ImplVulkan_CreateShaderModules(device)) - return false; - - VkPipelineShaderStageCreateInfo stage[2] = {}; - stage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stage[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - stage[0].module = bd->ShaderModuleVert; - stage[0].pName = "main"; - stage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - stage[1].module = bd->ShaderModuleFrag; - stage[1].pName = "main"; - - VkVertexInputBindingDescription binding_desc[1] = {}; - binding_desc[0].stride = sizeof(ImDrawVert); - binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription attribute_desc[3] = {}; - attribute_desc[0].location = 0; - attribute_desc[0].binding = binding_desc[0].binding; - attribute_desc[0].format = VK_FORMAT_R32G32_SFLOAT; - attribute_desc[0].offset = IM_OFFSETOF(ImDrawVert, pos); - attribute_desc[1].location = 1; - attribute_desc[1].binding = binding_desc[0].binding; - attribute_desc[1].format = VK_FORMAT_R32G32_SFLOAT; - attribute_desc[1].offset = IM_OFFSETOF(ImDrawVert, uv); - attribute_desc[2].location = 2; - attribute_desc[2].binding = binding_desc[0].binding; - attribute_desc[2].format = VK_FORMAT_R8G8B8A8_UNORM; - attribute_desc[2].offset = IM_OFFSETOF(ImDrawVert, col); - - VkPipelineVertexInputStateCreateInfo vertex_info = {}; - vertex_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - vertex_info.vertexBindingDescriptionCount = 1; - vertex_info.pVertexBindingDescriptions = binding_desc; - vertex_info.vertexAttributeDescriptionCount = 3; - vertex_info.pVertexAttributeDescriptions = attribute_desc; - - VkPipelineInputAssemblyStateCreateInfo ia_info = {}; - ia_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; - ia_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - - VkPipelineViewportStateCreateInfo viewport_info = {}; - viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - viewport_info.viewportCount = 1; - viewport_info.scissorCount = 1; - - VkPipelineRasterizationStateCreateInfo raster_info = {}; - raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - raster_info.polygonMode = VK_POLYGON_MODE_FILL; - raster_info.cullMode = VK_CULL_MODE_NONE; - raster_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; - raster_info.lineWidth = 1.0f; - - VkPipelineMultisampleStateCreateInfo ms_info = {}; - ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; - ms_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - - VkPipelineColorBlendAttachmentState color_attachment[1] = {}; - color_attachment[0].blendEnable = VK_TRUE; - color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; - color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - color_attachment[0].colorBlendOp = VK_BLEND_OP_ADD; - color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - color_attachment[0].alphaBlendOp = VK_BLEND_OP_ADD; - color_attachment[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - - VkPipelineDepthStencilStateCreateInfo depth_info = {}; - depth_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; - - VkPipelineColorBlendStateCreateInfo blend_info = {}; - blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - blend_info.attachmentCount = 1; - blend_info.pAttachments = color_attachment; - - VkDynamicState dynamic_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - VkPipelineDynamicStateCreateInfo dynamic_state = {}; - dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamic_state.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states); - dynamic_state.pDynamicStates = dynamic_states; - - if (!ImGui_ImplVulkan_CreatePipelineLayout(device)) - return false; - - VkGraphicsPipelineCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - info.flags = bd->PipelineCreateFlags; - info.stageCount = 2; - info.pStages = stage; - info.pVertexInputState = &vertex_info; - info.pInputAssemblyState = &ia_info; - info.pViewportState = &viewport_info; - info.pRasterizationState = &raster_info; - info.pMultisampleState = &ms_info; - info.pDepthStencilState = &depth_info; - info.pColorBlendState = &blend_info; - info.pDynamicState = &dynamic_state; - info.layout = bd->PipelineLayout; - info.renderPass = renderPass; - info.subpass = 0; - VkResult err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &info, nullptr, pipeline); - return (err == VK_SUCCESS); -} - -bool ImGui_ImplVulkan_CreateDeviceObjects() -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - - if (!bd->VertexStreamBuffer.Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VERTEX_BUFFER_SIZE) || - !bd->IndexStreamBuffer.Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_BUFFER_SIZE)) - { - return false; - } - - if (!ImGui_ImplVulkan_CreatePipeline(g_vulkan_context->GetDevice(), VK_NULL_HANDLE, bd->RenderPass, &bd->Pipeline)) - return false; - - return true; -} - -void ImGui_ImplVulkan_DestroyDeviceObjects() -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - - bd->VertexStreamBuffer.Destroy(false); - bd->IndexStreamBuffer.Destroy(false); - bd->FontTexture.Destroy(false); - - if (bd->ShaderModuleVert) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), bd->ShaderModuleVert, nullptr); bd->ShaderModuleVert = VK_NULL_HANDLE; } - if (bd->ShaderModuleFrag) { vkDestroyShaderModule(g_vulkan_context->GetDevice(), bd->ShaderModuleFrag, nullptr); bd->ShaderModuleFrag = VK_NULL_HANDLE; } - if (bd->FontSampler) { vkDestroySampler(g_vulkan_context->GetDevice(), bd->FontSampler, nullptr); bd->FontSampler = VK_NULL_HANDLE; } - if (bd->DescriptorSetLayout) { vkDestroyDescriptorSetLayout(g_vulkan_context->GetDevice(), bd->DescriptorSetLayout, nullptr); bd->DescriptorSetLayout = VK_NULL_HANDLE; } - if (bd->PipelineLayout) { vkDestroyPipelineLayout(g_vulkan_context->GetDevice(), bd->PipelineLayout, nullptr); bd->PipelineLayout = VK_NULL_HANDLE; } - if (bd->Pipeline) { vkDestroyPipeline(g_vulkan_context->GetDevice(), bd->Pipeline, nullptr); bd->Pipeline = VK_NULL_HANDLE; } -} - -bool ImGui_ImplVulkan_Init(VkRenderPass render_pass) -{ - ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!"); - - // Setup backend capabilities flags - ImGui_ImplVulkan_Data* bd = IM_NEW(ImGui_ImplVulkan_Data)(); - io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_vulkan"; - io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. - - IM_ASSERT(render_pass != VK_NULL_HANDLE); - - bd->RenderPass = render_pass; - - return ImGui_ImplVulkan_CreateDeviceObjects(); -} - -void ImGui_ImplVulkan_Shutdown() -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - IM_ASSERT(bd != NULL && "No renderer backend to shutdown, or already shutdown?"); - ImGuiIO& io = ImGui::GetIO(); - - ImGui_ImplVulkan_DestroyDeviceObjects(); - io.BackendRendererName = NULL; - io.BackendRendererUserData = NULL; - IM_DELETE(bd); -} diff --git a/pcsx2/Frontend/imgui_impl_vulkan.h b/pcsx2/Frontend/imgui_impl_vulkan.h deleted file mode 100644 index e6732431c6..0000000000 --- a/pcsx2/Frontend/imgui_impl_vulkan.h +++ /dev/null @@ -1,13 +0,0 @@ -// dear imgui: Renderer Backend for Vulkan -// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) - -#pragma once -#include "imgui.h" // IMGUI_IMPL_API -#include "common/Vulkan/Loader.h" - -// Called by user code -bool ImGui_ImplVulkan_Init(VkRenderPass render_pass); -void ImGui_ImplVulkan_Shutdown(); -void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data); -bool ImGui_ImplVulkan_CreateFontsTexture(); -void ImGui_ImplVulkan_DestroyFontUploadObjects(); diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 6adcfd04a9..d91a9f18c5 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -32,8 +32,8 @@ #include "common/StringUtil.h" #include "pcsx2/Config.h" #include "pcsx2/Counters.h" +#include "pcsx2/Frontend/ImGuiManager.h" #include "pcsx2/Host.h" -#include "pcsx2/HostDisplay.h" #include "pcsx2/HostSettings.h" #include "pcsx2/Frontend/FullscreenUI.h" #include "pcsx2/Frontend/InputManager.h" @@ -106,27 +106,6 @@ void GSshutdown() GSJoinSnapshotThreads(); } -void GSclose() -{ - GSTextureReplacements::Shutdown(); - - if (g_gs_renderer) - { - g_gs_renderer->Destroy(); - g_gs_renderer.reset(); - } - if (g_gs_device) - { - g_gs_device->Destroy(); - g_gs_device.reset(); - } - - if (g_host_display) - g_host_display->SetGPUTimingEnabled(false); - - Host::ReleaseHostDisplay(true); -} - static RenderAPI GetAPIForRenderer(GSRendererType renderer) { switch (renderer) @@ -155,11 +134,55 @@ static RenderAPI GetAPIForRenderer(GSRendererType renderer) } } -static bool DoGSOpen(GSRendererType renderer, u8* basemem) +static void UpdateExclusiveFullscreen(bool force_off) { - s_render_api = g_host_display->GetRenderAPI(); + if (!g_gs_device->SupportsExclusiveFullscreen()) + return; - switch (g_host_display->GetRenderAPI()) + std::string fullscreen_mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""); + u32 width, height; + float refresh_rate; + + const bool wants_fullscreen = !force_off && Host::IsFullscreen() && !fullscreen_mode.empty() && + GSDevice::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate); + const bool is_fullscreen = g_gs_device->IsExclusiveFullscreen(); + if (wants_fullscreen == is_fullscreen) + return; + + if (wants_fullscreen) + { + Console.WriteLn("Trying to acquire exclusive fullscreen..."); + if (g_gs_device->SetExclusiveFullscreen(true, width, height, refresh_rate)) + { + Host::AddKeyedOSDMessage( + "UpdateExclusiveFullscreen", "Acquired exclusive fullscreen.", Host::OSD_INFO_DURATION); + } + else + { + Host::AddKeyedOSDMessage( + "UpdateExclusiveFullscreen", "Failed to acquire exclusive fullscreen.", Host::OSD_WARNING_DURATION); + } + } + else + { + Console.WriteLn("Leaving exclusive fullscreen..."); + g_gs_device->SetExclusiveFullscreen(false, 0, 0, 0.0f); + Host::AddKeyedOSDMessage("UpdateExclusiveFullscreen", "Lost exclusive fullscreen.", Host::OSD_INFO_DURATION); + } +} + +static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail) +{ + s_render_api = GetAPIForRenderer(renderer); + + std::optional wi = Host::AcquireRenderWindow(s_render_api); + if (!wi.has_value()) + { + Console.Error("Failed to acquire render window."); + return false; + } + + switch (s_render_api) { #ifdef _WIN32 case RenderAPI::D3D11: @@ -188,49 +211,93 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem) #endif default: - Console.Error("Unknown render API %u", static_cast(g_host_display->GetRenderAPI())); + Console.Error("Unsupported render API %s", GSDevice::RenderAPIToString(s_render_api)); return false; } - if (!g_gs_device->Create()) + const VsyncMode vsync_mode = Host::GetEffectiveVSyncMode(); + bool okay = g_gs_device->Create(wi.value(), vsync_mode); + if (okay) { - g_gs_device->Destroy(); - g_gs_device.reset(); - return false; - } - - if (!g_gs_renderer) - { - if (renderer == GSRendererType::Null) - { - g_gs_renderer = std::make_unique(); - } - else if (renderer != GSRendererType::SW) - { - g_gs_renderer = std::make_unique(); - } - else - { - g_gs_renderer = std::unique_ptr(MULTI_ISA_SELECT(makeGSRendererSW)(GSConfig.SWExtraThreads)); - } - - g_gs_renderer->SetRegsMem(basemem); - g_gs_renderer->ResetPCRTC(); + okay = ImGuiManager::Initialize(); + if (!okay) + Console.Error("Failed to initialize ImGuiManager"); } else { - Console.Warning("(DoGSOpen) Using existing renderer."); + Console.Error("Failed to create GS device"); } - GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && g_host_display->SetGPUTimingEnabled(true); + if (!okay) + { + ImGuiManager::Shutdown(clear_state_on_fail); + g_gs_device->Destroy(); + g_gs_device.reset(); + Host::ReleaseRenderWindow(); + return false; + } + + GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && g_gs_device->SetGPUTimingEnabled(true); + + s_render_api = g_gs_device->GetRenderAPI(); + Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(s_render_api)); + Console.Indent().WriteLn(g_gs_device->GetDriverInfo()); + + // Switch to exclusive fullscreen if enabled. + UpdateExclusiveFullscreen(false); g_perfmon.Reset(); return true; } -bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config) +static void CloseGSDevice(bool clear_state) { - Console.WriteLn("Reopening GS with %s display", recreate_display ? "new" : "existing"); + if (!g_gs_device) + return; + + UpdateExclusiveFullscreen(true); + ImGuiManager::Shutdown(clear_state); + g_gs_device->Destroy(); + g_gs_device.reset(); + + Host::ReleaseRenderWindow(); +} + +static bool OpenGSRenderer(GSRendererType renderer, u8* basemem) +{ + if (renderer == GSRendererType::Null) + { + g_gs_renderer = std::make_unique(); + } + else if (renderer != GSRendererType::SW) + { + g_gs_renderer = std::make_unique(); + } + else + { + g_gs_renderer = std::unique_ptr(MULTI_ISA_SELECT(makeGSRendererSW)(GSConfig.SWExtraThreads)); + } + + g_gs_renderer->SetRegsMem(basemem); + g_gs_renderer->ResetPCRTC(); + return true; +} + +static void CloseGSRenderer() +{ + GSTextureReplacements::Shutdown(); + + if (g_gs_renderer) + { + g_gs_renderer->Destroy(); + g_gs_renderer.reset(); + } +} + +bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config) +{ + Console.WriteLn("Reopening GS with %s device and %s renderer", recreate_device ? "new" : "existing", + recreate_renderer ? "new" : "existing"); if (recreate_renderer) g_gs_renderer->Flush(GSState::GSFlushReason::GSREOPEN); @@ -238,6 +305,9 @@ bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config:: if (GSConfig.UserHacks_ReadTCOnClose) g_gs_renderer->ReadbackTextureCache(); + u8* basemem = g_gs_renderer->GetRegsMem(); + const u32 gamecrc = g_gs_renderer->GetGameCRC(); + freezeData fd = {}; std::unique_ptr fd_data; if (recreate_renderer) @@ -255,6 +325,8 @@ bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config:: Console.Error("(GSreopen) Failed to freeze GS"); return false; } + + CloseGSRenderer(); } else { @@ -263,66 +335,30 @@ bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config:: g_gs_renderer->PurgePool(); } - if (recreate_display) + if (recreate_device) { - g_gs_device->ResetAPIState(); - if (Host::BeginPresentFrame(true) == HostDisplay::PresentResult::OK) - Host::EndPresentFrame(); - } + CloseGSDevice(false); - u8* basemem = g_gs_renderer->GetRegsMem(); - const u32 gamecrc = g_gs_renderer->GetGameCRC(); - if (recreate_renderer) - { - GSTextureReplacements::Shutdown(); - g_gs_renderer->Destroy(); - g_gs_renderer.reset(); - } - - g_gs_device->Destroy(); - g_gs_device.reset(); - - if (recreate_display) - { - Host::ReleaseHostDisplay(false); - if (!Host::AcquireHostDisplay(GetAPIForRenderer(GSConfig.Renderer), false)) + if (!OpenGSDevice(GSConfig.Renderer, false) || (recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem))) { - Console.Error("(GSreopen) Failed to reacquire host display"); + Host::AddKeyedOSDMessage( + "GSReopenFailed", "Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION); - // try to get the old one back - if (!Host::AcquireHostDisplay(GetAPIForRenderer(old_config.Renderer), false)) - { - pxFailRel("Failed to recreate old config host display"); - return false; - } + CloseGSDevice(false); - Host::AddKeyedOSDMessage("GSReopenFailed", fmt::format("Failed to open {} display, switching back to {}.", - HostDisplay::RenderAPIToString(GetAPIForRenderer(GSConfig.Renderer)), - HostDisplay::RenderAPIToString(GetAPIForRenderer(old_config.Renderer)), Host::OSD_CRITICAL_ERROR_DURATION)); GSConfig = old_config; - } - } - - if (!DoGSOpen(GSConfig.Renderer, basemem)) - { - Console.Error("(GSreopen) Failed to recreate GS"); - - // try the old config - if (recreate_display && GSConfig.Renderer != old_config.Renderer) - { - Host::ReleaseHostDisplay(false); - if (!Host::AcquireHostDisplay(GetAPIForRenderer(old_config.Renderer), false)) + if (!OpenGSDevice(GSConfig.Renderer, false) || (recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem))) { - pxFailRel("Failed to recreate old config host display (part 2)"); + pxFailRel("Failed to reopen GS on old config"); return false; } } - - Host::AddKeyedOSDMessage("GSReopenFailed","Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION); - GSConfig = old_config; - if (!DoGSOpen(GSConfig.Renderer, basemem)) + } + else if (recreate_renderer) + { + if (!OpenGSRenderer(GSConfig.Renderer, basemem)) { - pxFailRel("Failed to reopen GS on old config"); + Console.Error("(GSreopen) Failed to create new renderer"); return false; } } @@ -349,21 +385,32 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b GSConfig = config; GSConfig.Renderer = renderer; - if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer), true)) + bool res = OpenGSDevice(renderer, true); + if (res) { - Console.Error("Failed to acquire host display"); - return false; + res = OpenGSRenderer(renderer, basemem); + if (!res) + CloseGSDevice(true); } - if (!DoGSOpen(renderer, basemem)) + if (!res) { - Host::ReleaseHostDisplay(true); + Host::ReportErrorAsync( + "Error", fmt::format("Failed to create render device. This may be due to your GPU not supporting the " + "chosen renderer ({}), or because your graphics drivers need to be updated.", + Pcsx2Config::GSOptions::GetRendererName(EmuConfig.GS.Renderer))); return false; } return true; } +void GSclose() +{ + CloseGSRenderer(); + CloseGSDevice(true); +} + void GSreset(bool hardware_reset) { try @@ -539,7 +586,7 @@ void GSPresentCurrentFrame() void GSThrottlePresentation() { - if (g_host_display->GetVsyncMode() != VsyncMode::Off) + if (g_gs_device->GetVsyncMode() != VsyncMode::Off) { // Let vsync take care of throttling. return; @@ -547,7 +594,7 @@ void GSThrottlePresentation() // Manually throttle presentation when vsync isn't enabled, so we don't try to render the // fullscreen UI at thousands of FPS and make the gpu go brrrrrrrr. - const float surface_refresh_rate = g_host_display->GetWindowInfo().surface_refresh_rate; + const float surface_refresh_rate = g_gs_device->GetWindowInfo().surface_refresh_rate; const float throttle_rate = (surface_refresh_rate > 0.0f) ? surface_refresh_rate : 60.0f; const u64 sleep_period = static_cast(static_cast(GetTickFrequency()) / static_cast(throttle_rate)); @@ -564,11 +611,96 @@ void GSThrottlePresentation() Threading::SleepUntil(s_next_manual_present_time); } -void GSsetGameCRC(u32 crc) +void GSSetGameCRC(u32 crc) { g_gs_renderer->SetGameCRC(crc); } +void GSResizeDisplayWindow(int width, int height, float scale) +{ + g_gs_device->ResizeWindow(width, height, scale); + ImGuiManager::WindowResized(); +} + +void GSUpdateDisplayWindow() +{ + UpdateExclusiveFullscreen(true); + g_gs_device->DestroySurface(); + + const std::optional wi = Host::UpdateRenderWindow(); + if (!wi.has_value()) + { + pxFailRel("Failed to get window info after update."); + return; + } + + if (!g_gs_device->ChangeWindow(wi.value())) + { + pxFailRel("Failed to change window after update."); + return; + } + + UpdateExclusiveFullscreen(false); + + ImGuiManager::WindowResized(); +} + +void GSSetVSyncMode(VsyncMode mode) +{ + g_gs_device->SetVSync(mode); +} + +bool GSGetHostRefreshRate(float* refresh_rate) +{ + if (!g_gs_device) + return false; + + return g_gs_device->GetHostRefreshRate(refresh_rate); +} + +void GSGetAdaptersAndFullscreenModes( + GSRendererType renderer, std::vector* adapters, std::vector* fullscreen_modes) +{ + switch (renderer) + { +#ifdef _WIN32 + case GSRendererType::DX11: + case GSRendererType::DX12: + { + auto factory = D3D::CreateFactory(false); + if (factory) + { + if (adapters) + *adapters = D3D::GetAdapterNames(factory.get()); + if (fullscreen_modes) + *fullscreen_modes = D3D::GetFullscreenModes(factory.get(), EmuConfig.GS.Adapter); + } + } + break; +#endif + +#ifdef ENABLE_VULKAN + case GSRendererType::VK: + { + GSDeviceVK::GetAdaptersAndFullscreenModes(adapters, fullscreen_modes); + } + break; +#endif + +#ifdef __APPLE__ + case GSRendererType::Metal: + { + if (adapters) + *adapters = GetMetalAdapterList(); + } + break; +#endif + + default: + break; + } +} + GSVideoMode GSgetDisplayMode() { GSRenderer* gs = g_gs_renderer.get(); @@ -594,7 +726,7 @@ void GSgetInternalResolution(int* width, int* height) void GSgetStats(std::string& info) { GSPerfMon& pm = g_perfmon; - const char* api_name = HostDisplay::RenderAPIToString(s_render_api); + const char* api_name = GSDevice::RenderAPIToString(s_render_api); if (GSConfig.Renderer == GSRendererType::SW) { const double fps = GetVerticalFrequency(); @@ -661,7 +793,7 @@ void GSgetTitleStats(std::string& info) static constexpr const char* deinterlace_modes[] = { "Automatic", "None", "Weave tff", "Weave bff", "Bob tff", "Bob bff", "Blend tff", "Blend bff", "Adaptive tff", "Adaptive bff"}; - const char* api_name = HostDisplay::RenderAPIToString(s_render_api); + const char* api_name = GSDevice::RenderAPIToString(s_render_api); const char* hw_sw_name = (GSConfig.Renderer == GSRendererType::Null) ? " Null" : (GSConfig.UseHardwareRenderer() ? " HW" : " SW"); const char* deinterlace_mode = deinterlace_modes[static_cast(GSConfig.InterlaceMode)]; @@ -681,28 +813,12 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config) // Handle OSD scale changes by pushing a window resize through. if (new_config.OsdScale != old_config.OsdScale) - { - g_gs_device->ResetAPIState(); - Host::ResizeHostDisplay(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight(), g_host_display->GetWindowScale()); - g_gs_device->RestoreAPIState(); - } + ImGuiManager::WindowResized(); // Options which need a full teardown/recreate. if (!GSConfig.RestartOptionsAreEqual(old_config)) { - RenderAPI existing_api = g_host_display->GetRenderAPI(); - if (existing_api == RenderAPI::OpenGLES) - existing_api = RenderAPI::OpenGL; - - const bool do_full_restart = ( - existing_api != GetAPIForRenderer(GSConfig.Renderer) || - GSConfig.Adapter != old_config.Adapter || - GSConfig.UseDebugDevice != old_config.UseDebugDevice || - GSConfig.UseBlitSwapChain != old_config.UseBlitSwapChain || - GSConfig.DisableShaderCache != old_config.DisableShaderCache || - GSConfig.DisableThreadedPresentation != old_config.DisableThreadedPresentation - ); - if (!GSreopen(do_full_restart, true, old_config)) + if (!GSreopen(true, true, old_config)) pxFailRel("Failed to do full GS reopen"); return; } @@ -771,7 +887,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config) if (GSConfig.OsdShowGPU != old_config.OsdShowGPU) { - if (!g_host_display->SetGPUTimingEnabled(GSConfig.OsdShowGPU)) + if (!g_gs_device->SetGPUTimingEnabled(GSConfig.OsdShowGPU)) GSConfig.OsdShowGPU = false; } } @@ -784,7 +900,7 @@ void GSSwitchRenderer(GSRendererType new_renderer) if (!g_gs_renderer || GSConfig.Renderer == new_renderer) return; - RenderAPI existing_api = g_host_display->GetRenderAPI(); + RenderAPI existing_api = g_gs_device->GetRenderAPI(); if (existing_api == RenderAPI::OpenGLES) existing_api = RenderAPI::OpenGL; @@ -796,22 +912,6 @@ void GSSwitchRenderer(GSRendererType new_renderer) pxFailRel("Failed to reopen GS for renderer switch."); } -void GSResetAPIState() -{ - if (!g_gs_device) - return; - - g_gs_device->ResetAPIState(); -} - -void GSRestoreAPIState() -{ - if (!g_gs_device) - return; - - g_gs_device->RestoreAPIState(); -} - bool GSSaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders, u32* width, u32* height, std::vector* pixels) { diff --git a/pcsx2/GS/GS.h b/pcsx2/GS/GS.h index a58850d3cd..9a9bbd50fc 100644 --- a/pcsx2/GS/GS.h +++ b/pcsx2/GS/GS.h @@ -20,8 +20,21 @@ #include "pcsx2/Config.h" #include +#include #include #include +#include + +enum class RenderAPI +{ + None, + D3D11, + Metal, + D3D12, + Vulkan, + OpenGL, + OpenGLES +}; // ST_WRITE is defined in libc, avoid this enum stateType @@ -42,6 +55,13 @@ enum class GSVideoMode : u8 HDTV_1080I }; +enum class GSDisplayAlignment +{ + Center, + LeftOrTop, + RightOrBottom +}; + extern Pcsx2Config::GSOptions GSConfig; class HostDisplay; @@ -53,7 +73,7 @@ s16 GSLookupBeforeDrawFunctionId(const std::string_view& name); int GSinit(); void GSshutdown(); bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* basemem); -bool GSreopen(bool recreate_display, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config); +bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::GSOptions& old_config); void GSreset(bool hardware_reset); void GSclose(); void GSgifSoftReset(u32 mask); @@ -74,8 +94,15 @@ bool GSBeginCapture(std::string filename); void GSEndCapture(); void GSPresentCurrentFrame(); void GSThrottlePresentation(); -void GSsetGameCRC(u32 crc); +void GSSetGameCRC(u32 crc); +void GSSetDisplayAlignment(GSDisplayAlignment alignment); +void GSResizeDisplayWindow(int width, int height, float scale); +void GSUpdateDisplayWindow(); +void GSSetVSyncMode(VsyncMode mode); +bool GSGetHostRefreshRate(float* refresh_rate); +void GSGetAdaptersAndFullscreenModes( + GSRendererType renderer, std::vector* adapters, std::vector* fullscreen_modes); GSVideoMode GSgetDisplayMode(); void GSgetInternalResolution(int* width, int* height); void GSgetStats(std::string& info); @@ -88,8 +115,6 @@ void GSTranslateWindowToDisplayCoordinates(float window_x, float window_y, float void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config); void GSSwitchRenderer(GSRendererType new_renderer); -void GSResetAPIState(); -void GSRestoreAPIState(); bool GSSaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders, u32* width, u32* height, std::vector* pixels); void GSJoinSnapshotThreads(); @@ -100,3 +125,28 @@ struct GSError struct GSRecoverableError : GSError { }; + +namespace Host +{ + /// Called when the GS is creating a render device. + std::optional AcquireRenderWindow(RenderAPI api); + + /// Called on the MTGS thread when a request to update the display is received. + /// This could be a fullscreen transition, for example. + std::optional UpdateRenderWindow(); + + /// Called before drawing the OSD and other display elements. + void BeginPresentFrame(); + + /// Called when the GS 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); + + /// Returns the desired vsync mode, depending on the runtime environment. + VsyncMode GetEffectiveVSyncMode(); +} diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 5f3da320e1..c0a5be0477 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -20,6 +20,8 @@ #include "Host.h" #include "common/StringUtil.h" +#include "imgui.h" + #include const char* shaderName(ShaderConvert value) @@ -91,23 +93,129 @@ GSDevice::~GSDevice() pxAssert(m_pool.empty() && !m_merge && !m_weavebob && !m_blend && !m_mad && !m_target_tmp && !m_cas); } -bool GSDevice::Create() +const char* GSDevice::RenderAPIToString(RenderAPI api) { + switch (api) + { + // clang-format off +#define CASE(x) case RenderAPI::x: return #x + CASE(None); + CASE(D3D11); + CASE(D3D12); + CASE(Metal); + CASE(Vulkan); + CASE(OpenGL); + CASE(OpenGLES); +#undef CASE + // clang-format on + default: + return "Unknown"; + } +} + +bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate) +{ + if (!mode.empty()) + { + std::string_view::size_type sep1 = mode.find('x'); + if (sep1 != std::string_view::npos) + { + std::optional owidth = StringUtil::FromChars(mode.substr(0, sep1)); + sep1++; + + while (sep1 < mode.length() && std::isspace(mode[sep1])) + sep1++; + + if (owidth.has_value() && sep1 < mode.length()) + { + std::string_view::size_type sep2 = mode.find('@', sep1); + if (sep2 != std::string_view::npos) + { + std::optional oheight = StringUtil::FromChars(mode.substr(sep1, sep2 - sep1)); + sep2++; + + while (sep2 < mode.length() && std::isspace(mode[sep2])) + sep2++; + + if (oheight.has_value() && sep2 < mode.length()) + { + std::optional orefresh_rate = StringUtil::FromChars(mode.substr(sep2)); + if (orefresh_rate.has_value()) + { + *width = owidth.value(); + *height = oheight.value(); + *refresh_rate = orefresh_rate.value(); + return true; + } + } + } + } + } + } + + *width = 0; + *height = 0; + *refresh_rate = 0; + return false; +} + +std::string GSDevice::GetFullscreenModeString(u32 width, u32 height, float refresh_rate) +{ + return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate); +} + +bool GSDevice::Create(const WindowInfo& wi, VsyncMode vsync) +{ + m_window_info = wi; + m_vsync_mode = vsync; return true; } void GSDevice::Destroy() { + if (m_imgui_font) + { + Recycle(m_imgui_font); + m_imgui_font = nullptr; + } + ClearCurrent(); PurgePool(); } -void GSDevice::ResetAPIState() +bool GSDevice::GetHostRefreshRate(float* refresh_rate) { + if (m_window_info.surface_refresh_rate > 0.0f) + { + *refresh_rate = m_window_info.surface_refresh_rate; + return true; + } + + return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate); } -void GSDevice::RestoreAPIState() +bool GSDevice::UpdateImGuiFontTexture() { + unsigned char* pixels; + int width, height; + ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + const GSVector4i r(0, 0, width, height); + const int pitch = sizeof(u32) * width; + + if (m_imgui_font && m_imgui_font->GetWidth() == width && m_imgui_font->GetHeight() == height) + return m_imgui_font->Update(r, pixels, pitch); + + GSTexture* new_font = CreateTexture(width, height, 1, GSTexture::Format::Color); + if (!new_font || !new_font->Update(r, pixels, pitch)) + return false; + + if (m_imgui_font) + Recycle(m_imgui_font); + + m_imgui_font = new_font; + ImGui::GetIO().Fonts->SetTexID(new_font->GetNativeHandle()); + return true; } GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_reuse) @@ -182,11 +290,6 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i return t; } -std::unique_ptr GSDevice::CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) -{ - return {}; -} - void GSDevice::Recycle(GSTexture* t) { if (!t) @@ -208,6 +311,12 @@ void GSDevice::Recycle(GSTexture* t) } } +bool GSDevice::UsesLowerLeftOrigin() const +{ + const RenderAPI api = GetRenderAPI(); + return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES); +} + void GSDevice::AgePool() { m_frame++; @@ -229,10 +338,6 @@ void GSDevice::PurgePool() m_pool_memory_usage = 0; } -void GSDevice::ClearSamplerCache() -{ -} - GSTexture* GSDevice::CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear) { return FetchSurface(GSTexture::Type::RenderTarget, w, h, 1, format, clear, true); diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 9151a6afc2..09a9f41db5 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -17,15 +17,14 @@ #include "common/HashCombine.h" #include "common/WindowInfo.h" -#include "GSFastList.h" -#include "GSTexture.h" -#include "GSVertex.h" +#include "GS/GS.h" +#include "GS/Renderers/Common/GSFastList.h" +#include "GS/Renderers/Common/GSTexture.h" +#include "GS/Renderers/Common/GSVertex.h" #include "GS/GSAlignedClass.h" #include "GS/GSExtra.h" #include -class HostDisplay; - enum class ShaderConvert { COPY = 0, @@ -694,6 +693,22 @@ struct alignas(16) GSHWDrawConfig class GSDevice : public GSAlignedClass<32> { public: + enum class PresentResult + { + OK, + FrameSkipped, + DeviceLost + }; + + enum class DebugMessageCategory + { + Cache, + Reg, + Debug, + Message, + Performance + }; + // clang-format off struct FeatureSupport { @@ -747,6 +762,7 @@ public: private: FastList m_pool; u64 m_pool_memory_usage = 0; + static const std::array m_blendMap; static const std::array m_replaceDualSrcBlendMap; @@ -756,6 +772,11 @@ protected: static constexpr u32 MAX_POOLED_TEXTURES = 300; static constexpr u32 NUM_CAS_CONSTANTS = 12; // 8 plus src offset x/y, 16 byte alignment + WindowInfo m_window_info; + VsyncMode m_vsync_mode = VsyncMode::Off; + + GSTexture* m_imgui_font = nullptr; + GSTexture* m_merge = nullptr; GSTexture* m_weavebob = nullptr; GSTexture* m_blend = nullptr; @@ -781,8 +802,8 @@ protected: virtual void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c, const bool linear) = 0; virtual void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, bool linear, const InterlaceConstantBuffer& cb) = 0; - virtual void DoFXAA(GSTexture* sTex, GSTexture* dTex) {} - virtual void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) {} + virtual void DoFXAA(GSTexture* sTex, GSTexture* dTex) = 0; + virtual void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) = 0; /// Resolves CAS shader includes for the specified source. static bool GetCASShaderSource(std::string* source); @@ -794,58 +815,111 @@ public: GSDevice(); virtual ~GSDevice(); + /// Returns a string representing the specified API. + static const char* RenderAPIToString(RenderAPI api); + + /// Parses a fullscreen mode into its components (width * height @ refresh hz) + static bool ParseFullscreenMode(const std::string_view& mode, 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); + __fi unsigned int GetFrameNumber() const { return m_frame; } __fi u64 GetPoolMemoryUsage() const { return m_pool_memory_usage; } + __fi FeatureSupport Features() const { return m_features; } + + __fi const WindowInfo& GetWindowInfo() const { return m_window_info; } + __fi s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } + __fi s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } + __fi GSVector2i GetWindowSize() const { return GSVector2i(static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)); } + __fi float GetWindowScale() const { return m_window_info.surface_scale; } + __fi VsyncMode GetVsyncMode() const { return m_vsync_mode; } + + __fi GSTexture* GetCurrent() const { return m_current; } + void Recycle(GSTexture* t); - enum - { - Windowed, - Fullscreen, - DontCare - }; + /// Returns true if it's an OpenGL-based renderer. + bool UsesLowerLeftOrigin() const; - enum class DebugMessageCategory - { - Cache, - Reg, - Debug, - Message, - Performance - }; + /// Recreates the font, call when the window scaling changes. + bool UpdateImGuiFontTexture(); - virtual bool Create(); + virtual bool Create(const WindowInfo& wi, VsyncMode vsync); virtual void Destroy(); - virtual void ResetAPIState(); - virtual void RestoreAPIState(); + /// Returns the graphics API used by this device. + virtual RenderAPI GetRenderAPI() const = 0; - virtual void ClearRenderTarget(GSTexture* t, const GSVector4& c) {} - virtual void ClearRenderTarget(GSTexture* t, u32 c) {} - virtual void InvalidateRenderTarget(GSTexture* t) {} - virtual void ClearDepth(GSTexture* t) {} - virtual void ClearStencil(GSTexture* t, u8 c) {} + /// Returns true if we have a window we're rendering into. + virtual bool HasSurface() const = 0; - virtual void PushDebugGroup(const char* fmt, ...) {} - virtual void PopDebugGroup() {} - virtual void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) {} + /// Destroys the surface we're currently drawing to. + virtual void DestroySurface() = 0; + + /// Switches to a new window/surface. + virtual bool ChangeWindow(const WindowInfo& wi) = 0; + + /// 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; + + /// Returns true if exclusive fullscreen is supported. + virtual bool SupportsExclusiveFullscreen() const = 0; + + /// Returns true if exclusive fullscreen is active. + virtual bool IsExclusiveFullscreen() = 0; + + /// Attempts to switch to the specified mode in exclusive fullscreen. + virtual bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0; + + /// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be + /// displayed, but the GPU command queue will still be flushed. + virtual PresentResult BeginPresent(bool frame_skip) = 0; + + /// Presents the frame to the display. + virtual void EndPresent() = 0; + + /// Changes vsync mode for this display. + virtual void SetVSync(VsyncMode mode) = 0; + + /// Returns the effective refresh rate of this display. + virtual bool GetHostRefreshRate(float* refresh_rate); + + /// Returns a string of information about the graphics driver being used. + virtual std::string GetDriverInfo() const = 0; + + /// Enables/disables GPU frame timing. + virtual bool SetGPUTimingEnabled(bool enabled) = 0; + + /// Returns the amount of GPU time utilized since the last time this method was called. + virtual float GetAndResetAccumulatedGPUTime() = 0; + + virtual void ClearRenderTarget(GSTexture* t, const GSVector4& c) = 0; + virtual void ClearRenderTarget(GSTexture* t, u32 c) = 0; + virtual void InvalidateRenderTarget(GSTexture* t) = 0; + virtual void ClearDepth(GSTexture* t) = 0; + virtual void ClearStencil(GSTexture* t, u8 c) = 0; + + virtual void PushDebugGroup(const char* fmt, ...) = 0; + virtual void PopDebugGroup() = 0; + virtual void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) = 0; GSTexture* CreateRenderTarget(int w, int h, GSTexture::Format format, bool clear = true); GSTexture* CreateDepthStencil(int w, int h, GSTexture::Format format, bool clear = true); GSTexture* CreateTexture(int w, int h, int mipmap_levels, GSTexture::Format format, bool prefer_reuse = false); GSTexture::Format GetDefaultTextureFormat(GSTexture::Type type); - virtual std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format); + virtual std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) = 0; - virtual void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) {} - virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) {} - virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) {} + virtual void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) = 0; + virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) = 0; + virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) = 0; void StretchRect(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true); /// Performs a screen blit for display. If dTex is null, it assumes you are writing to the system framebuffer/swap chain. - virtual void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) {} + virtual void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) = 0; /// Same as doing StretchRect for each item, except tries to batch together rectangles in as few draws as possible. /// The provided list should be sorted by texture, the implementations only check if it's the same as the last. @@ -855,18 +929,14 @@ public: static void SortMultiStretchRects(MultiStretchRect* rects, u32 num_rects); /// Updates a GPU CLUT texture from a source texture. - virtual void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) {} + virtual void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) = 0; /// Converts a colour format to an indexed format texture. - virtual void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) {} + virtual void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) = 0; - /// Converts a colour format to an indexed format texture. - virtual void ConvertToIndexedTexture(GSTexture* sTex, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) {} + virtual void RenderHW(GSHWDrawConfig& config) = 0; - virtual void RenderHW(GSHWDrawConfig& config) {} - - __fi FeatureSupport Features() const { return m_features; } - __fi GSTexture* GetCurrent() const { return m_current; } + virtual void ClearSamplerCache() = 0; void ClearCurrent(); void Merge(GSTexture* sTex[3], GSVector4* sRect, GSVector4* dRect, const GSVector2i& fs, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c); @@ -887,8 +957,6 @@ public: void AgePool(); void PurgePool(); - virtual void ClearSamplerCache(); - __fi static constexpr bool IsDualSourceBlendFactor(u8 factor) { return (factor == SRC1_ALPHA || factor == INV_SRC1_ALPHA || factor == SRC1_COLOR diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 01ccb213df..f214bd2953 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -14,22 +14,27 @@ */ #include "PrecompiledHeader.h" -#include "GSRenderer.h" + +#include "Frontend/FullscreenUI.h" +#include "Frontend/ImGuiManager.h" +#include "GS/Renderers/Common/GSRenderer.h" #include "GS/GSCapture.h" #include "GS/GSGL.h" #include "GSDumpReplayer.h" #include "Host.h" -#include "HostDisplay.h" #include "PerformanceMetrics.h" #include "pcsx2/Config.h" #include "IconsFontAwesome5.h" #include "VMManager.h" + #include "common/FileSystem.h" #include "common/Image.h" #include "common/Path.h" #include "common/StringUtil.h" #include "common/Timer.h" + #include "fmt/core.h" + #include #include #include @@ -53,6 +58,9 @@ static GSVector4 s_last_draw_rect; // Last time we reset the renderer due to a GPU crash, if any. static Common::Timer::Value s_last_gpu_reset_time; +// Screen alignment +static GSDisplayAlignment s_display_alignment = GSDisplayAlignment::Center; + GSRenderer::GSRenderer() : m_shader_time_start(Common::Timer::GetCurrentValue()) { @@ -230,9 +238,9 @@ bool GSRenderer::Merge(int field) g_gs_device->FXAA(); // Sharpens biinear at lower resolutions, almost nearest but with more uniform pixels. - if (GSConfig.LinearPresent == GSPostBilinearMode::BilinearSharp && (g_host_display->GetWindowWidth() > fs.x || g_host_display->GetWindowHeight() > fs.y)) + if (GSConfig.LinearPresent == GSPostBilinearMode::BilinearSharp && (g_gs_device->GetWindowWidth() > fs.x || g_gs_device->GetWindowHeight() > fs.y)) { - g_gs_device->Resize(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()); + g_gs_device->Resize(g_gs_device->GetWindowWidth(), g_gs_device->GetWindowHeight()); } if (m_scanmask_used) @@ -271,7 +279,7 @@ static float GetCurrentAspectRatioFloat(bool is_progressive) return ars[static_cast(GSConfig.AspectRatio) + (3u * (is_progressive && GSConfig.AspectRatio == AspectRatioType::RAuto4_3_3_2))]; } -static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const GSVector4i& src_rect, const GSVector2i& src_size, HostDisplay::Alignment alignment, bool flip_y, bool is_progressive) +static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const GSVector4i& src_rect, const GSVector2i& src_size, GSDisplayAlignment alignment, bool flip_y, bool is_progressive) { const float f_width = static_cast(window_width); const float f_height = static_cast(window_height); @@ -318,7 +326,7 @@ static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const const GSVector2i fs = GSVector2i(static_cast(static_cast(resolution.x) * g_gs_renderer->GetUpscaleMultiplier()), static_cast(static_cast(resolution.y) * g_gs_renderer->GetUpscaleMultiplier())); - if (g_host_display->GetWindowWidth() > fs.x || g_host_display->GetWindowHeight() > fs.y) + if (g_gs_device->GetWindowWidth() > fs.x || g_gs_device->GetWindowHeight() > fs.y) { t_width *= static_cast(fs.x) / src_rect.width(); t_height *= static_cast(fs.y) / src_rect.height(); @@ -348,13 +356,13 @@ static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const { switch (alignment) { - case HostDisplay::Alignment::Center: + case GSDisplayAlignment::Center: target_x = (f_width - target_width) * 0.5f; break; - case HostDisplay::Alignment::RightOrBottom: + case GSDisplayAlignment::RightOrBottom: target_x = (f_width - target_width); break; - case HostDisplay::Alignment::LeftOrTop: + case GSDisplayAlignment::LeftOrTop: default: target_x = 0.0f; break; @@ -368,13 +376,13 @@ static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const { switch (alignment) { - case HostDisplay::Alignment::Center: + case GSDisplayAlignment::Center: target_y = (f_height - target_height) * 0.5f; break; - case HostDisplay::Alignment::RightOrBottom: + case GSDisplayAlignment::RightOrBottom: target_y = (f_height - target_height); break; - case HostDisplay::Alignment::LeftOrTop: + case GSDisplayAlignment::LeftOrTop: default: target_y = 0.0f; break; @@ -466,11 +474,21 @@ void GSJoinSnapshotThreads() bool GSRenderer::BeginPresentFrame(bool frame_skip) { - const HostDisplay::PresentResult result = Host::BeginPresentFrame(frame_skip); - if (result == HostDisplay::PresentResult::OK) - return true; - else if (result == HostDisplay::PresentResult::FrameSkipped) + Host::BeginPresentFrame(); + + const GSDevice::PresentResult res = g_gs_device->BeginPresent(frame_skip); + if (res == GSDevice::PresentResult::FrameSkipped) + { + // If we're skipping a frame, we need to reset imgui's state, since + // we won't be calling EndPresentFrame(). + ImGuiManager::SkipFrame(); return false; + } + else if (res == GSDevice::PresentResult::OK) + { + // All good! + return true; + } // If we're constantly crashing on something in particular, we don't want to end up in an // endless reset loop.. that'd probably end up leaking memory and/or crashing us for other @@ -498,6 +516,17 @@ bool GSRenderer::BeginPresentFrame(bool frame_skip) return false; } +void GSRenderer::EndPresentFrame() +{ + if (GSDumpReplayer::IsReplayingDump()) + GSDumpReplayer::RenderUI(); + + FullscreenUI::Render(); + ImGuiManager::RenderOSD(); + g_gs_device->EndPresent(); + ImGuiManager::NewFrame(); +} + void GSRenderer::VSync(u32 field, bool registers_written) { Flush(GSFlushReason::VSYNC); @@ -542,10 +571,9 @@ void GSRenderer::VSync(u32 field, bool registers_written) if (skip_frame) { - g_gs_device->ResetAPIState(); if (BeginPresentFrame(true)) - Host::EndPresentFrame(); - g_gs_device->RestoreAPIState(); + EndPresentFrame(); + PerformanceMetrics::Update(registers_written, fb_sprite_frame, true); return; } @@ -564,8 +592,8 @@ void GSRenderer::VSync(u32 field, bool registers_written) { src_rect = CalculateDrawSrcRect(current); src_uv = GSVector4(src_rect) / GSVector4(current->GetSize()).xyxy(); - draw_rect = CalculateDrawDstRect(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight(), - src_rect, current->GetSize(), g_host_display->GetDisplayAlignment(), g_host_display->UsesLowerLeftOrigin(), + draw_rect = CalculateDrawDstRect(g_gs_device->GetWindowWidth(), g_gs_device->GetWindowHeight(), + src_rect, current->GetSize(), s_display_alignment, g_gs_device->UsesLowerLeftOrigin(), GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets)); s_last_draw_rect = draw_rect; @@ -576,8 +604,8 @@ void GSRenderer::VSync(u32 field, bool registers_written) { // sharpen only if the IR is higher than the display resolution const bool sharpen_only = (GSConfig.CASMode == GSCASMode::SharpenOnly || - (current->GetWidth() > g_host_display->GetWindowWidth() && - current->GetHeight() > g_host_display->GetWindowHeight())); + (current->GetWidth() > g_gs_device->GetWindowWidth() && + current->GetHeight() > g_gs_device->GetWindowHeight())); g_gs_device->CAS(current, src_rect, src_uv, draw_rect, sharpen_only); } else if (!cas_log_once) @@ -589,7 +617,6 @@ void GSRenderer::VSync(u32 field, bool registers_written) } } - g_gs_device->ResetAPIState(); if (BeginPresentFrame(false)) { if (current && !blank_frame) @@ -601,12 +628,12 @@ void GSRenderer::VSync(u32 field, bool registers_written) s_tv_shader_indices[GSConfig.TVShader], shader_time, GSConfig.LinearPresent != GSPostBilinearMode::Off); } - Host::EndPresentFrame(); + EndPresentFrame(); if (GSConfig.OsdShowGPU) - PerformanceMetrics::OnGPUPresent(g_host_display->GetAndResetAccumulatedGPUTime()); + PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime()); } - g_gs_device->RestoreAPIState(); + PerformanceMetrics::Update(registers_written, fb_sprite_frame, false); // snapshot @@ -665,8 +692,8 @@ void GSRenderer::VSync(u32 field, bool registers_written) const bool aspect_correct = (GSConfig.ScreenshotSize != GSScreenshotSize::InternalResolutionUncorrected); if (g_gs_device->GetCurrent() && SaveSnapshotToMemory( - internal_resolution ? 0 : g_host_display->GetWindowWidth(), - internal_resolution ? 0 : g_host_display->GetWindowHeight(), + internal_resolution ? 0 : g_gs_device->GetWindowWidth(), + internal_resolution ? 0 : g_gs_device->GetWindowHeight(), aspect_correct, true, &screenshot_width, &screenshot_height, &screenshot_pixels)) { @@ -805,7 +832,6 @@ void GSRenderer::StopGSDump() void GSRenderer::PresentCurrentFrame() { - g_gs_device->ResetAPIState(); if (BeginPresentFrame(false)) { GSTexture* current = g_gs_device->GetCurrent(); @@ -813,8 +839,8 @@ void GSRenderer::PresentCurrentFrame() { const GSVector4i src_rect(CalculateDrawSrcRect(current)); const GSVector4 src_uv(GSVector4(src_rect) / GSVector4(current->GetSize()).xyxy()); - const GSVector4 draw_rect(CalculateDrawDstRect(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight(), - src_rect, current->GetSize(), g_host_display->GetDisplayAlignment(), g_host_display->UsesLowerLeftOrigin(), + const GSVector4 draw_rect(CalculateDrawDstRect(g_gs_device->GetWindowWidth(), g_gs_device->GetWindowHeight(), + src_rect, current->GetSize(), s_display_alignment, g_gs_device->UsesLowerLeftOrigin(), GetVideoMode() == GSVideoMode::SDTV_480P || (GSConfig.PCRTCOverscan && GSConfig.PCRTCOffsets))); s_last_draw_rect = draw_rect; @@ -825,9 +851,8 @@ void GSRenderer::PresentCurrentFrame() s_tv_shader_indices[GSConfig.TVShader], shader_time, GSConfig.LinearPresent != GSPostBilinearMode::Off); } - Host::EndPresentFrame(); + EndPresentFrame(); } - g_gs_device->RestoreAPIState(); } void GSTranslateWindowToDisplayCoordinates(float window_x, float window_y, float* display_x, float* display_y) @@ -847,6 +872,11 @@ void GSTranslateWindowToDisplayCoordinates(float window_x, float window_y, float *display_y = rel_y / draw_height; } +void GSSetDisplayAlignment(GSDisplayAlignment alignment) +{ + s_display_alignment = alignment; +} + bool GSRenderer::BeginCapture(std::string filename) { const GSVector2i capture_resolution(GSConfig.VideoCaptureAutoResolution ? @@ -910,7 +940,7 @@ bool GSRenderer::SaveSnapshotToMemory(u32 window_width, u32 window_height, bool else { draw_rect = CalculateDrawDstRect(window_width, window_height, src_rect, current->GetSize(), - HostDisplay::Alignment::LeftOrTop, false, is_progressive); + GSDisplayAlignment::LeftOrTop, false, is_progressive); } const u32 draw_width = static_cast(draw_rect.z - draw_rect.x); const u32 draw_height = static_cast(draw_rect.w - draw_rect.y); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index 5fd27488c5..8496738781 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -24,6 +24,7 @@ class GSRenderer : public GSState private: bool Merge(int field); bool BeginPresentFrame(bool frame_skip); + void EndPresentFrame(); u64 m_shader_time_start = 0; diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index ffc3597e87..dee6346bcb 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -14,6 +14,7 @@ */ #include "PrecompiledHeader.h" +#include "GS/Renderers/Common/GSDevice.h" #include "GS/Renderers/DX11/D3D.h" #include "GS/GSExtra.h" @@ -80,6 +81,45 @@ std::vector D3D::GetAdapterNames(IDXGIFactory5* factory) return adapter_names; } +std::vector D3D::GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name) +{ + std::vector modes; + HRESULT hr; + + wil::com_ptr_nothrow adapter = GetChosenOrFirstAdapter(factory, adapter_name); + if (!adapter) + return modes; + + wil::com_ptr_nothrow output; + if (FAILED(hr = adapter->EnumOutputs(0, &output))) + { + Console.Error("EnumOutputs() failed: %08X", hr); + return modes; + } + + UINT num_modes = 0; + if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr))) + { + Console.Error("GetDisplayModeList() failed: %08X", hr); + return modes; + } + + std::vector dmodes(num_modes); + if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, dmodes.data()))) + { + Console.Error("GetDisplayModeList() (2) failed: %08X", hr); + return modes; + } + + for (const DXGI_MODE_DESC& mode : dmodes) + { + modes.push_back(GSDevice::GetFullscreenModeString(mode.Width, mode.Height, + static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); + } + + return modes; +} + wil::com_ptr_nothrow D3D::GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name) { if (name.empty()) diff --git a/pcsx2/GS/Renderers/DX11/D3D.h b/pcsx2/GS/Renderers/DX11/D3D.h index dc53c6ce1b..aea293f887 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.h +++ b/pcsx2/GS/Renderers/DX11/D3D.h @@ -33,6 +33,9 @@ namespace D3D // returns a list of all adapter names std::vector GetAdapterNames(IDXGIFactory5* factory); + // returns a list of fullscreen modes for the specified adapter + std::vector GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name); + // get an adapter based on name wil::com_ptr_nothrow GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name); diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index e4705179ea..c1b63e892a 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -21,16 +21,22 @@ #include "GS/GSPerfMon.h" #include "GS/GSUtil.h" #include "Host.h" -#include "HostDisplay.h" #include "ShaderCacheVersion.h" + +#include "common/Align.h" #include "common/Path.h" #include "common/StringUtil.h" + +#include "imgui.h" + #include #include #include #include #include +// #define REPORT_LEAKED_OBJECTS 1 + static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format) { UINT support; @@ -63,17 +69,89 @@ GSDevice11::GSDevice11() m_features.test_and_sample_depth = false; } -GSDevice11::~GSDevice11() +GSDevice11::~GSDevice11() = default; + +RenderAPI GSDevice11::GetRenderAPI() const { - if (m_state.rt_view) - m_state.rt_view->Release(); - if (m_state.dsv) - m_state.dsv->Release(); + return RenderAPI::D3D11; } -bool GSDevice11::Create() +bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync) { - if (!GSDevice::Create()) + if (!GSDevice::Create(wi, vsync)) + return false; + + UINT create_flags = 0; + if (GSConfig.UseDebugDevice) + create_flags |= D3D11_CREATE_DEVICE_DEBUG; + + m_dxgi_factory = D3D::CreateFactory(GSConfig.UseDebugDevice); + if (!m_dxgi_factory) + return false; + + wil::com_ptr_nothrow dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), GSConfig.Adapter); + + static constexpr std::array requested_feature_levels = { + {D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0}}; + + wil::com_ptr_nothrow temp_dev; + wil::com_ptr_nothrow temp_ctx; + + HRESULT hr = + D3D11CreateDevice(dxgi_adapter.get(), dxgi_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, + nullptr, create_flags, requested_feature_levels.data(), static_cast(requested_feature_levels.size()), + D3D11_SDK_VERSION, temp_dev.put(), nullptr, temp_ctx.put()); + + if (FAILED(hr)) + { + Console.Error("Failed to create D3D device: 0x%08X", hr); + return false; + } + + if (!temp_dev.try_query_to(&m_dev) || !temp_ctx.try_query_to(&m_ctx)) + { + Console.Error("Direct3D 11.1 is required and not supported."); + return false; + } + + // we re-grab these later, see below + dxgi_adapter.reset(); + temp_dev.reset(); + temp_ctx.reset(); + + if (GSConfig.UseDebugDevice && IsDebuggerPresent()) + { + wil::com_ptr_nothrow info; + if (m_dev.try_query_to(&info)) + { + info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE); + info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, TRUE); + + // Silence some annoying harmless warnings. + D3D11_MESSAGE_ID hide[] = { + D3D11_MESSAGE_ID_DEVICE_OMSETRENDERTARGETS_HAZARD, + D3D11_MESSAGE_ID_DEVICE_PSSETSHADERRESOURCES_HAZARD, + }; + + D3D11_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = std::size(hide); + filter.DenyList.pIDList = hide; + info->AddStorageFilterEntries(&filter); + } + } + + wil::com_ptr_nothrow dxgi_device; + if (m_dev.try_query_to(&dxgi_device) && SUCCEEDED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.put())))) + Console.WriteLn(fmt::format("D3D Adapter: {}", D3D::GetAdapterName(dxgi_adapter.get()))); + else + Console.Error("Failed to obtain D3D adapter name."); + + 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); + + if (wi.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) return false; D3D11_BUFFER_DESC bd; @@ -84,14 +162,6 @@ bool GSDevice11::Create() D3D_FEATURE_LEVEL level; - if (g_host_display->GetRenderAPI() != RenderAPI::D3D11) - { - Console.Error("Render API is incompatible with D3D11"); - return false; - } - - m_dev = static_cast(g_host_display->GetDevice()); - m_ctx = static_cast(g_host_display->GetContext()); if (GSConfig.UseDebugDevice) m_annotation = m_ctx.try_query(); level = m_dev->GetFeatureLevel(); @@ -373,9 +443,63 @@ bool GSDevice11::Create() m_features.cas_sharpening = support_feature_level_11_0 && CreateCASShaders(); + if (!CreateImGuiResources()) + return false; + return true; } +void GSDevice11::Destroy() +{ + GSDevice::Destroy(); + GSDevice11::DestroySurface(); + DestroyTimestampQueries(); + + m_convert = {}; + m_present = {}; + m_merge = {}; + m_interlace = {}; + m_shadeboost = {}; + m_date = {}; + m_cas = {}; + m_imgui = {}; + + m_vb.reset(); + m_ib.reset(); + + m_vs.clear(); + m_vs_cb.reset(); + m_gs.clear(); + m_ps.clear(); + m_ps_cb.reset(); + m_ps_ss.clear(); + m_om_dss.clear(); + m_om_bs.clear(); + m_rs.reset(); + + if (m_state.rt_view) + m_state.rt_view->Release(); + if (m_state.dsv) + m_state.dsv->Release(); + + m_shader_cache.Close(); + +#ifdef REPORT_LEAKED_OBJECTS + wil::com_ptr_nothrow debug; + m_dev.try_query_to(&debug); +#endif + + m_annotation.reset(); + m_ctx.reset(); + m_dev.reset(); + m_dxgi_factory.reset(); + +#ifdef REPORT_LEAKED_OBJECTS + if (debug) + debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL | D3D11_RLDO_IGNORE_INTERNAL); +#endif +} + void GSDevice11::SetFeatures() { // Check all three formats, since the feature means any can be used. @@ -386,44 +510,477 @@ void GSDevice11::SetFeatures() m_features.bptc_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC7_UNORM); } -void GSDevice11::ResetAPIState() +bool GSDevice11::HasSurface() const { - // Clear out the GS, since the imgui draw doesn't get rid of it. - m_ctx->GSSetShader(nullptr, nullptr, 0); + return static_cast(m_swap_chain); } -void GSDevice11::RestoreAPIState() +bool GSDevice11::GetHostRefreshRate(float* refresh_rate) { - const UINT vb_offset = 0; - m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset); - m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0); - m_ctx->IASetInputLayout(m_state.layout); - m_ctx->IASetPrimitiveTopology(m_state.topology); - m_ctx->VSSetShader(m_state.vs, nullptr, 0); - m_ctx->VSSetConstantBuffers(0, 1, &m_state.vs_cb); - m_ctx->GSSetShader(m_state.gs, nullptr, 0); - m_ctx->GSSetConstantBuffers(0, 1, &m_state.gs_cb); - m_ctx->PSSetShader(m_state.ps, nullptr, 0); - m_ctx->PSSetConstantBuffers(0, 1, &m_state.ps_cb); + if (m_swap_chain && IsExclusiveFullscreen()) + { + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && + desc.BufferDesc.RefreshRate.Denominator > 0) + { + DevCon.WriteLn( + "using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); + *refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + return true; + } + } - const CD3D11_VIEWPORT vp(0.0f, 0.0f, - static_cast(m_state.viewport.x), static_cast(m_state.viewport.y), - 0.0f, 1.0f); - m_ctx->RSSetViewports(1, &vp); - m_ctx->RSSetScissorRects(1, reinterpret_cast(&m_state.scissor)); - m_ctx->RSSetState(m_rs.get()); + return GSDevice::GetHostRefreshRate(refresh_rate); +} - m_ctx->OMSetDepthStencilState(m_state.dss, m_state.sref); +void GSDevice11::SetVSync(VsyncMode mode) +{ + m_vsync_mode = mode; +} - const float blend_factors[4] = { m_state.bf, m_state.bf, m_state.bf, m_state.bf }; - m_ctx->OMSetBlendState(m_state.bs, blend_factors, 0xFFFFFFFFu); +bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) +{ + if (m_window_info.type != WindowInfo::Type::Win32) + return false; - PSUpdateShaderState(); + m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || fullscreen_mode; + const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + const u32 width = static_cast(client_rc.right - client_rc.left); + const u32 height = static_cast(client_rc.bottom - client_rc.top); + + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; + swap_chain_desc.Width = width; + swap_chain_desc.Height = height; + swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.BufferCount = 3; + 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 && !fullscreen_mode); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; + if (fullscreen_mode) + { + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swap_chain_desc.Width = fullscreen_mode->Width; + swap_chain_desc.Height = fullscreen_mode->Height; + fs_desc.RefreshRate = fullscreen_mode->RefreshRate; + fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering; + fs_desc.Scaling = fullscreen_mode->Scaling; + fs_desc.Windowed = FALSE; + } + + Console.WriteLn("Creating a %dx%d %s %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height, + m_using_flip_model_swap_chain ? "flip-discard" : "discard", fullscreen_mode ? "full-screen" : "windowed"); + + HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc, + fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); + if (FAILED(hr) && m_using_flip_model_swap_chain) + { + Console.Warning("Failed to create a flip-discard swap chain, trying discard."); + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + swap_chain_desc.Flags = 0; + m_using_flip_model_swap_chain = false; + m_using_allow_tearing = false; + + hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc, + fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); + if (FAILED(hr)) + { + Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr); + return false; + } + } + + hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + if (FAILED(hr)) + Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed"); + + return CreateSwapChainRTV(); +} + +bool GSDevice11::CreateSwapChainRTV() +{ + wil::com_ptr_nothrow backbuffer; + HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.put())); + if (FAILED(hr)) + { + Console.Error("GetBuffer for RTV failed: 0x%08X", hr); + return false; + } + + D3D11_TEXTURE2D_DESC backbuffer_desc; + backbuffer->GetDesc(&backbuffer_desc); + + CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc( + D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, backbuffer_desc.ArraySize); + hr = m_dev->CreateRenderTargetView(backbuffer.get(), &rtv_desc, m_swap_chain_rtv.put()); + if (FAILED(hr)) + { + Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr); + return false; + } + + m_window_info.surface_width = backbuffer_desc.Width; + m_window_info.surface_height = backbuffer_desc.Height; + DevCon.WriteLn("Swap chain buffer size: %ux%u", 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))) + { + m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + } + else + { + m_window_info.surface_refresh_rate = 0.0f; + } + } + + return true; +} + +bool GSDevice11::ChangeWindow(const WindowInfo& new_wi) +{ + DestroySurface(); + + m_window_info = new_wi; + if (new_wi.type == WindowInfo::Type::Surfaceless) + return true; + + return CreateSwapChain(nullptr); +} + +void GSDevice11::DestroySurface() +{ + m_swap_chain_rtv.reset(); + + if (IsExclusiveFullscreen()) + SetExclusiveFullscreen(false, 0, 0, 0.0f); + + m_swap_chain.reset(); +} + +std::string GSDevice11::GetDriverInfo() const +{ + std::string ret = "Unknown Feature Level"; + + static constexpr std::array, 4> feature_level_names = {{ + {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"}, + {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_1"}, + {D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"}, + {D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"}, + }}; + + const D3D_FEATURE_LEVEL fl = m_dev->GetFeatureLevel(); + for (size_t i = 0; i < std::size(feature_level_names); i++) + { + if (fl == std::get<0>(feature_level_names[i])) + { + ret = std::get<1>(feature_level_names[i]); + break; + } + } + + ret += "\n"; + + wil::com_ptr_nothrow dxgi_dev; + if (m_dev.try_query_to(&dxgi_dev)) + { + wil::com_ptr_nothrow dxgi_adapter; + if (SUCCEEDED(dxgi_dev->GetAdapter(dxgi_adapter.put()))) + { + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(dxgi_adapter->GetDesc(&desc))) + { + ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId); + ret += StringUtil::WideStringToUTF8String(desc.Description); + ret += "\n"; + + const std::string driver_version(D3D::GetDriverVersionFromLUID(desc.AdapterLuid)); + if (!driver_version.empty()) + { + ret += "Driver Version: "; + ret += driver_version; + } + } + } + } + + return ret; +} + +void GSDevice11::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + if (!m_swap_chain) + return; + + m_window_info.surface_scale = new_window_scale; + + if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height) + return; + + 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)) + Console.Error("ResizeBuffers() failed: 0x%08X", hr); + + if (!CreateSwapChainRTV()) + pxFailRel("Failed to recreate swap chain RTV after resize"); +} + +bool GSDevice11::SupportsExclusiveFullscreen() const +{ + return true; +} + +bool GSDevice11::IsExclusiveFullscreen() +{ + BOOL is_fullscreen = FALSE; + return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); +} + +bool GSDevice11::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + if (!m_swap_chain) + return false; + + BOOL is_fullscreen = FALSE; + HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); + if (!fullscreen) + { + // leaving fullscreen + if (is_fullscreen) + return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); + else + return true; + } + + IDXGIOutput* output; + if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) + return false; + + DXGI_SWAP_CHAIN_DESC current_desc; + hr = m_swap_chain->GetDesc(¤t_desc); + if (FAILED(hr)) + return false; + + DXGI_MODE_DESC new_mode = current_desc.BufferDesc; + new_mode.Width = width; + new_mode.Height = height; + new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); + new_mode.RefreshRate.Denominator = 1000u; + + DXGI_MODE_DESC closest_mode; + if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || + new_mode.Format != current_desc.BufferDesc.Format) + { + Console.Error("Failed to find closest matching mode, hr=%08X", hr); + return false; + } + + if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Height && + new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && + new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) + { + DevCon.WriteLn("Fullscreen mode already set"); + return true; + } + + m_swap_chain_rtv.reset(); + m_swap_chain.reset(); + + if (!CreateSwapChain(&closest_mode)) + { + Console.Error("Failed to create a fullscreen swap chain"); + if (!CreateSwapChain(nullptr)) + pxFailRel("Failed to recreate windowed swap chain"); + + return false; + } + + return true; +} + +GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip) +{ + if (frame_skip || !m_swap_chain) + return PresentResult::FrameSkipped; + + // 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 != VsyncMode::Off && m_gpu_timing_enabled) + PopTimestampQuery(); + + static constexpr std::array clear_color = {}; + m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), clear_color.data()); + m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr); if (m_state.rt_view) - m_ctx->OMSetRenderTargets(1, &m_state.rt_view, m_state.dsv); + m_state.rt_view->Release(); + m_state.rt_view = m_swap_chain_rtv.get(); + m_state.rt_view->AddRef(); + if (m_state.dsv) + { + m_state.dsv->Release(); + m_state.dsv = nullptr; + } + + const GSVector2i size = GetWindowSize(); + SetViewport(size); + SetScissor(GSVector4i::loadh(size)); + + return PresentResult::OK; +} + +void GSDevice11::EndPresent() +{ + RenderImGui(); + + // See note in BeginPresent() for why it's conditional on vsync-off. + const bool vsync_on = m_vsync_mode != VsyncMode::Off; + if (!vsync_on && m_gpu_timing_enabled) + PopTimestampQuery(); + + if (!vsync_on && m_using_allow_tearing) + m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); else - m_ctx->OMSetRenderTargets(0, nullptr, m_state.dsv); + m_swap_chain->Present(static_cast(vsync_on), 0); + + if (m_gpu_timing_enabled) + KickTimestampQuery(); + + // clear out the swap chain view, it might get resized.. + OMSetRenderTargets(nullptr, nullptr, nullptr); +} + +bool GSDevice11::CreateTimestampQueries() +{ + for (u32 i = 0; i < NUM_TIMESTAMP_QUERIES; i++) + { + for (u32 j = 0; j < 3; j++) + { + const CD3D11_QUERY_DESC qdesc((j == 0) ? D3D11_QUERY_TIMESTAMP_DISJOINT : D3D11_QUERY_TIMESTAMP); + const HRESULT hr = m_dev->CreateQuery(&qdesc, m_timestamp_queries[i][j].put()); + if (FAILED(hr)) + { + m_timestamp_queries = {}; + return false; + } + } + } + + KickTimestampQuery(); + return true; +} + +void GSDevice11::DestroyTimestampQueries() +{ + if (!m_timestamp_queries[0][0]) + return; + + if (m_timestamp_query_started) + m_ctx->End(m_timestamp_queries[m_write_timestamp_query][1].get()); + + m_timestamp_queries = {}; + m_read_timestamp_query = 0; + m_write_timestamp_query = 0; + m_waiting_timestamp_queries = 0; + m_timestamp_query_started = 0; +} + +void GSDevice11::PopTimestampQuery() +{ + while (m_waiting_timestamp_queries > 0) + { + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint; + const HRESULT disjoint_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][0].get(), &disjoint, + sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH); + if (disjoint_hr != S_OK) + break; + + if (disjoint.Disjoint) + { + DevCon.WriteLn("GPU timing disjoint, resetting."); + m_read_timestamp_query = 0; + m_write_timestamp_query = 0; + m_waiting_timestamp_queries = 0; + m_timestamp_query_started = 0; + } + else + { + u64 start = 0, end = 0; + const HRESULT start_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][1].get(), &start, + sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH); + const HRESULT end_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][2].get(), &end, + sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH); + if (start_hr == S_OK && end_hr == S_OK) + { + m_accumulated_gpu_time += static_cast( + static_cast(end - start) / (static_cast(disjoint.Frequency) / 1000.0)); + m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; + m_waiting_timestamp_queries--; + } + } + } + + if (m_timestamp_query_started) + { + m_ctx->End(m_timestamp_queries[m_write_timestamp_query][2].get()); + m_ctx->End(m_timestamp_queries[m_write_timestamp_query][0].get()); + m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; + m_timestamp_query_started = false; + m_waiting_timestamp_queries++; + } +} + +void GSDevice11::KickTimestampQuery() +{ + if (m_timestamp_query_started || !m_timestamp_queries[0][0] || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES) + return; + + m_ctx->Begin(m_timestamp_queries[m_write_timestamp_query][0].get()); + m_ctx->End(m_timestamp_queries[m_write_timestamp_query][1].get()); + m_timestamp_query_started = true; +} + +bool GSDevice11::SetGPUTimingEnabled(bool enabled) +{ + if (m_gpu_timing_enabled == enabled) + return true; + + m_gpu_timing_enabled = enabled; + if (m_gpu_timing_enabled) + { + return CreateTimestampQueries(); + } + else + { + DestroyTimestampQueries(); + return true; + } +} + +float GSDevice11::GetAndResetAccumulatedGPUTime() +{ + const float value = m_accumulated_gpu_time; + m_accumulated_gpu_time = 0.0f; + return value; } void GSDevice11::DrawPrimitive() @@ -464,17 +1021,21 @@ void GSDevice11::ClearRenderTarget(GSTexture* t, u32 c) m_ctx->ClearRenderTargetView(*(GSTexture11*)t, color.v); } +void GSDevice11::InvalidateRenderTarget(GSTexture* t) +{ + if (t->IsDepthStencil()) + m_ctx->DiscardView(static_cast(*static_cast(t))); + else + m_ctx->DiscardView(static_cast(*static_cast(t))); +} + void GSDevice11::ClearDepth(GSTexture* t) { - if (!t) - return; m_ctx->ClearDepthStencilView(*(GSTexture11*)t, D3D11_CLEAR_DEPTH, 0.0f, 0); } void GSDevice11::ClearStencil(GSTexture* t, u8 c) { - if (!t) - return; m_ctx->ClearDepthStencilView(*(GSTexture11*)t, D3D11_CLEAR_STENCIL, 0, c); } @@ -545,22 +1106,15 @@ GSTexture* GSDevice11::CreateSurface(GSTexture::Type type, int width, int height break; } - GSTexture11* t = nullptr; - wil::com_ptr_nothrow texture; HRESULT hr = m_dev->CreateTexture2D(&desc, nullptr, texture.put()); - - if (SUCCEEDED(hr)) + if (FAILED(hr)) { - t = new GSTexture11(std::move(texture), desc, type, format); - assert(type == t->GetType()); - } - else - { - throw std::bad_alloc(); + Console.Error("DX11: Failed to allocate %dx%d surface", width, height); + return nullptr; } - return t; + return new GSTexture11(std::move(texture), desc, type, format); } std::unique_ptr GSDevice11::CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) @@ -640,7 +1194,7 @@ void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* } else { - ds = GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()); + ds = GSVector2i(m_window_info.surface_width, m_window_info.surface_height); } @@ -712,7 +1266,7 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* } else { - ds = GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()); + ds = GSVector2i(m_window_info.surface_width, m_window_info.surface_height); } DisplayConstantBuffer cb; @@ -1039,6 +1593,177 @@ bool GSDevice11::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, cons return true; } +bool GSDevice11::CreateImGuiResources() +{ + HRESULT hr; + + const std::optional hlsl = Host::ReadResourceFileToString("shaders/dx11/imgui.fx"); + if (!hlsl.has_value()) + { + Console.Error("Failed to read imgui.fx"); + return false; + } + + // clang-format off + static constexpr D3D11_INPUT_ELEMENT_DESC layout[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + // clang-format on + + if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_imgui.vs.put(), m_imgui.il.put(), layout, + std::size(layout), hlsl.value(), nullptr, "vs_main") || + !(m_imgui.ps = m_shader_cache.GetPixelShader(m_dev.get(), hlsl.value(), nullptr, "ps_main"))) + { + Console.Error("Failed to compile ImGui shaders"); + return false; + } + + D3D11_BLEND_DESC blend_desc = {}; + blend_desc.RenderTarget[0].BlendEnable = true; + blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + hr = m_dev->CreateBlendState(&blend_desc, m_imgui.bs.put()); + if (FAILED(hr)) + { + Console.Error("CreateImGuiResources(): CreateBlendState() failed: %08X", hr); + return false; + } + + D3D11_BUFFER_DESC buffer_desc = {}; + buffer_desc.Usage = D3D11_USAGE_DEFAULT; + buffer_desc.ByteWidth = sizeof(float) * 4 * 4; + buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + hr = m_dev->CreateBuffer(&buffer_desc, nullptr, m_imgui.vs_cb.put()); + if (FAILED(hr)) + { + Console.Error("CreateImGuiResources(): CreateBlendState() failed: %08X", hr); + return false; + } + + return true; +} + +void GSDevice11::RenderImGui() +{ + ImGui::Render(); + const ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount == 0) + return; + + const float L = 0.0f; + const float R = static_cast(m_window_info.surface_width); + const float T = 0.0f; + const float B = static_cast(m_window_info.surface_height); + + // clang-format off + 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 }, + { 0.0f, 0.0f, 0.5f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, + }; + // clang-format on + + m_ctx->UpdateSubresource(m_imgui.vs_cb.get(), 0, nullptr, ortho_projection, 0, 0); + + const UINT vb_stride = sizeof(ImDrawVert); + const UINT vb_offset = 0; + m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &vb_stride, &vb_offset); + m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R16_UINT, 0); + IASetInputLayout(m_imgui.il.get()); + IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + VSSetShader(m_imgui.vs.get(), m_imgui.vs_cb.get()); + GSSetShader(nullptr, nullptr); + PSSetShader(m_imgui.ps.get(), nullptr); + OMSetBlendState(m_imgui.bs.get(), 0.0f); + OMSetDepthStencilState(m_convert.dss.get(), 0); + PSSetSamplerState(m_convert.ln.get()); + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // This mess is because the vertex size isn't the same... + u32 vertex_offset; + { + static_assert(Common::IsPow2(sizeof(GSVertexPT1))); + + D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE; + + const u32 unaligned_size = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); + u32 start_pos = Common::AlignUp(m_vb_pos, sizeof(ImDrawVert)); + u32 end_pos = Common::AlignUpPow2(start_pos + unaligned_size, sizeof(GSVertexPT1)); + if (end_pos > VERTEX_BUFFER_SIZE) + { + type = D3D11_MAP_WRITE_DISCARD; + m_vb_pos = 0; + start_pos = 0; + end_pos = Common::AlignUpPow2(unaligned_size, sizeof(GSVertexPT1)); + } + + m_vb_pos = end_pos; + vertex_offset = start_pos / sizeof(ImDrawVert); + + D3D11_MAPPED_SUBRESOURCE sr; + const HRESULT hr = m_ctx->Map(m_vb.get(), 0, type, 0, &sr); + if (FAILED(hr)) + continue; + + std::memcpy(static_cast(sr.pData) + start_pos, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + m_ctx->Unmap(m_vb.get(), 0); + } + + // Bit awkward, because this is using 16-bit indices, not 32-bit. + static_assert(sizeof(ImDrawIdx) == sizeof(u16)); + const u32 index_count = static_cast(cmd_list->IdxBuffer.Size + 1) / 2; + u32* index_map = IAMapIndexBuffer(index_count); + if (!index_map) + continue; + + const u32 index_start = m_index.start * 2; + std::memcpy(index_map, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + IAUnmapIndexBuffer(index_count); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + pxAssert(!pcmd->UserCallback); + + const GSVector4 clip = GSVector4::load(&pcmd->ClipRect); + if ((clip.zwzw() <= clip.xyxy()).mask() != 0) + continue; + + const GSVector4i iclip = GSVector4i(clip); + if (!m_state.scissor.eq(iclip)) + { + m_state.scissor = iclip; + m_ctx->RSSetScissorRects(1, reinterpret_cast(&iclip)); + } + + // Since we don't have the GSTexture... + m_state.ps_sr_views[0] = static_cast(pcmd->GetTexID()); + PSUpdateShaderState(); + + m_ctx->DrawIndexed(pcmd->ElemCount, index_start + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset); + } + + g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size); + } + + m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset); + m_ctx->IASetIndexBuffer(m_ib.get(), DXGI_FORMAT_R32_UINT, 0); +} + void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm) { // sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows @@ -1291,9 +2016,6 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector ID3D11RenderTargetView* rtv = nullptr; ID3D11DepthStencilView* dsv = nullptr; - if (!rt && !ds) - throw GSRecoverableError(); - if (rt) rtv = *(GSTexture11*)rt; if (ds) dsv = *(GSTexture11*)ds; @@ -1317,31 +2039,34 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector if (changed) m_ctx->OMSetRenderTargets(1, &rtv, dsv); - const GSVector2i size = rt ? rt->GetSize() : ds->GetSize(); - if (m_state.viewport != size) + if (rt || ds) { - m_state.viewport = size; + const GSVector2i size = rt ? rt->GetSize() : ds->GetSize(); + SetViewport(size); + SetScissor(scissor ? *scissor : GSVector4i::loadh(size)); + } +} - D3D11_VIEWPORT vp; - memset(&vp, 0, sizeof(vp)); - - vp.TopLeftX = 0.0f; - vp.TopLeftY = 0.0f; - vp.Width = (float)size.x; - vp.Height = (float)size.y; - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; +void GSDevice11::SetViewport(const GSVector2i& viewport) +{ + if (m_state.viewport != viewport) + { + m_state.viewport = viewport; + const D3D11_VIEWPORT vp = { + 0.0f, 0.0f, static_cast(viewport.x), static_cast(viewport.y), 0.0f, 1.0f}; m_ctx->RSSetViewports(1, &vp); } +} - GSVector4i r = scissor ? *scissor : GSVector4i(size).zwxy(); +void GSDevice11::SetScissor(const GSVector4i& scissor) +{ + static_assert(sizeof(D3D11_RECT) == sizeof(GSVector4i)); - if (!m_state.scissor.eq(r)) + if (!m_state.scissor.eq(scissor)) { - m_state.scissor = r; - - m_ctx->RSSetScissorRects(1, reinterpret_cast(&r)); + m_state.scissor = scissor; + m_ctx->RSSetScissorRects(1, reinterpret_cast(&scissor)); } } diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index 37990bf1dd..b7a2032efd 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -21,7 +21,7 @@ #include "common/D3D11/ShaderCache.h" #include #include -#include +#include #include struct GSVertexShader11 @@ -116,32 +116,47 @@ private: MAX_SAMPLERS = 1, VERTEX_BUFFER_SIZE = 32 * 1024 * 1024, INDEX_BUFFER_SIZE = 16 * 1024 * 1024, + NUM_TIMESTAMP_QUERIES = 5, }; - int m_d3d_texsize; - void SetFeatures(); - GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) final; + bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); + bool CreateSwapChainRTV(); - std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) final; + bool CreateTimestampQueries(); + void DestroyTimestampQueries(); + void PopTimestampQuery(); + void KickTimestampQuery(); - void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c, const bool linear) final; - void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, bool linear, const InterlaceConstantBuffer& cb) final; - void DoFXAA(GSTexture* sTex, GSTexture* dTex) final; - void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) final; + void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c, const bool linear) override; + void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, bool linear, const InterlaceConstantBuffer& cb) override; + void DoFXAA(GSTexture* sTex, GSTexture* dTex) override; + void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) override; bool CreateCASShaders(); - bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) final; + bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) override; - wil::com_ptr_nothrow m_dev; - wil::com_ptr_nothrow m_ctx; + bool CreateImGuiResources(); + void RenderImGui(); + + wil::com_ptr_nothrow m_dxgi_factory; + wil::com_ptr_nothrow m_dev; + wil::com_ptr_nothrow m_ctx; wil::com_ptr_nothrow m_annotation; - wil::com_ptr_nothrow m_swapchain; + + wil::com_ptr_nothrow m_swap_chain; + wil::com_ptr_nothrow m_swap_chain_rtv; + wil::com_ptr_nothrow m_vb; wil::com_ptr_nothrow m_ib; u32 m_vb_pos = 0; // bytes u32 m_ib_pos = 0; // indices/sizeof(u32) + int m_d3d_texsize = 0; + + bool m_allow_tearing_supported = false; + bool m_using_flip_model_swap_chain = true; + bool m_using_allow_tearing = false; struct { @@ -166,6 +181,13 @@ private: ID3D11DepthStencilView* dsv; } m_state; + std::array, 3>, NUM_TIMESTAMP_QUERIES> m_timestamp_queries = {}; + float m_accumulated_gpu_time = 0.0f; + u8 m_read_timestamp_query = 0; + u8 m_write_timestamp_query = 0; + u8 m_waiting_timestamp_queries = 0; + bool m_timestamp_query_started = false; + bool m_gpu_timing_enabled = false; struct { @@ -222,6 +244,15 @@ private: wil::com_ptr_nothrow cs_sharpen; } m_cas; + struct + { + wil::com_ptr_nothrow il; + wil::com_ptr_nothrow vs; + wil::com_ptr_nothrow ps; + wil::com_ptr_nothrow bs; + wil::com_ptr_nothrow vs_cb; + } m_imgui; + // Shaders... std::unordered_map m_vs; @@ -237,8 +268,6 @@ private: GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache; GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache; - std::unique_ptr m_download_tex; - D3D11::ShaderCache m_shader_cache; std::string m_tfx_source; @@ -247,13 +276,32 @@ public: ~GSDevice11() override; __fi static GSDevice11* GetInstance() { return static_cast(g_gs_device.get()); } - __fi ID3D11Device* GetD3DDevice() const { return m_dev.get(); } - __fi ID3D11DeviceContext* GetD3DContext() const { return m_ctx.get(); } + __fi ID3D11Device1* GetD3DDevice() const { return m_dev.get(); } + __fi ID3D11DeviceContext1* GetD3DContext() const { return m_ctx.get(); } - bool Create() override; + bool Create(const WindowInfo& wi, VsyncMode vsync) override; + void Destroy() override; - void ResetAPIState() override; - void RestoreAPIState() override; + RenderAPI GetRenderAPI() const override; + + bool ChangeWindow(const WindowInfo& new_wi) override; + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsExclusiveFullscreen() const override; + bool IsExclusiveFullscreen() override; + bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + bool HasSurface() const override; + void DestroySurface() override; + std::string GetDriverInfo() const override; + + bool GetHostRefreshRate(float* refresh_rate) override; + + void SetVSync(VsyncMode mode) override; + + PresentResult BeginPresent(bool frame_skip) override; + void EndPresent() override; + + bool SetGPUTimingEnabled(bool enabled) override; + float GetAndResetAccumulatedGPUTime() override; void DrawPrimitive(); void DrawIndexedPrimitive(); @@ -261,6 +309,7 @@ public: void ClearRenderTarget(GSTexture* t, const GSVector4& c) override; void ClearRenderTarget(GSTexture* t, u32 c) override; + void InvalidateRenderTarget(GSTexture* t) override; void ClearDepth(GSTexture* t) override; void ClearStencil(GSTexture* t, u8 c) override; @@ -268,6 +317,9 @@ public: void PopDebugGroup() override; void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override; + GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override; + std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override; + void CloneTexture(GSTexture* src, GSTexture** dest, const GSVector4i& rect); void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override; @@ -296,7 +348,7 @@ public: void IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology); void VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb); - void GSSetShader(ID3D11GeometryShader* gs, ID3D11Buffer* gs_cb = NULL); + void GSSetShader(ID3D11GeometryShader* gs, ID3D11Buffer* gs_cb = nullptr); void PSSetShaderResources(GSTexture* sr0, GSTexture* sr1); void PSSetShaderResource(int i, GSTexture* sr); @@ -306,7 +358,9 @@ public: void OMSetDepthStencilState(ID3D11DepthStencilState* dss, u8 sref); void OMSetBlendState(ID3D11BlendState* bs, float bf); - void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = NULL); + void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = nullptr); + void SetViewport(const GSVector2i& viewport); + void SetScissor(const GSVector4i& scissor); bool CreateTextureFX(); void SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer* cb); @@ -314,11 +368,11 @@ public: void SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel); void SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8 afix); - void RenderHW(GSHWDrawConfig& config) final; + void RenderHW(GSHWDrawConfig& config) override; - void ClearSamplerCache() final; + void ClearSamplerCache() override; - ID3D11Device* operator->() { return m_dev.get(); } - operator ID3D11Device*() { return m_dev.get(); } - operator ID3D11DeviceContext*() { return m_ctx.get(); } + ID3D11Device1* operator->() { return m_dev.get(); } + operator ID3D11Device1*() { return m_dev.get(); } + operator ID3D11DeviceContext1*() { return m_ctx.get(); } }; diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 93c3614b08..d2321e41c6 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -14,6 +14,16 @@ */ #include "PrecompiledHeader.h" + +#include "GS/GS.h" +#include "GS/GSGL.h" +#include "GS/GSPerfMon.h" +#include "GS/GSUtil.h" +#include "GS/Renderers/DX11/D3D.h" +#include "GS/Renderers/DX12/GSDevice12.h" +#include "Host.h" +#include "ShaderCacheVersion.h" + #include "common/D3D12/Builders.h" #include "common/D3D12/Context.h" #include "common/D3D12/ShaderCache.h" @@ -21,15 +31,10 @@ #include "common/Align.h" #include "common/ScopedGuard.h" #include "common/StringUtil.h" + #include "D3D12MemAlloc.h" -#include "GS.h" -#include "GSDevice12.h" -#include "GS/GSGL.h" -#include "GS/GSPerfMon.h" -#include "GS/GSUtil.h" -#include "Host.h" -#include "HostDisplay.h" -#include "ShaderCacheVersion.h" +#include "imgui.h" + #include #include @@ -94,16 +99,52 @@ D3D_SHADER_MACRO* GSDevice12::ShaderMacro::GetPtr(void) return (D3D_SHADER_MACRO*)mout.data(); } -GSDevice12::GSDevice12() +GSDevice12::GSDevice12() = default; + +GSDevice12::~GSDevice12() { - std::memset(&m_pipeline_selector, 0, sizeof(m_pipeline_selector)); + pxAssert(!g_d3d12_context); } -GSDevice12::~GSDevice12() {} - -bool GSDevice12::Create() +RenderAPI GSDevice12::GetRenderAPI() const { - if (!GSDevice::Create() || !CheckFeatures()) + return RenderAPI::D3D12; +} + +bool GSDevice12::HasSurface() const +{ + return static_cast(m_swap_chain); +} + +bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync) +{ + if (!GSDevice::Create(wi, vsync)) + return false; + + m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice); + if (!m_dxgi_factory) + return false; + + ComPtr dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), EmuConfig.GS.Adapter); + + if (!D3D12::Context::Create(m_dxgi_factory.get(), dxgi_adapter.get(), EmuConfig.GS.UseDebugDevice)) + { + Console.Error("Failed to create D3D12 context"); + return false; + } + + 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); + + if (!CheckFeatures()) + { + Console.Error("Your GPU does not support the required D3D12 features."); + return false; + } + + if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr)) return false; { @@ -157,6 +198,9 @@ bool GSDevice12::Create() } CompileCASPipelines(); + if (!CompileImGuiPipeline()) + return false; + InitializeState(); InitializeSamplers(); return true; @@ -164,25 +208,381 @@ bool GSDevice12::Create() void GSDevice12::Destroy() { - if (!g_d3d12_context) + GSDevice::Destroy(); + + if (g_d3d12_context) + { + EndRenderPass(); + ExecuteCommandList(true); + DestroyResources(); + + g_d3d12_context->WaitForGPUIdle(); + GSDevice12::DestroySurface(); + g_d3d12_context->Destroy(); + } +} + +bool GSDevice12::GetHostRefreshRate(float* refresh_rate) +{ + if (m_swap_chain && IsExclusiveFullscreen()) + { + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 && + desc.BufferDesc.RefreshRate.Denominator > 0) + { + DevCon.WriteLn( + "using fs rr: %u %u", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator); + *refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + return true; + } + } + + return GSDevice::GetHostRefreshRate(refresh_rate); +} + +void GSDevice12::SetVSync(VsyncMode mode) +{ + m_vsync_mode = mode; +} + + +bool GSDevice12::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode) +{ + if (m_window_info.type != WindowInfo::Type::Win32) + return false; + + const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + const u32 width = static_cast(client_rc.right - client_rc.left); + const u32 height = static_cast(client_rc.bottom - client_rc.top); + + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; + swap_chain_desc.Width = width; + swap_chain_desc.Height = height; + swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.BufferCount = 3; + 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 && !fullscreen_mode); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; + if (fullscreen_mode) + { + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swap_chain_desc.Width = fullscreen_mode->Width; + swap_chain_desc.Height = fullscreen_mode->Height; + fs_desc.RefreshRate = fullscreen_mode->RefreshRate; + fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering; + fs_desc.Scaling = fullscreen_mode->Scaling; + fs_desc.Windowed = FALSE; + } + + DevCon.WriteLn("Creating a %dx%d %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height, + fullscreen_mode ? "full-screen" : "windowed"); + + HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd, + &swap_chain_desc, fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put()); + if (FAILED(hr)) + { + Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr); + return false; + } + + hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + if (FAILED(hr)) + Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed"); + + return CreateSwapChainRTV(); +} + +bool GSDevice12::CreateSwapChainRTV() +{ + DXGI_SWAP_CHAIN_DESC swap_chain_desc; + HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc); + if (FAILED(hr)) + return false; + + for (u32 i = 0; i < swap_chain_desc.BufferCount; i++) + { + ComPtr backbuffer; + hr = m_swap_chain->GetBuffer(i, IID_PPV_ARGS(backbuffer.put())); + if (FAILED(hr)) + { + Console.Error("GetBuffer for RTV failed: 0x%08X", hr); + return false; + } + + D3D12::Texture tex; + if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format, + DXGI_FORMAT_UNKNOWN, D3D12_RESOURCE_STATE_PRESENT)) + { + return false; + } + + m_swap_chain_buffers.push_back(std::move(tex)); + } + + m_window_info.surface_width = swap_chain_desc.BufferDesc.Width; + m_window_info.surface_height = swap_chain_desc.BufferDesc.Height; + DevCon.WriteLn("Swap chain buffer size: %ux%u", 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))) + { + m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); + } + else + { + m_window_info.surface_refresh_rate = 0.0f; + } + } + + m_current_swap_chain_buffer = 0; + return true; +} + +void GSDevice12::DestroySwapChainRTVs() +{ + for (D3D12::Texture& buffer : m_swap_chain_buffers) + buffer.Destroy(false); + m_swap_chain_buffers.clear(); + m_current_swap_chain_buffer = 0; +} + +bool GSDevice12::ChangeWindow(const WindowInfo& new_wi) +{ + DestroySurface(); + + m_window_info = new_wi; + if (new_wi.type == WindowInfo::Type::Surfaceless) + return true; + + return CreateSwapChain(nullptr); +} + +void GSDevice12::DestroySurface() +{ + ExecuteCommandList(true); + + if (IsExclusiveFullscreen()) + SetExclusiveFullscreen(false, 0, 0, 0.0f); + + DestroySwapChainRTVs(); + m_swap_chain.reset(); +} + +std::string GSDevice12::GetDriverInfo() const +{ + std::string ret = "Unknown Feature Level"; + + static constexpr std::array, 4> feature_level_names = {{ + {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"}, + {D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_1"}, + {D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"}, + {D3D_FEATURE_LEVEL_11_1, "D3D_FEATURE_LEVEL_11_1"}, + }}; + + const D3D_FEATURE_LEVEL fl = g_d3d12_context->GetFeatureLevel(); + for (size_t i = 0; i < std::size(feature_level_names); i++) + { + if (fl == std::get<0>(feature_level_names[i])) + { + ret = std::get<1>(feature_level_names[i]); + break; + } + } + + ret += "\n"; + + IDXGIAdapter* adapter = g_d3d12_context->GetAdapter(); + DXGI_ADAPTER_DESC desc; + if (adapter && SUCCEEDED(adapter->GetDesc(&desc))) + { + ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId); + ret += StringUtil::WideStringToUTF8String(desc.Description); + ret += "\n"; + + const std::string driver_version(D3D::GetDriverVersionFromLUID(desc.AdapterLuid)); + if (!driver_version.empty()) + { + ret += "Driver Version: "; + ret += driver_version; + } + } + + return ret; +} + +void GSDevice12::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + if (!m_swap_chain) + return; + + m_window_info.surface_scale = new_window_scale; + + if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height) return; - EndRenderPass(); ExecuteCommandList(true); - DestroyResources(); - GSDevice::Destroy(); + + DestroySwapChainRTVs(); + + 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)) + Console.Error("ResizeBuffers() failed: 0x%08X", hr); + + if (!CreateSwapChainRTV()) + pxFailRel("Failed to recreate swap chain RTV after resize"); } -void GSDevice12::ResetAPIState() +bool GSDevice12::SupportsExclusiveFullscreen() const +{ + return true; +} + +bool GSDevice12::IsExclusiveFullscreen() +{ + BOOL is_fullscreen = FALSE; + return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen); +} + +bool GSDevice12::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + if (!m_swap_chain) + return false; + + BOOL is_fullscreen = FALSE; + HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr); + if (!fullscreen) + { + // leaving fullscreen + if (is_fullscreen) + return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr)); + else + return true; + } + + IDXGIOutput* output; + if (FAILED(hr = m_swap_chain->GetContainingOutput(&output))) + return false; + + DXGI_SWAP_CHAIN_DESC current_desc; + hr = m_swap_chain->GetDesc(¤t_desc); + if (FAILED(hr)) + return false; + + DXGI_MODE_DESC new_mode = current_desc.BufferDesc; + new_mode.Width = width; + new_mode.Height = height; + new_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); + new_mode.RefreshRate.Denominator = 1000u; + + DXGI_MODE_DESC closest_mode; + if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) || + new_mode.Format != current_desc.BufferDesc.Format) + { + Console.Error("Failed to find closest matching mode, hr=%08X", hr); + return false; + } + + if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Width && + new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator && + new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator) + { + DevCon.WriteLn("Fullscreen mode already set"); + return true; + } + + g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep); + DestroySwapChainRTVs(); + m_swap_chain.reset(); + + if (!CreateSwapChain(&closest_mode)) + { + Console.Error("Failed to create a fullscreen swap chain"); + if (!CreateSwapChain(nullptr)) + pxFailRel("Failed to recreate windowed swap chain"); + + return false; + } + + return true; +} + +GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip) { EndRenderPass(); + + if (m_device_lost) + return PresentResult::DeviceLost; + + if (frame_skip || !m_swap_chain) + return PresentResult::FrameSkipped; + + static constexpr std::array clear_color = {}; + D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; + + ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList(); + swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET); + cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), clear_color.data(), 0, nullptr); + cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetWriteDescriptor().cpu_handle, FALSE, nullptr); + + const D3D12_VIEWPORT vp{0.0f, 0.0f, static_cast(m_window_info.surface_width), + static_cast(m_window_info.surface_height), 0.0f, 1.0f}; + const D3D12_RECT scissor{ + 0, 0, static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)}; + cmdlist->RSSetViewports(1, &vp); + cmdlist->RSSetScissorRects(1, &scissor); + return PresentResult::OK; } -void GSDevice12::RestoreAPIState() +void GSDevice12::EndPresent() { + RenderImGui(); + + D3D12::Texture& 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(m_swap_chain_buffers.size())); + + swap_chain_buf.TransitionToState(g_d3d12_context->GetCommandList(), D3D12_RESOURCE_STATE_PRESENT); + if (!g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::None)) + { + m_device_lost = true; + InvalidateCachedState(); + return; + } + + const bool vsync = static_cast(m_vsync_mode != VsyncMode::Off); + if (!vsync && m_using_allow_tearing) + m_swap_chain->Present(0, DXGI_PRESENT_ALLOW_TEARING); + else + m_swap_chain->Present(static_cast(vsync), 0); + InvalidateCachedState(); } +bool GSDevice12::SetGPUTimingEnabled(bool enabled) +{ + g_d3d12_context->SetEnableGPUTiming(enabled); + return true; +} + +float GSDevice12::GetAndResetAccumulatedGPUTime() +{ + return g_d3d12_context->GetAndResetAccumulatedGPUTime(); +} + void GSDevice12::PushDebugGroup(const char* fmt, ...) { } @@ -453,7 +853,7 @@ void GSDevice12::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* { DisplayConstantBuffer cb; cb.SetSource(sRect, sTex->GetSize()); - cb.SetTarget(dRect, dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight())); + cb.SetTarget(dRect, dTex ? dTex->GetSize() : GSVector2i(GetWindowWidth(), GetWindowHeight())); cb.SetTime(shaderTime); SetUtilityRootSignature(); SetUtilityPushConstants(&cb, sizeof(cb)); @@ -661,8 +1061,7 @@ void GSDevice12::DoStretchRect(GSTexture12* sTex, const GSVector4& sRect, GSText const bool is_present = (!dTex); const bool depth = (dTex && dTex->GetType() == GSTexture::Type::DepthStencil); - const GSVector2i size( - is_present ? GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()) : dTex->GetSize()); + const GSVector2i size(is_present ? GSVector2i(GetWindowWidth(), GetWindowHeight()) : dTex->GetSize()); const GSVector4i dtex_rc(0, 0, size.x, size.y); const GSVector4i dst_rc(GSVector4i(dRect).rintersect(dtex_rc)); @@ -918,6 +1317,163 @@ bool GSDevice12::CompileCASPipelines() return true; } +bool GSDevice12::CompileImGuiPipeline() +{ + const std::optional hlsl = Host::ReadResourceFileToString("shaders/dx11/imgui.fx"); + if (!hlsl.has_value()) + { + Console.Error("Failed to read imgui.fx"); + return false; + } + + const ComPtr vs = m_shader_cache.GetVertexShader(hlsl.value(), nullptr, "vs_main"); + const ComPtr ps = m_shader_cache.GetPixelShader(hlsl.value(), nullptr, "ps_main"); + if (!vs || !ps) + { + Console.Error("Failed to compile ImGui shaders"); + return false; + } + + D3D12::GraphicsPipelineBuilder gpb; + gpb.SetRootSignature(m_utility_root_signature.get()); + gpb.AddVertexAttribute("POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, pos)); + gpb.AddVertexAttribute("TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(ImDrawVert, uv)); + gpb.AddVertexAttribute("COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col)); + gpb.SetPrimitiveTopologyType(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE); + gpb.SetVertexShader(vs.get()); + gpb.SetPixelShader(ps.get()); + gpb.SetNoCullRasterizationState(); + gpb.SetNoDepthTestState(); + gpb.SetBlendState(0, true, D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, + D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD); + gpb.SetRenderTarget(0, DXGI_FORMAT_R8G8B8A8_UNORM); + + m_imgui_pipeline = gpb.Create(g_d3d12_context->GetDevice(), m_shader_cache, false); + if (!m_imgui_pipeline) + { + Console.Error("Failed to compile ImGui pipeline"); + return false; + } + + D3D12::SetObjectName(m_imgui_pipeline.get(), "ImGui pipeline"); + return true; +} + +void GSDevice12::RenderImGui() +{ + ImGui::Render(); + const ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount == 0) + return; + + const float L = 0.0f; + const float R = static_cast(m_window_info.surface_width); + const float T = 0.0f; + const float B = static_cast(m_window_info.surface_height); + + // clang-format off + 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 }, + { 0.0f, 0.0f, 0.5f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, + }; + // clang-format on + + SetUtilityRootSignature(); + SetUtilityPushConstants(ortho_projection, sizeof(ortho_projection)); + SetPipeline(m_imgui_pipeline.get()); + SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + if (m_utility_sampler_cpu != m_linear_sampler_cpu) + { + m_utility_sampler_cpu = m_linear_sampler_cpu; + m_dirty_flags |= DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE; + + // just skip if we run out.. we can't resume the present render pass :/ + if (!g_d3d12_context->GetSamplerAllocator().LookupSingle(&m_utility_sampler_gpu, m_linear_sampler_cpu)) + { + Console.Warning("Skipping ImGui draw because of no descriptors"); + return; + } + } + + // this is for presenting, we don't want to screw with the viewport/scissor set by display + m_dirty_flags &= ~(DIRTY_FLAG_RENDER_TARGET | DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR); + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + u32 vertex_offset; + { + const u32 size = sizeof(ImDrawVert) * static_cast(cmd_list->VtxBuffer.Size); + if (!m_vertex_stream_buffer.ReserveMemory(size, sizeof(ImDrawVert))) + { + Console.Warning("Skipping ImGui draw because of no vertex buffer space"); + return; + } + + vertex_offset = m_vertex_stream_buffer.GetCurrentOffset() / sizeof(ImDrawVert); + std::memcpy(m_vertex_stream_buffer.GetCurrentHostPointer(), cmd_list->VtxBuffer.Data, size); + m_vertex_stream_buffer.CommitMemory(size); + } + + u32 index_offset; + { + const u32 size = sizeof(ImDrawIdx) * static_cast(cmd_list->IdxBuffer.Size); + if (!m_index_stream_buffer.ReserveMemory(size, sizeof(ImDrawIdx))) + { + Console.Warning("Skipping ImGui draw because of no vertex buffer space"); + return; + } + + index_offset = m_index_stream_buffer.GetCurrentOffset() / sizeof(ImDrawIdx); + std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), cmd_list->IdxBuffer.Data, size); + m_index_stream_buffer.CommitMemory(size); + } + + SetVertexBuffer(m_vertex_stream_buffer.GetGPUPointer(), m_vertex_stream_buffer.GetSize(), sizeof(ImDrawVert)); + SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + pxAssert(!pcmd->UserCallback); + + const GSVector4 clip = GSVector4::load(&pcmd->ClipRect); + if ((clip.zwzw() <= clip.xyxy()).mask() != 0) + continue; + + SetScissor(GSVector4i(clip)); + + // Since we don't have the GSTexture... + D3D12::Texture* tex = static_cast(pcmd->GetTexID()); + D3D12::DescriptorHandle handle = tex ? tex->GetSRVDescriptor() : m_null_texture.GetSRVDescriptor(); + if (m_utility_texture_cpu != handle) + { + m_utility_texture_cpu = handle; + m_dirty_flags |= DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE; + + if (!GetTextureGroupDescriptors(&m_utility_texture_gpu, &handle, 1)) + { + Console.Warning("Skipping ImGui draw because of no descriptors"); + return; + } + } + + if (ApplyUtilityState()) + { + g_d3d12_context->GetCommandList()->DrawIndexedInstanced( + pcmd->ElemCount, 1, index_offset + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset, 0); + } + } + + g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size); + } +} + bool GSDevice12::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) { EndRenderPass(); @@ -1182,7 +1738,7 @@ bool GSDevice12::CreateRootSignatures() // Convert Pipeline Layout ////////////////////////////////////////////////////////////////////////// rsb.SetInputAssemblerFlag(); - rsb.Add32BitConstants(0, CONVERT_PUSH_CONSTANTS_SIZE / sizeof(u32), static_cast(D3D12_SHADER_VISIBILITY_VERTEX | D3D12_SHADER_VISIBILITY_PIXEL)); + rsb.Add32BitConstants(0, CONVERT_PUSH_CONSTANTS_SIZE / sizeof(u32), D3D12_SHADER_VISIBILITY_ALL); rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, NUM_UTILITY_SAMPLERS, D3D12_SHADER_VISIBILITY_PIXEL); rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, NUM_UTILITY_SAMPLERS, D3D12_SHADER_VISIBILITY_PIXEL); if (!(m_utility_root_signature = rsb.Create())) @@ -1528,6 +2084,12 @@ void GSDevice12::DestroyResources() { g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep); + m_convert_vs.reset(); + + m_cas_sharpen_pipeline.reset(); + m_cas_upscale_pipeline.reset(); + m_cas_root_signature.reset(); + for (auto& it : m_tfx_pipelines) g_d3d12_context->DeferObjectDestruction(it.second.get()); m_tfx_pipelines.clear(); @@ -1544,6 +2106,7 @@ void GSDevice12::DestroyResources() m_date_image_setup_pipelines = {}; m_fxaa_pipeline.reset(); m_shadeboost_pipeline.reset(); + m_imgui_pipeline.reset(); m_linear_sampler_cpu.Clear(); m_point_sampler_cpu.Clear(); @@ -1563,6 +2126,8 @@ void GSDevice12::DestroyResources() m_tfx_root_signature.reset(); m_null_texture.Destroy(false); + + m_shader_cache.Close(); } const ID3DBlob* GSDevice12::GetTFXVertexShader(GSHWDrawConfig::VSSelector sel) diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index 41deda69db..a6fe9d90b4 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -138,6 +138,15 @@ public: }; private: + ComPtr m_dxgi_factory; + ComPtr m_swap_chain; + std::vector 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_device_lost = false; + ComPtr m_tfx_root_signature; ComPtr m_utility_root_signature; @@ -161,6 +170,7 @@ private: std::array, 2>, 2> m_date_image_setup_pipelines{}; // [depth][datm] ComPtr m_fxaa_pipeline; ComPtr m_shadeboost_pipeline; + ComPtr m_imgui_pipeline; std::unordered_map> m_tfx_vertex_shaders; std::unordered_map> m_tfx_geometry_shaders; @@ -180,6 +190,10 @@ private: void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const; + bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode); + bool CreateSwapChainRTV(); + void DestroySwapChainRTVs(); + GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override; void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, @@ -215,6 +229,9 @@ private: bool CompilePostProcessingPipelines(); bool CompileCASPipelines(); + bool CompileImGuiPipeline(); + void RenderImGui(); + void DestroyResources(); public: @@ -223,11 +240,29 @@ public: __fi static GSDevice12* GetInstance() { return static_cast(g_gs_device.get()); } - bool Create() override; + RenderAPI GetRenderAPI() const override; + bool HasSurface() const override; + + bool Create(const WindowInfo& wi, VsyncMode vsync) override; void Destroy() override; - void ResetAPIState() override; - void RestoreAPIState() override; + bool ChangeWindow(const WindowInfo& new_wi) override; + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsExclusiveFullscreen() const override; + bool IsExclusiveFullscreen() override; + bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + void DestroySurface() override; + std::string GetDriverInfo() const override; + + bool GetHostRefreshRate(float* refresh_rate) override; + + void SetVSync(VsyncMode mode) override; + + PresentResult BeginPresent(bool frame_skip) override; + void EndPresent() override; + + bool SetGPUTimingEnabled(bool enabled) override; + float GetAndResetAccumulatedGPUTime() override; void PushDebugGroup(const char* fmt, ...) override; void PopDebugGroup() override; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index a50d4318de..94f8e5646b 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -143,6 +143,12 @@ class GSDeviceMTL final : public GSDevice public: using DepthStencilSelector = GSHWDrawConfig::DepthStencilSelector; using SamplerSelector = GSHWDrawConfig::SamplerSelector; + enum class UsePresentDrawable : u8 + { + Never = 0, + Always = 1, + IfVsync = 2, + }; enum class LoadAction { DontCare, @@ -219,7 +225,18 @@ public: MRCOwned> m_draw_sync_fence; MRCOwned m_fn_constants; MRCOwned m_hw_vertex; - std::unique_ptr m_font; + + // Previously in MetalHostDisplay. + MRCOwned m_view; + MRCOwned m_layer; + MRCOwned> m_current_drawable; + MRCOwned m_pass_desc; + u32 m_capture_start_frame; + UsePresentDrawable m_use_present_drawable; + bool m_gpu_timing_enabled = false; + double m_accumulated_gpu_time = 0; + double m_last_gpu_time_end = 0; + std::mutex m_mtx; // Draw IDs are used to make sure we're not clobbering things u64 m_current_draw = 1; @@ -361,15 +378,45 @@ public: MRCOwned> LoadShader(NSString* name); MRCOwned> MakePipeline(MTLRenderPipelineDescriptor* desc, id vertex, id fragment, NSString* name); MRCOwned> MakeComputePipeline(id compute, NSString* name); - bool Create() override; + bool Create(const WindowInfo& wi, VsyncMode vsync) override; + void Destroy() override; + + void AttachSurfaceOnMainThread(); + void DetachSurfaceOnMainThread(); + + RenderAPI GetRenderAPI() const override; + bool HasSurface() const override; + void DestroySurface() override; + bool ChangeWindow(const WindowInfo& wi) override; + bool SupportsExclusiveFullscreen() const override; + bool IsExclusiveFullscreen() override; + bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + std::string GetDriverInfo() const override; + + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + + void UpdateTexture(id texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride); + + PresentResult BeginPresent(bool frame_skip) override; + void EndPresent() override; + void SetVSync(VsyncMode mode) override; + + bool GetHostRefreshRate(float* refresh_rate) override; + + bool SetGPUTimingEnabled(bool enabled) override; + float GetAndResetAccumulatedGPUTime() override; + void AccumulateCommandBufferTime(id buffer); void ClearRenderTarget(GSTexture* t, const GSVector4& c) override; void ClearRenderTarget(GSTexture* t, u32 c) override; void ClearDepth(GSTexture* t) override; void ClearStencil(GSTexture* t, u8 c) override; + void InvalidateRenderTarget(GSTexture* t) override; std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override; + void ClearSamplerCache() override; + void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override; void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, id pipeline, bool linear, LoadAction load_action, const void* frag_uniform, size_t frag_uniform_len); void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds); diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 3898a694b2..586e39ebce 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -14,14 +14,14 @@ */ #include "PrecompiledHeader.h" -#include "GSMetalCPPAccessible.h" -#include "GSDeviceMTL.h" -#include "Frontend/MetalHostDisplay.h" -#include "GSTextureMTL.h" +#include "Host.h" +#include "GS/Renderers/Metal/GSMetalCPPAccessible.h" +#include "GS/Renderers/Metal/GSDeviceMTL.h" +#include "GS/Renderers/Metal/GSTextureMTL.h" #include "GS/GSPerfMon.h" -#include "HostDisplay.h" -#include + +#include "imgui.h" #ifdef __APPLE__ #include "GSMTLSharedHeader.h" @@ -36,6 +36,15 @@ GSDevice* MakeGSDeviceMTL() return new GSDeviceMTL(); } +std::vector GetMetalAdapterList() +{ @autoreleasepool { + std::vector list; + auto devs = MRCTransfer(MTLCopyAllDevices()); + for (id dev in devs.Get()) + list.push_back([[dev name] UTF8String]); + return list; +}} + bool GSDeviceMTL::UsageTracker::PrepareForAllocation(u64 last_draw, size_t amt) { auto removeme = std::find_if(m_usage.begin(), m_usage.end(), [last_draw](UsageEntry usage){ return usage.drawno > last_draw; }); @@ -82,11 +91,8 @@ GSDeviceMTL::GSDeviceMTL() } GSDeviceMTL::~GSDeviceMTL() -{ @autoreleasepool { - FlushEncoders(); - std::lock_guard guard(m_backref->first); - m_backref->second = nullptr; -}} +{ +} GSDeviceMTL::Map GSDeviceMTL::Allocate(UploadBuffer& buffer, size_t amt) { @@ -241,7 +247,7 @@ void GSDeviceMTL::DrawCommandBufferFinished(u64 draw, id buffe // We can do the update non-atomically because we only ever update under the lock u64 newval = std::max(draw, m_last_finished_draw.load(std::memory_order_relaxed)); m_last_finished_draw.store(newval, std::memory_order_release); - static_cast(g_host_display.get())->AccumulateCommandBufferTime(buffer); + AccumulateCommandBufferTime(buffer); } void GSDeviceMTL::FlushEncoders() @@ -706,19 +712,100 @@ static void setFnConstantI(MTLFunctionConstantValues* fc, unsigned int value, GS [fc setConstantValue:&value type:MTLDataTypeUInt atIndex:constant]; } -bool GSDeviceMTL::Create() +template +static void OnMainThread(Fn&& fn) +{ + if ([NSThread isMainThread]) + fn(); + else + dispatch_sync(dispatch_get_main_queue(), fn); +} + +RenderAPI GSDeviceMTL::GetRenderAPI() const +{ + return RenderAPI::Metal; +} + +bool GSDeviceMTL::HasSurface() const { return static_cast(m_layer);} + +void GSDeviceMTL::AttachSurfaceOnMainThread() +{ + ASSERT([NSThread isMainThread]); + m_layer = MRCRetain([CAMetalLayer layer]); + [m_layer setDrawableSize:CGSizeMake(m_window_info.surface_width, m_window_info.surface_height)]; + [m_layer setDevice:m_dev.dev]; + m_view = MRCRetain((__bridge NSView*)m_window_info.window_handle); + [m_view setWantsLayer:YES]; + [m_view setLayer:m_layer]; +} + +void GSDeviceMTL::DetachSurfaceOnMainThread() +{ + ASSERT([NSThread isMainThread]); + [m_view setLayer:nullptr]; + [m_view setWantsLayer:NO]; + m_view = nullptr; + m_layer = nullptr; +} + +bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync) { @autoreleasepool { - if (!GSDevice::Create()) + if (!GSDevice::Create(wi, vsync)) return false; - if (g_host_display->GetRenderAPI() != RenderAPI::Metal) - return false; + NSString* ns_adapter_name = [NSString stringWithUTF8String:EmuConfig.GS.Adapter.c_str()]; + auto devs = MRCTransfer(MTLCopyAllDevices()); + for (id dev in devs.Get()) + { + if ([[dev name] isEqualToString:ns_adapter_name]) + m_dev = GSMTLDevice(MRCRetain(dev)); + } + if (!m_dev.dev) + { + if (!EmuConfig.GS.Adapter.empty()) + Console.Warning("Metal: Couldn't find adapter %s, using default", EmuConfig.GS.Adapter.c_str()); + m_dev = GSMTLDevice(MRCTransfer(MTLCreateSystemDefaultDevice())); + if (!m_dev.dev) + Host::ReportErrorAsync("No Metal Devices Available", "No Metal-supporting GPUs were found. PCSX2 requires a Metal GPU (available on all macs from 2012 onwards)."); + } + m_queue = MRCTransfer([m_dev.dev newCommandQueue]); - if (!g_host_display->HasDevice() || !g_host_display->HasSurface()) + m_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]); + [m_pass_desc colorAttachments][0].loadAction = MTLLoadActionClear; + [m_pass_desc colorAttachments][0].clearColor = MTLClearColorMake(0, 0, 0, 0); + [m_pass_desc colorAttachments][0].storeAction = MTLStoreActionStore; + + if (char* env = getenv("MTL_USE_PRESENT_DRAWABLE")) + m_use_present_drawable = static_cast(atoi(env)); + else if (@available(macOS 13.0, *)) + m_use_present_drawable = UsePresentDrawable::Always; + else // Before Ventura, presentDrawable acts like vsync is on when windowed + m_use_present_drawable = UsePresentDrawable::IfVsync; + + m_capture_start_frame = 0; + if (char* env = getenv("MTL_CAPTURE")) + { + m_capture_start_frame = atoi(env); + } + if (m_capture_start_frame) + { + Console.WriteLn("Metal will capture frame %u", m_capture_start_frame); + } + + if (m_dev.IsOk() && m_queue) + { + OnMainThread([this] + { + AttachSurfaceOnMainThread(); + }); + SetVSync(vsync); + } + else + { return false; - m_dev = *static_cast(g_host_display->GetDevice()); - m_queue = MRCRetain((__bridge id)g_host_display->GetContext()); - MTLPixelFormat layer_px_fmt = [(__bridge CAMetalLayer*)g_host_display->GetSurface() pixelFormat]; + } + + MTLPixelFormat layer_px_fmt = [m_layer pixelFormat]; m_features.broken_point_sampler = [[m_dev.dev name] containsString:@"AMD"]; m_features.geometry_shader = false; @@ -1042,8 +1129,6 @@ bool GSDeviceMTL::Create() pdesc.vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); pdesc.colorAttachments[0].pixelFormat = layer_px_fmt; m_imgui_pipeline = MakePipeline(pdesc, LoadShader(@"vs_imgui"), LoadShader(@"ps_imgui"), @"imgui"); - if (!m_dev.features.texture_swizzle) - m_imgui_pipeline_a8 = MakePipeline(pdesc, LoadShader(@"vs_imgui"), LoadShader(@"ps_imgui_a8"), @"imgui_a8"); [initCommands commit]; } @@ -1054,6 +1139,255 @@ bool GSDeviceMTL::Create() return true; }} +void GSDeviceMTL::Destroy() +{ @autoreleasepool { + FlushEncoders(); + std::lock_guard guard(m_backref->first); + m_backref->second = nullptr; + + GSDevice::Destroy(); + GSDeviceMTL::DestroySurface(); + m_queue = nullptr; + m_dev.Reset(); +}} + +void GSDeviceMTL::DestroySurface() +{ + if (!m_layer) + return; + OnMainThread([this]{ DetachSurfaceOnMainThread(); }); + m_layer = nullptr; +} + +bool GSDeviceMTL::ChangeWindow(const WindowInfo& wi) +{ + OnMainThread([this, &wi] + { + DetachSurfaceOnMainThread(); + m_window_info = wi; + AttachSurfaceOnMainThread(); + }); + return true; +} + +bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; } +bool GSDeviceMTL::IsExclusiveFullscreen() { return false; } +bool GSDeviceMTL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) { return false; } + +std::string GSDeviceMTL::GetDriverInfo() const +{ @autoreleasepool { + std::string desc([[m_dev.dev description] UTF8String]); + desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported"); + desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported"); + desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported"); + desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported"); + desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version)); + desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize); + return desc; +}} + +void GSDeviceMTL::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + m_window_info.surface_scale = new_window_scale; + if (m_window_info.surface_width == static_cast(new_window_width) && m_window_info.surface_height == static_cast(new_window_height)) + return; + m_window_info.surface_width = new_window_width; + m_window_info.surface_height = new_window_height; + @autoreleasepool + { + [m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)]; + } +} + +void GSDeviceMTL::UpdateTexture(id texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) +{ + id cmdbuf = [m_queue commandBuffer]; + id enc = [cmdbuf blitCommandEncoder]; + size_t bytes = data_stride * height; + MRCOwned> buf = MRCTransfer([m_dev.dev newBufferWithLength:bytes options:MTLResourceStorageModeShared | MTLResourceCPUCacheModeWriteCombined]); + memcpy([buf contents], data, bytes); + [enc copyFromBuffer:buf + sourceOffset:0 + sourceBytesPerRow:data_stride + sourceBytesPerImage:bytes + sourceSize:MTLSizeMake(width, height, 1) + toTexture:texture + destinationSlice:0 + destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [enc endEncoding]; + [cmdbuf commit]; +} + +static bool s_capture_next = false; + +GSDevice::PresentResult GSDeviceMTL::BeginPresent(bool frame_skip) +{ @autoreleasepool { + GSDeviceMTL* dev = static_cast(g_gs_device.get()); + if (dev && m_capture_start_frame && dev->FrameNo() == m_capture_start_frame) + s_capture_next = true; + if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless || !g_gs_device) + { + ImGui::EndFrame(); + return PresentResult::FrameSkipped; + } + id buf = dev->GetRenderCmdBuf(); + m_current_drawable = MRCRetain([m_layer nextDrawable]); + dev->EndRenderPass(); + if (!m_current_drawable) + { + [buf pushDebugGroup:@"Present Skipped"]; + [buf popDebugGroup]; + dev->FlushEncoders(); + ImGui::EndFrame(); + return PresentResult::FrameSkipped; + } + [m_pass_desc colorAttachments][0].texture = [m_current_drawable texture]; + id enc = [buf renderCommandEncoderWithDescriptor:m_pass_desc]; + [enc setLabel:@"Present"]; + dev->m_current_render.encoder = MRCRetain(enc); + return PresentResult::OK; +}} + +void GSDeviceMTL::EndPresent() +{ @autoreleasepool { + GSDeviceMTL* dev = static_cast(g_gs_device.get()); + pxAssertDev(dev && dev->m_current_render.encoder && dev->m_current_render_cmdbuf, "BeginPresent cmdbuf was destroyed"); + ImGui::Render(); + dev->RenderImGui(ImGui::GetDrawData()); + dev->EndRenderPass(); + if (m_current_drawable) + { + const bool use_present_drawable = m_use_present_drawable == UsePresentDrawable::Always || + (m_use_present_drawable == UsePresentDrawable::IfVsync && m_vsync_mode != VsyncMode::Off); + + if (use_present_drawable) + [dev->m_current_render_cmdbuf presentDrawable:m_current_drawable]; + else + [dev->m_current_render_cmdbuf addScheduledHandler:[drawable = std::move(m_current_drawable)](id){ + [drawable present]; + }]; + } + dev->FlushEncoders(); + dev->FrameCompleted(); + m_current_drawable = nullptr; + if (m_capture_start_frame) + { + if (@available(macOS 10.15, iOS 13, *)) + { + static NSString* const path = @"/tmp/PCSX2MTLCapture.gputrace"; + static u32 frames; + if (frames) + { + --frames; + if (!frames) + { + [[MTLCaptureManager sharedCaptureManager] stopCapture]; + Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace finished"); + [[NSWorkspace sharedWorkspace] selectFile:path + inFileViewerRootedAtPath:@"/tmp/"]; + } + } + else if (s_capture_next) + { + s_capture_next = false; + MTLCaptureManager* mgr = [MTLCaptureManager sharedCaptureManager]; + if ([mgr supportsDestination:MTLCaptureDestinationGPUTraceDocument]) + { + MTLCaptureDescriptor* desc = [[MTLCaptureDescriptor new] autorelease]; + [desc setCaptureObject:m_dev.dev]; + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) + [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; + [desc setOutputURL:[NSURL fileURLWithPath:path]]; + [desc setDestination:MTLCaptureDestinationGPUTraceDocument]; + NSError* err = nullptr; + [mgr startCaptureWithDescriptor:desc error:&err]; + if (err) + { + Console.Error("Metal Trace Capture failed: %s", [[err localizedDescription] UTF8String]); + } + else + { + Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace started"); + frames = 2; + } + } + else + { + Console.Error("Metal Trace Capture Failed: MTLCaptureManager doesn't support GPU trace documents! (Did you forget to run with METAL_CAPTURE_ENABLED=1?)"); + } + } + } + } +}} + +void GSDeviceMTL::SetVSync(VsyncMode mode) +{ + [m_layer setDisplaySyncEnabled:mode != VsyncMode::Off]; + m_vsync_mode = mode; +} + +bool GSDeviceMTL::GetHostRefreshRate(float* refresh_rate) +{ + OnMainThread([this, refresh_rate] + { + u32 did = [[[[[m_view window] screen] deviceDescription] valueForKey:@"NSScreenNumber"] unsignedIntValue]; + if (CGDisplayModeRef mode = CGDisplayCopyDisplayMode(did)) + { + *refresh_rate = CGDisplayModeGetRefreshRate(mode); + CGDisplayModeRelease(mode); + } + else + { + *refresh_rate = 0; + } + }); + return *refresh_rate != 0; +} + +bool GSDeviceMTL::SetGPUTimingEnabled(bool enabled) +{ + if (enabled == m_gpu_timing_enabled) + return true; + if (@available(macOS 10.15, iOS 10.3, *)) + { + std::lock_guard l(m_mtx); + m_gpu_timing_enabled = enabled; + m_accumulated_gpu_time = 0; + m_last_gpu_time_end = 0; + return true; + } + return false; +} + +float GSDeviceMTL::GetAndResetAccumulatedGPUTime() +{ + std::lock_guard l(m_mtx); + float time = m_accumulated_gpu_time * 1000; + m_accumulated_gpu_time = 0; + return time; +} + +void GSDeviceMTL::AccumulateCommandBufferTime(id buffer) +{ + std::lock_guard l(m_mtx); + if (!m_gpu_timing_enabled) + return; + // We do the check before enabling m_gpu_timing_enabled +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + // It's unlikely, but command buffers can overlap or run out of order + // This doesn't handle every case (fully out of order), but it should at least handle overlapping + double begin = std::max(m_last_gpu_time_end, [buffer GPUStartTime]); + double end = [buffer GPUEndTime]; + if (end > begin) + { + m_accumulated_gpu_time += end - begin; + m_last_gpu_time_end = end; + } +#pragma clang diagnostic pop +} + void GSDeviceMTL::ClearRenderTarget(GSTexture* t, const GSVector4& c) { if (!t) return; @@ -1078,11 +1412,21 @@ void GSDeviceMTL::ClearStencil(GSTexture* t, uint8 c) static_cast(t)->RequestStencilClear(c); } +void GSDeviceMTL::InvalidateRenderTarget(GSTexture* t) +{ + // TODO: Implement me +} + std::unique_ptr GSDeviceMTL::CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) { return GSDownloadTextureMTL::Create(this, width, height, format); } +void GSDeviceMTL::ClearSamplerCache() +{ + // TODO: Implement me +} + void GSDeviceMTL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) { @autoreleasepool { g_perfmon.Put(GSPerfMon::TextureCopies, 1); @@ -1231,7 +1575,7 @@ static_assert(offsetof(DisplayConstantBuffer, TimeAndPad.x) == offsetof(G void GSDeviceMTL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) { @autoreleasepool { - GSVector2i ds = dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()); + GSVector2i ds = dTex ? dTex->GetSize() : GetWindowSize(); DisplayConstantBuffer cb; cb.SetSource(sRect, sTex->GetSize()); cb.SetTarget(dRect, ds); @@ -1965,12 +2309,11 @@ void GSDeviceMTL::RenderImGui(ImDrawData* data) [enc setVertexBuffer:map.gpu_buffer offset:map.gpu_offset atIndex:GSMTLBufferIndexVertices]; [enc setVertexBytes:&transform length:sizeof(transform) atIndex:GSMTLBufferIndexUniforms]; - simd::uint4 last_scissor = simd::make_uint4(0, 0, g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()); + simd::uint4 last_scissor = simd::make_uint4(0, 0, GetWindowWidth(), GetWindowHeight()); simd::float2 fb_size = simd_float(last_scissor.zw); simd::float2 clip_off = ToSimd(data->DisplayPos); // (0,0) unless using multi-viewports simd::float2 clip_scale = ToSimd(data->FramebufferScale); // (1,1) unless using retina display which are often (2,2) ImTextureID last_tex = nullptr; - bool last_tex_a8 = false; for (int i = 0; i < data->CmdListsCount; i++) { @@ -2003,15 +2346,6 @@ void GSDeviceMTL::RenderImGui(ImDrawData* data) { last_tex = tex; [enc setFragmentTexture:(__bridge id)tex atIndex:0]; - if (!m_dev.features.texture_swizzle) - { - bool a8 = [(__bridge id)tex pixelFormat] == MTLPixelFormatA8Unorm; - if (last_tex_a8 != a8) - { - [enc setRenderPipelineState:a8 ? m_imgui_pipeline_a8 : m_imgui_pipeline]; - last_tex_a8 = a8; - } - } } [enc setVertexBufferOffset:map.gpu_offset + vtx_off + cmd.VtxOffset * sizeof(ImDrawVert) atIndex:0]; diff --git a/pcsx2/GS/Renderers/Metal/GSMetalCPPAccessible.h b/pcsx2/GS/Renderers/Metal/GSMetalCPPAccessible.h index cd4fae8120..efa9f6de68 100644 --- a/pcsx2/GS/Renderers/Metal/GSMetalCPPAccessible.h +++ b/pcsx2/GS/Renderers/Metal/GSMetalCPPAccessible.h @@ -14,15 +14,16 @@ */ #pragma once + +#include +#include + // Header with all metal stuff available for use with C++ (rather than Objective-C++) #ifdef __APPLE__ -#include "HostDisplay.h" - class GSDevice; GSDevice* MakeGSDeviceMTL(); -HostDisplay* MakeMetalHostDisplay(); -HostDisplay::AdapterAndModeList GetMetalAdapterAndModeList(); +std::vector GetMetalAdapterList(); #endif diff --git a/pcsx2/GS/Renderers/Metal/convert.metal b/pcsx2/GS/Renderers/Metal/convert.metal index 67290f3c43..b16449d513 100644 --- a/pcsx2/GS/Renderers/Metal/convert.metal +++ b/pcsx2/GS/Renderers/Metal/convert.metal @@ -362,12 +362,6 @@ fragment half4 ps_imgui(ImGuiShaderData data [[stage_in]], texture2d textu return data.c * texture.sample(s, data.t); } -fragment half4 ps_imgui_a8(ImGuiShaderData data [[stage_in]], texture2d texture [[texture(GSMTLTextureIndexNonHW)]]) -{ - constexpr sampler s(coord::normalized, filter::linear, address::clamp_to_edge); - return data.c * half4(1, 1, 1, texture.sample(s, data.t).a); -} - fragment float4 ps_shadeboost(float4 p [[position]], DirectReadTextureIn tex, constant float3& cb [[buffer(GSMTLBufferIndexUniforms)]]) { const float brt = cb.x; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index d4764e95bd..1fa8087555 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -14,16 +14,20 @@ */ #include "PrecompiledHeader.h" -#include "common/StringUtil.h" + #include "GS/GSState.h" -#include "GSDeviceOGL.h" -#include "GLState.h" #include "GS/GSGL.h" #include "GS/GSUtil.h" +#include "GS/Renderers/OpenGL/GSDeviceOGL.h" +#include "GS/Renderers/OpenGL/GLState.h" #include "Host.h" -#include "HostDisplay.h" #include "ShaderCacheVersion.h" + +#include "common/StringUtil.h" + +#include "imgui.h" #include "IconsFontAwesome5.h" + #include #include #include @@ -45,35 +49,7 @@ GSDeviceOGL::GSDeviceOGL() = default; GSDeviceOGL::~GSDeviceOGL() { - s_texture_upload_buffer.reset(); - if (m_vertex_array_object) - glDeleteVertexArrays(1, &m_vertex_array_object); - m_vertex_stream_buffer.reset(); - m_index_stream_buffer.reset(); - - // Clean m_convert - delete m_convert.dss; - delete m_convert.dss_write; - - // Clean m_date - delete m_date.dss; - - // Clean various opengl allocation - glDeleteFramebuffers(1, &m_fbo); - glDeleteFramebuffers(1, &m_fbo_read); - glDeleteFramebuffers(1, &m_fbo_write); - - // Delete HW FX - m_vertex_uniform_stream_buffer.reset(); - m_fragment_uniform_stream_buffer.reset(); - glDeleteSamplers(1, &m_palette_ss); - - m_programs.clear(); - - glDeleteSamplers(std::size(m_ps_ss), m_ps_ss); - - for (GSDepthStencilOGL* ds : m_om_dss) - delete ds; + pxAssert(!m_gl_context); } GSTexture* GSDeviceOGL::CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) @@ -82,21 +58,60 @@ GSTexture* GSDeviceOGL::CreateSurface(GSTexture::Type type, int width, int heigh return new GSTextureOGL(type, width, height, levels, format); } -bool GSDeviceOGL::Create() +RenderAPI GSDeviceOGL::GetRenderAPI() const { - if (!GSDevice::Create()) + return m_gl_context->IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL; +} + +bool GSDeviceOGL::HasSurface() const +{ + return m_window_info.type != WindowInfo::Type::Surfaceless; +} + +void GSDeviceOGL::SetVSync(VsyncMode mode) +{ + if (m_vsync_mode == mode || m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless) + return; + + // Window framebuffer has to be bound to call SetSwapInterval. + GLint current_fbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) + m_gl_context->SetSwapInterval(static_cast(mode != VsyncMode::Off)); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + m_vsync_mode = mode; +} + +bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync) +{ + if (!GSDevice::Create(wi, vsync)) return false; - const RenderAPI render_api = g_host_display->GetRenderAPI(); - if (render_api != RenderAPI::OpenGL && render_api != RenderAPI::OpenGLES) + m_gl_context = GL::Context::Create(wi); + if (!m_gl_context) + { + Console.Error("Failed to create any GL context"); + m_gl_context.reset(); return false; + } + + if (!m_gl_context->MakeCurrent()) + { + Console.Error("Failed to make GL context current"); + return false; + } // Check openGL requirement as soon as possible so we can switch to another // renderer/device - GLLoader::is_gles = (render_api == RenderAPI::OpenGLES); + GLLoader::is_gles = m_gl_context->IsGLES(); if (!GLLoader::check_gl_requirements()) return false; + SetSwapInterval(); + if (!GSConfig.DisableShaderCache) { if (!m_shader_cache.Open(false, EmuFolders::Cache, SHADER_CACHE_VERSION)) @@ -155,7 +170,7 @@ bool GSDeviceOGL::Create() { if (!GLLoader::is_gles) { - glDebugMessageCallback((GLDEBUGPROC)DebugOutputToFile, NULL); + glDebugMessageCallback(DebugMessageCallback, NULL); glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, true); // Useless info message on Nvidia driver @@ -164,7 +179,7 @@ bool GSDeviceOGL::Create() } else if (GLAD_GL_KHR_debug) { - glDebugMessageCallbackKHR((GLDEBUGPROC)DebugOutputToFile, NULL); + glDebugMessageCallbackKHR(DebugMessageCallback, NULL); glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, true); } @@ -183,14 +198,15 @@ bool GSDeviceOGL::Create() GL_PUSH("GSDeviceOGL::Various"); glGenFramebuffers(1, &m_fbo); - // Always write to the first buffer - OMSetFBO(m_fbo); - const GLenum target[1] = {GL_COLOR_ATTACHMENT0}; - glDrawBuffers(1, target); - OMSetFBO(0); - glGenFramebuffers(1, &m_fbo_read); glGenFramebuffers(1, &m_fbo_write); + + OMSetFBO(m_fbo); + + // Always write to the first buffer + static constexpr GLenum target[1] = {GL_COLOR_ATTACHMENT0}; + glDrawBuffers(1, target); + // Always read from the first buffer glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_read); glReadBuffer(GL_COLOR_ATTACHMENT0); @@ -239,10 +255,6 @@ bool GSDeviceOGL::Create() glVertexAttribPointer(7, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(GSVertex), (const GLvoid*)(28)); } - // must be done after va is created - GLState::Clear(); - RestoreAPIState(); - // **************************************************************** // Pre Generate the different sampler object // **************************************************************** @@ -395,24 +407,10 @@ bool GSDeviceOGL::Create() } // **************************************************************** - // Shade boost + // Post processing // **************************************************************** - { - GL_PUSH("GSDeviceOGL::Shadeboost"); - - const auto shader = Host::ReadResourceFileToString("shaders/opengl/shadeboost.glsl"); - if (!shader.has_value()) - { - Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/shadeboost.glsl."); - return false; - } - - const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, *shader)); - if (!m_shader_cache.GetProgram(&m_shadeboost.ps, m_convert.vs, {}, ps)) - return false; - m_shadeboost.ps.RegisterUniform("params"); - m_shadeboost.ps.SetName("Shadeboost pipe"); - } + if (!CompileShadeBoostProgram() || !CompileFXAAProgram()) + return false; // Image load store and GLSL 420pack is core in GL4.2, no need to check. m_features.cas_sharpening = ((GLAD_GL_VERSION_4_2 && GLAD_GL_ARB_compute_shader) || GLAD_GL_ES_VERSION_3_2) && CreateCASPrograms(); @@ -491,6 +489,9 @@ bool GSDeviceOGL::Create() } } + if (!CreateImGuiProgram()) + return false; + // Basic to ensure structures are correctly packed static_assert(sizeof(VSSelector) == 1, "Wrong VSSelector size"); static_assert(sizeof(PSSelector) == 12, "Wrong PSSelector size"); @@ -501,6 +502,20 @@ bool GSDeviceOGL::Create() return true; } +void GSDeviceOGL::Destroy() +{ + GSDevice::Destroy(); + + if (m_gl_context) + { + DestroyTimestampQueries(); + DestroyResources(); + + m_gl_context->DoneCurrent(); + m_gl_context.reset(); + } +} + bool GSDeviceOGL::CreateTextureFX() { GL_PUSH("GSDeviceOGL::CreateTextureFX"); @@ -529,78 +544,293 @@ bool GSDeviceOGL::CreateTextureFX() m_om_dss[key] = CreateDepthStencil(OMDepthStencilSelector(key)); } + GL::Program::ResetLastProgram(); return true; } -void GSDeviceOGL::ResetAPIState() +void GSDeviceOGL::SetSwapInterval() { - if (GLState::point_size) - glDisable(GL_PROGRAM_POINT_SIZE); - if (GLState::line_width != 1.0f) - glLineWidth(1.0f); - - // clear out DSB - glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO); - glDisable(GL_BLEND); - glActiveTexture(GL_TEXTURE0); + const int interval = ((m_vsync_mode == VsyncMode::Adaptive) ? -1 : ((m_vsync_mode == VsyncMode::On) ? 1 : 0)); + m_gl_context->SetSwapInterval(interval); } -void GSDeviceOGL::RestoreAPIState() +void GSDeviceOGL::DestroyResources() { - glBindVertexArray(m_vertex_array_object); + m_shader_cache.Close(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo); + if (m_palette_ss != 0) + glDeleteSamplers(1, &m_palette_ss); - glViewportIndexedf(0, 0, 0, static_cast(GLState::viewport.x), static_cast(GLState::viewport.y)); - glScissorIndexed(0, GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height()); + m_programs.clear(); - glBlendEquationSeparate(GLState::eq_RGB, GL_FUNC_ADD); - glBlendFuncSeparate(GLState::f_sRGB, GLState::f_dRGB, GL_ONE, GL_ZERO); + for (GSDepthStencilOGL* ds : m_om_dss) + delete ds; - const float bf = static_cast(GLState::bf) / 128.0f; - glBlendColor(bf, bf, bf, bf); + if (m_ps_ss[0] != 0) + glDeleteSamplers(std::size(m_ps_ss), m_ps_ss); - if (GLState::blend) + m_imgui.ps.Destroy(); + if (m_imgui.vao != 0) + glDeleteVertexArrays(1, &m_imgui.vao); + + m_cas.upscale_ps.Destroy(); + m_cas.sharpen_ps.Destroy(); + + m_shadeboost.ps.Destroy(); + + for (GL::Program& prog : m_date.primid_ps) + prog.Destroy(); + delete m_date.dss; + + m_fxaa.ps.Destroy(); + + for (GL::Program& prog : m_present) + prog.Destroy(); + + for (GL::Program& prog : m_convert.ps) + prog.Destroy(); + delete m_convert.dss; + delete m_convert.dss_write; + + for (GL::Program& prog : m_interlace.ps) + prog.Destroy(); + + for (GL::Program& prog : m_merge_obj.ps) + prog.Destroy(); + + m_fragment_uniform_stream_buffer.reset(); + m_vertex_uniform_stream_buffer.reset(); + + glBindVertexArray(0); + if (m_vertex_array_object != 0) + glDeleteVertexArrays(1, &m_vertex_array_object); + + m_index_stream_buffer.reset(); + m_vertex_stream_buffer.reset(); + s_texture_upload_buffer.reset(); + + if (m_fbo != 0) + glDeleteFramebuffers(1, &m_fbo); + if (m_fbo_read != 0) + glDeleteFramebuffers(1, &m_fbo_read); + if (m_fbo_write != 0) + glDeleteFramebuffers(1, &m_fbo_write); +} + +bool GSDeviceOGL::ChangeWindow(const WindowInfo& new_wi) +{ + pxAssert(m_gl_context); + + if (!m_gl_context->ChangeSurface(new_wi)) { - glEnable(GL_BLEND); + Console.Error("Failed to change surface"); + return false; } + + m_window_info = m_gl_context->GetWindowInfo(); + + if (new_wi.type != WindowInfo::Type::Surfaceless) + { + // reset vsync rate, since it (usually) gets lost + if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1)) + m_gl_context->SetSwapInterval(static_cast(m_vsync_mode != VsyncMode::Off)); + } + + return true; +} + +void GSDeviceOGL::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + m_window_info.surface_scale = new_window_scale; + if (m_window_info.surface_width == static_cast(new_window_width) && + m_window_info.surface_height == static_cast(new_window_height)) + { + return; + } + + m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); + m_window_info = m_gl_context->GetWindowInfo(); +} + +bool GSDeviceOGL::SupportsExclusiveFullscreen() const +{ + return false; +} + +bool GSDeviceOGL::IsExclusiveFullscreen() +{ + return false; +} + +bool GSDeviceOGL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + +void GSDeviceOGL::DestroySurface() +{ + m_window_info = {}; + if (!m_gl_context->ChangeSurface(m_window_info)) + Console.Error("Failed to switch to surfaceless"); +} + +std::string GSDeviceOGL::GetDriverInfo() const +{ + const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); + const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); + const char* gl_version = reinterpret_cast(glGetString(GL_VERSION)); + return StringUtil::StdStringFromFormat( + "%s Context:\n%s\n%s %s", m_gl_context->IsGLES() ? "OpenGL ES" : "OpenGL", gl_version, gl_vendor, gl_renderer); +} + +GSDevice::PresentResult GSDeviceOGL::BeginPresent(bool frame_skip) +{ + if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless) + return PresentResult::FrameSkipped; + + OMSetFBO(0); + OMSetColorMaskState(); + + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + + const GSVector2i size = GetWindowSize(); + SetViewport(size); + SetScissor(GSVector4i::loadh(size)); + + return PresentResult::OK; +} + +void GSDeviceOGL::EndPresent() +{ + RenderImGui(); + + if (m_gpu_timing_enabled) + PopTimestampQuery(); + + m_gl_context->SwapBuffers(); + + if (m_gpu_timing_enabled) + KickTimestampQuery(); +} + +void GSDeviceOGL::CreateTimestampQueries() +{ + const bool gles = m_gl_context->IsGLES(); + const auto GenQueries = gles ? glGenQueriesEXT : glGenQueries; + + GenQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); + KickTimestampQuery(); +} + +void GSDeviceOGL::DestroyTimestampQueries() +{ + if (m_timestamp_queries[0] == 0) + return; + + const bool gles = m_gl_context->IsGLES(); + const auto DeleteQueries = gles ? glDeleteQueriesEXT : glDeleteQueries; + + if (m_timestamp_query_started) + { + const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; + EndQuery(GL_TIME_ELAPSED); + } + + DeleteQueries(static_cast(m_timestamp_queries.size()), m_timestamp_queries.data()); + m_timestamp_queries.fill(0); + m_read_timestamp_query = 0; + m_write_timestamp_query = 0; + m_waiting_timestamp_queries = 0; + m_timestamp_query_started = false; +} + +void GSDeviceOGL::PopTimestampQuery() +{ + const bool gles = m_gl_context->IsGLES(); + + if (gles) + { + GLint disjoint = 0; + glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint); + if (disjoint) + { + DevCon.WriteLn("GPU timing disjoint, resetting."); + if (m_timestamp_query_started) + glEndQueryEXT(GL_TIME_ELAPSED); + + m_read_timestamp_query = 0; + m_write_timestamp_query = 0; + m_waiting_timestamp_queries = 0; + m_timestamp_query_started = false; + } + } + + while (m_waiting_timestamp_queries > 0) + { + const auto GetQueryObjectiv = gles ? glGetQueryObjectivEXT : glGetQueryObjectiv; + const auto GetQueryObjectui64v = gles ? glGetQueryObjectui64vEXT : glGetQueryObjectui64v; + + GLint available = 0; + GetQueryObjectiv(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT_AVAILABLE, &available); + + if (!available) + break; + + u64 result = 0; + GetQueryObjectui64v(m_timestamp_queries[m_read_timestamp_query], GL_QUERY_RESULT, &result); + m_accumulated_gpu_time += static_cast(static_cast(result) / 1000000.0); + m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; + m_waiting_timestamp_queries--; + } + + if (m_timestamp_query_started) + { + const auto EndQuery = gles ? glEndQueryEXT : glEndQuery; + EndQuery(GL_TIME_ELAPSED); + + m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES; + m_timestamp_query_started = false; + m_waiting_timestamp_queries++; + } +} + +void GSDeviceOGL::KickTimestampQuery() +{ + if (m_timestamp_query_started || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES) + return; + + const bool gles = m_gl_context->IsGLES(); + const auto BeginQuery = gles ? glBeginQueryEXT : glBeginQuery; + + BeginQuery(GL_TIME_ELAPSED, m_timestamp_queries[m_write_timestamp_query]); + m_timestamp_query_started = true; +} + +bool GSDeviceOGL::SetGPUTimingEnabled(bool enabled) +{ + if (m_gpu_timing_enabled == enabled) + return true; + + if (enabled && m_gl_context->IsGLES() && !GLAD_GL_EXT_disjoint_timer_query) + return false; + + m_gpu_timing_enabled = enabled; + if (m_gpu_timing_enabled) + CreateTimestampQueries(); else - { - glDisable(GL_BLEND); - } + DestroyTimestampQueries(); - const OMColorMaskSelector msel{ GLState::wrgba }; - glColorMask(msel.wr, msel.wg, msel.wb, msel.wa); + return true; +} - GLState::depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST); - glDepthFunc(GLState::depth_func); - glDepthMask(GLState::depth_mask); - - if (GLState::stencil) - { - glEnable(GL_STENCIL_TEST); - } - else - { - glDisable(GL_STENCIL_TEST); - } - - glStencilFunc(GLState::stencil_func, 1, 1); - glStencilOp(GL_KEEP, GL_KEEP, GLState::stencil_pass); - - glBindSampler(0, GLState::ps_ss); - - for (GLuint i = 0; i < sizeof(GLState::tex_unit) / sizeof(GLState::tex_unit[0]); i++) - glBindTextureUnit(i, GLState::tex_unit[i]); - - if (GLState::point_size) - glEnable(GL_PROGRAM_POINT_SIZE); - if (GLState::line_width != 1.0f) - glLineWidth(GLState::line_width); - - // Force UBOs to be reuploaded, we don't know what else was bound there. - std::memset(&m_vs_cb_cache, 0xFF, sizeof(m_vs_cb_cache)); - std::memset(&m_ps_cb_cache, 0xFF, sizeof(m_ps_cb_cache)); +float GSDeviceOGL::GetAndResetAccumulatedGPUTime() +{ + const float value = m_accumulated_gpu_time; + m_accumulated_gpu_time = 0.0f; + return value; } void GSDeviceOGL::DrawPrimitive() @@ -898,8 +1128,6 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view& entry, GLenum typ // Need GL version 420 header += "#extension GL_ARB_shading_language_420pack: require\n"; - // Need GL version 410 - header += "#extension GL_ARB_separate_shader_objects: require\n"; if (m_features.framebuffer_fetch && GLAD_GL_EXT_shader_framebuffer_fetch) header += "#extension GL_EXT_shader_framebuffer_fetch : require\n"; @@ -1176,7 +1404,7 @@ void GSDeviceOGL::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture { ASSERT(sTex); - const GSVector2i ds(dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight())); + const GSVector2i ds(dTex ? dTex->GetSize() : GSVector2i(GetWindowWidth(), GetWindowHeight())); DisplayConstantBuffer cb; cb.SetSource(sRect, sTex->GetSize()); cb.SetTarget(dRect, ds); @@ -1441,24 +1669,39 @@ void GSDeviceOGL::DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture StretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast(shader)], linear); } +bool GSDeviceOGL::CompileFXAAProgram() +{ + // Needs ARB_gpu_shader5 for gather. + if (!GLLoader::is_gles && !GLLoader::found_GL_ARB_gpu_shader5) + { + Console.Warning("FXAA is not supported with the current GPU"); + return true; + } + + const std::string_view fxaa_macro = "#define FXAA_GLSL_130 1\n"; + std::optional shader = Host::ReadResourceFileToString("shaders/common/fxaa.fx"); + if (!shader.has_value()) + { + Console.Error("Failed to read fxaa.fs"); + return false; + } + + const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, shader->c_str(), fxaa_macro)); + std::optional prog = m_shader_cache.GetProgram(m_convert.vs, {}, ps); + if (!prog.has_value()) + { + Console.Error("Failed to compile FXAA fragment shader"); + return false; + } + + m_fxaa.ps = std::move(prog.value()); + return true; +} + void GSDeviceOGL::DoFXAA(GSTexture* sTex, GSTexture* dTex) { - // Lazy compile if (!m_fxaa.ps.IsValid()) - { - // Needs ARB_gpu_shader5 for gather. - if (!GLLoader::is_gles && !GLLoader::found_GL_ARB_gpu_shader5) - return; - - const std::string_view fxaa_macro = "#define FXAA_GLSL_130 1\n"; - std::optional shader = Host::ReadResourceFileToString("shaders/common/fxaa.fx"); - if (!shader.has_value()) - return; - - const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, shader->c_str(), fxaa_macro)); - if (!m_fxaa.ps.Compile(m_convert.vs, {}, ps) || !m_fxaa.ps.Link()) - return; - } + return; GL_PUSH("DoFxaa"); @@ -1472,6 +1715,23 @@ void GSDeviceOGL::DoFXAA(GSTexture* sTex, GSTexture* dTex) StretchRect(sTex, sRect, dTex, dRect, m_fxaa.ps, true); } +bool GSDeviceOGL::CompileShadeBoostProgram() +{ + const auto shader = Host::ReadResourceFileToString("shaders/opengl/shadeboost.glsl"); + if (!shader.has_value()) + { + Host::ReportErrorAsync("GS", "Failed to read shaders/opengl/shadeboost.glsl."); + return false; + } + + const std::string ps(GetShaderSource("ps_main", GL_FRAGMENT_SHADER, *shader)); + if (!m_shader_cache.GetProgram(&m_shadeboost.ps, m_convert.vs, {}, ps)) + return false; + m_shadeboost.ps.RegisterUniform("params"); + m_shadeboost.ps.SetName("Shadeboost pipe"); + return true; +} + void GSDeviceOGL::DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) { GL_PUSH("DoShadeBoost"); @@ -1651,6 +1911,140 @@ bool GSDeviceOGL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, con return true; } +bool GSDeviceOGL::CreateImGuiProgram() +{ + std::optional glsl = Host::ReadResourceFileToString("shaders/opengl/imgui.glsl"); + if (!glsl.has_value()) + { + Console.Error("Failed to read imgui.glsl"); + return false; + } + + std::optional prog = m_shader_cache.GetProgram( + GetShaderSource("vs_main", GL_VERTEX_SHADER, glsl.value()), {}, + GetShaderSource("ps_main", GL_FRAGMENT_SHADER, glsl.value())); + if (!prog.has_value()) + { + Console.Error("Failed to compile imgui shaders"); + return false; + } + + prog->SetName("ImGui Render"); + prog->RegisterUniform("ProjMtx"); + m_imgui.ps = std::move(prog.value()); + + // Need a different VAO because the layout doesn't match GS + glGenVertexArrays(1, &m_imgui.vao); + glBindVertexArray(m_imgui.vao); + m_vertex_stream_buffer->Bind(); + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + + glBindVertexArray(m_vertex_array_object); + return true; +} + +void GSDeviceOGL::RenderImGui() +{ + ImGui::Render(); + const ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount == 0) + return; + + constexpr float L = 0.0f; + const float R = static_cast(m_window_info.surface_width); + constexpr float T = 0.0f; + const float B = static_cast(m_window_info.surface_height); + + // clang-format off + 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 }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, + }; + // clang-format on + + m_imgui.ps.Bind(); + m_imgui.ps.UniformMatrix4fv(0, &ortho_projection[0][0]); + glBindVertexArray(m_imgui.vao); + OMSetBlendState(true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_FUNC_ADD); + OMSetDepthStencilState(m_convert.dss); + PSSetSamplerState(m_convert.ln); + + // Need to flip the scissor due to lower-left on the window framebuffer + GSVector4i last_scissor = GSVector4i::xffffffff(); + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // Different vertex format. + u32 vertex_start; + { + const u32 size = static_cast(cmd_list->VtxBuffer.Size) * sizeof(ImDrawVert); + auto res = m_vertex_stream_buffer->Map(sizeof(ImDrawVert), size); + std::memcpy(res.pointer, cmd_list->VtxBuffer.Data, size); + vertex_start = res.index_aligned; + m_vertex_stream_buffer->Unmap(size); + } + + // Bit awkward, because this is using 16-bit indices, not 32-bit. + u32 index_start; + { + static_assert(sizeof(ImDrawIdx) == sizeof(u16)); + + const u32 size = static_cast(cmd_list->IdxBuffer.Size) * sizeof(ImDrawIdx); + auto res = m_index_stream_buffer->Map(sizeof(u16), size); + index_start = res.index_aligned; + std::memcpy(res.pointer, cmd_list->IdxBuffer.Data, size); + m_index_stream_buffer->Unmap(size); + m_index_stream_buffer->Bind(); + } + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + pxAssert(!pcmd->UserCallback); + + const GSVector4 clip = GSVector4::load(&pcmd->ClipRect); + if ((clip.zwzw() <= clip.xyxy()).mask() != 0) + continue; + + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + const GSVector4i iclip = GSVector4i(clip); + if (!last_scissor.eq(iclip)) + { + glScissor(iclip.x, m_window_info.surface_height - iclip.w, iclip.width(), iclip.height()); + last_scissor = iclip; + } + + // Since we don't have the GSTexture... + const GLuint texture_id = static_cast(reinterpret_cast(pcmd->GetTexID())); + if (GLState::tex_unit[0] != texture_id) + { + GLState::tex_unit[0] = texture_id; + glBindTextureUnit(0, texture_id); + } + + glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, GL_UNSIGNED_SHORT, + (void*)(intptr_t)((pcmd->IdxOffset + index_start) * sizeof(ImDrawIdx)), pcmd->VtxOffset + vertex_start); + } + + g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size); + } + + glBindVertexArray(m_vertex_array_object); + glScissor(GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height()); +} + void GSDeviceOGL::OMAttachRt(GSTextureOGL* rt) { if (rt) @@ -1780,21 +2174,29 @@ void GSDeviceOGL::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVecto else OMAttachDs(); - const GSVector2i size = rt ? rt->GetSize() : ds ? ds->GetSize() : GLState::viewport; - if (GLState::viewport != size) + if (rt || ds) { - GLState::viewport = size; - // FIXME ViewportIndexedf or ViewportIndexedfv (GL4.1) - glViewportIndexedf(0, 0, 0, GLfloat(size.x), GLfloat(size.y)); + const GSVector2i size = rt ? rt->GetSize() : ds->GetSize(); + SetViewport(size); + SetScissor(scissor ? *scissor : GSVector4i::loadh(size)); } +} - const GSVector4i r = scissor ? *scissor : GSVector4i(size).zwxy(); - - if (!GLState::scissor.eq(r)) +void GSDeviceOGL::SetViewport(const GSVector2i& viewport) +{ + if (GLState::viewport != viewport) { - GLState::scissor = r; - // FIXME ScissorIndexedv (GL4.1) - glScissorIndexed(0, r.x, r.y, r.width(), r.height()); + GLState::viewport = viewport; + glViewport(0, 0, viewport.x, viewport.y); + } +} + +void GSDeviceOGL::SetScissor(const GSVector4i& scissor) +{ + if (!GLState::scissor.eq(scissor)) + { + GLState::scissor = scissor; + glScissor(scissor.x, scissor.y, scissor.width(), scissor.height()); } } @@ -2174,7 +2576,7 @@ void GSDeviceOGL::SendHWDraw(const GSHWDrawConfig& config, bool needs_barrier) } // Note: used as a callback of DebugMessageCallback. Don't change the signature -void GSDeviceOGL::DebugOutputToFile(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam) +void GSDeviceOGL::DebugMessageCallback(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam) { std::string message(gl_message, gl_length >= 0 ? gl_length : strlen(gl_message)); std::string type, severity, source; @@ -2230,7 +2632,7 @@ GL::StreamBuffer* GSDeviceOGL::GetTextureUploadBuffer() void GSDeviceOGL::PushDebugGroup(const char* fmt, ...) { #ifdef ENABLE_OGL_DEBUG - if (!glPushDebugGroup) + if (!glPushDebugGroup || !GSConfig.UseDebugDevice) return; std::va_list ap; @@ -2245,7 +2647,7 @@ void GSDeviceOGL::PushDebugGroup(const char* fmt, ...) void GSDeviceOGL::PopDebugGroup() { #ifdef ENABLE_OGL_DEBUG - if (!glPopDebugGroup) + if (!glPopDebugGroup || !GSConfig.UseDebugDevice) return; glPopDebugGroup(); @@ -2255,7 +2657,7 @@ void GSDeviceOGL::PopDebugGroup() void GSDeviceOGL::InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) { #ifdef ENABLE_OGL_DEBUG - if (!glDebugMessageInsert) + if (!glDebugMessageInsert || !GSConfig.UseDebugDevice) return; GLenum type, id, severity; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h index 7c49bd9f3f..54b054a07f 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h @@ -202,8 +202,9 @@ public: }; private: - std::string m_shader_tfx_vgs; - std::string m_shader_tfx_fs; + static constexpr u8 NUM_TIMESTAMP_QUERIES = 5; + + std::unique_ptr m_gl_context; GLuint m_fbo = 0; // frame buffer container GLuint m_fbo_read = 0; // frame buffer container only for reading @@ -262,6 +263,12 @@ private: GL::Program sharpen_ps; } m_cas; + struct + { + GL::Program ps; + GLuint vao = 0; + } m_imgui; + GLuint m_ps_ss[1 << 8]; GSDepthStencilOGL* m_om_dss[1 << 5] = {}; std::unordered_map m_programs; @@ -269,21 +276,47 @@ private: GLuint m_palette_ss = 0; + std::array m_timestamp_queries = {}; + float m_accumulated_gpu_time = 0.0f; + u8 m_read_timestamp_query = 0; + u8 m_write_timestamp_query = 0; + u8 m_waiting_timestamp_queries = 0; + bool m_timestamp_query_started = false; + bool m_gpu_timing_enabled = false; + GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache; GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache; - GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) final; + std::string m_shader_tfx_vgs; + std::string m_shader_tfx_fs; - void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c, const bool linear) final; - void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, bool linear, const InterlaceConstantBuffer& cb) final; - void DoFXAA(GSTexture* sTex, GSTexture* dTex) final; - void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) final; + void SetSwapInterval(); + void DestroyResources(); + + void CreateTimestampQueries(); + void DestroyTimestampQueries(); + void PopTimestampQuery(); + void KickTimestampQuery(); + + GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override; + + void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, const GSVector4& c, const bool linear) override; + void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, bool linear, const InterlaceConstantBuffer& cb) override; + + bool CompileFXAAProgram(); + void DoFXAA(GSTexture* sTex, GSTexture* dTex) override; + + bool CompileShadeBoostProgram(); + void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) override; bool CreateCASPrograms(); - bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) final; + bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) override; - void OMAttachRt(GSTextureOGL* rt = NULL); - void OMAttachDs(GSTextureOGL* ds = NULL); + bool CreateImGuiProgram(); + void RenderImGui(); + + void OMAttachRt(GSTextureOGL* rt = nullptr); + void OMAttachDs(GSTextureOGL* ds = nullptr); void OMSetFBO(GLuint fbo); void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds); @@ -295,53 +328,70 @@ public: __fi static GSDeviceOGL* GetInstance() { return static_cast(g_gs_device.get()); } // Used by OpenGL, so the same calling convention is required. - static void APIENTRY DebugOutputToFile(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam); + static void APIENTRY DebugMessageCallback(GLenum gl_source, GLenum gl_type, GLuint id, GLenum gl_severity, GLsizei gl_length, const GLchar* gl_message, const void* userParam); static GL::StreamBuffer* GetTextureUploadBuffer(); __fi u32 GetFBORead() const { return m_fbo_read; } __fi u32 GetFBOWrite() const { return m_fbo_write; } - bool Create() override; + RenderAPI GetRenderAPI() const override; + bool HasSurface() const override; - void ResetAPIState() override; - void RestoreAPIState() override; + bool Create(const WindowInfo& wi, VsyncMode vsync) override; + void Destroy() override; + + bool ChangeWindow(const WindowInfo& new_wi) override; + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsExclusiveFullscreen() const override; + bool IsExclusiveFullscreen() override; + bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + void DestroySurface() override; + std::string GetDriverInfo() const override; + + void SetVSync(VsyncMode mode) override; + + PresentResult BeginPresent(bool frame_skip) override; + void EndPresent() override; + + bool SetGPUTimingEnabled(bool enabled) override; + float GetAndResetAccumulatedGPUTime() override; void DrawPrimitive(); void DrawIndexedPrimitive(); void DrawIndexedPrimitive(int offset, int count); - void ClearRenderTarget(GSTexture* t, const GSVector4& c) final; - void ClearRenderTarget(GSTexture* t, u32 c) final; - void InvalidateRenderTarget(GSTexture* t) final; - void ClearDepth(GSTexture* t) final; - void ClearStencil(GSTexture* t, u8 c) final; + void ClearRenderTarget(GSTexture* t, const GSVector4& c) override; + void ClearRenderTarget(GSTexture* t, u32 c) override; + void InvalidateRenderTarget(GSTexture* t) override; + void ClearDepth(GSTexture* t) override; + void ClearStencil(GSTexture* t, u8 c) override; std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override; GSTexture* InitPrimDateTexture(GSTexture* rt, const GSVector4i& area, bool datm); - void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) final; + void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override; - void PushDebugGroup(const char* fmt, ...) final; - void PopDebugGroup() final; - void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) final; + void PushDebugGroup(const char* fmt, ...) override; + void PopDebugGroup() override; + void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override; // BlitRect *does* mess with GL state, be sure to re-bind. void BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2i& dsize, bool at_origin, bool linear); - void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) final; + void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override; void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool linear = true); - void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) final; + void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha) override; void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GL::Program& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear = true); - void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) final; - void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) final; - void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) final; + void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override; + void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override; + void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override; - void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) final; + void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) override; void DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, const GSVector2& ds); - void RenderHW(GSHWDrawConfig& config) final; + void RenderHW(GSHWDrawConfig& config) override; void SendHWDraw(const GSHWDrawConfig& config, bool needs_barrier); void SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, bool datm); @@ -353,14 +403,17 @@ public: void PSSetShaderResource(int i, GSTexture* sr); void PSSetShaderResources(GSTexture* sr0, GSTexture* sr1); void PSSetSamplerState(GLuint ss); - void ClearSamplerCache() final; + void ClearSamplerCache() override; void OMSetDepthStencilState(GSDepthStencilOGL* dss); void OMSetBlendState(bool enable = false, GLenum src_factor = GL_ONE, GLenum dst_factor = GL_ZERO, GLenum op = GL_FUNC_ADD, bool is_constant = false, u8 constant = 0); - void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = NULL); + void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i* scissor = nullptr); void OMSetColorMaskState(OMColorMaskSelector sel = OMColorMaskSelector()); void OMUnbindTexture(GSTextureOGL* tex); + void SetViewport(const GSVector2i& viewport); + void SetScissor(const GSVector4i& scissor); + bool CreateTextureFX(); std::string GetShaderSource(const std::string_view& entry, GLenum type, const std::string_view& glsl_h_code, const std::string_view& macro_sel = std::string_view()); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 07173c6cbe..a1cf61a0eb 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -14,6 +14,15 @@ */ #include "PrecompiledHeader.h" + +#include "GS/GS.h" +#include "GS/Renderers/Vulkan/GSDeviceVK.h" +#include "GS/GSGL.h" +#include "GS/GSPerfMon.h" +#include "GS/GSUtil.h" +#include "Host.h" +#include "ShaderCacheVersion.h" + #include "common/Vulkan/Builders.h" #include "common/Vulkan/Context.h" #include "common/Vulkan/ShaderCache.h" @@ -22,13 +31,9 @@ #include "common/Align.h" #include "common/Path.h" #include "common/ScopedGuard.h" -#include "GS.h" -#include "GSDeviceVK.h" -#include "GS/GSGL.h" -#include "GS/GSPerfMon.h" -#include "GS/GSUtil.h" -#include "Host.h" -#include "HostDisplay.h" + +#include "imgui.h" + #include #include @@ -55,6 +60,16 @@ static VkAttachmentLoadOp GetLoadOpForTexture(GSTextureVK* tex) // clang-format on } +static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(VsyncMode mode) +{ + if (mode == VsyncMode::On) + return VK_PRESENT_MODE_FIFO_KHR; + else if (mode == VsyncMode::Adaptive) + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + else + return VK_PRESENT_MODE_IMMEDIATE_KHR; +} + GSDeviceVK::GSDeviceVK() { #ifdef ENABLE_OGL_DEBUG @@ -64,13 +79,90 @@ GSDeviceVK::GSDeviceVK() std::memset(&m_pipeline_selector, 0, sizeof(m_pipeline_selector)); } -GSDeviceVK::~GSDeviceVK() {} - -bool GSDeviceVK::Create() +GSDeviceVK::~GSDeviceVK() { - if (!GSDevice::Create() || !CheckFeatures()) + pxAssert(!g_vulkan_context); +} + +void GSDeviceVK::GetAdaptersAndFullscreenModes( + std::vector* adapters, std::vector* fullscreen_modes) +{ + std::vector fsmodes; + + if (g_vulkan_context) + { + if (adapters) + *adapters = Vulkan::Context::EnumerateGPUNames(g_vulkan_context->GetVulkanInstance()); + + if (fullscreen_modes) + { + fsmodes = Vulkan::SwapChain::GetSurfaceFullscreenModes( + g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), WindowInfo()); + } + } + else + { + if (Vulkan::LoadVulkanLibrary()) + { + ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); }); + const VkInstance instance = Vulkan::Context::CreateVulkanInstance(nullptr, false, false); + if (instance != VK_NULL_HANDLE) + { + if (Vulkan::LoadVulkanInstanceFunctions(instance)) + *adapters = Vulkan::Context::EnumerateGPUNames(instance); + + vkDestroyInstance(instance, nullptr); + } + } + } + + if (!fsmodes.empty()) + { + fullscreen_modes->clear(); + fullscreen_modes->reserve(fsmodes.size()); + for (const Vulkan::SwapChain::FullscreenModeInfo& fmi : fsmodes) + { + fullscreen_modes->push_back(GetFullscreenModeString(fmi.width, fmi.height, fmi.refresh_rate)); + } + } +} + +RenderAPI GSDeviceVK::GetRenderAPI() const +{ + return RenderAPI::Vulkan; +} + +bool GSDeviceVK::HasSurface() const +{ + return static_cast(m_swap_chain); +} + +bool GSDeviceVK::Create(const WindowInfo& wi, VsyncMode vsync) +{ + if (!GSDevice::Create(wi, vsync)) return false; + WindowInfo local_wi(wi); + const bool debug_device = GSConfig.UseDebugDevice; + if (!Vulkan::Context::Create(GSConfig.Adapter, &local_wi, &m_swap_chain, GetPreferredPresentModeForVsyncMode(vsync), + !GSConfig.DisableThreadedPresentation, debug_device, debug_device)) + { + Console.Error("Failed to create Vulkan context"); + return false; + } + + Vulkan::ShaderCache::Create(GSConfig.DisableShaderCache ? std::string_view() : std::string_view(EmuFolders::Cache), + SHADER_CACHE_VERSION, GSConfig.UseDebugDevice); + + // NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal). + m_window_info = m_swap_chain ? m_swap_chain->GetWindowInfo() : local_wi; + + if (!CheckFeatures()) + { + Console.Error("Your GPU does not support the required Vulkan features."); + return false; + } + { std::optional shader = Host::ReadResourceFileToString("shaders/vulkan/tfx.glsl"); if (!shader.has_value()) @@ -119,31 +211,253 @@ bool GSDeviceVK::Create() CompileCASPipelines(); + if (!CompileImGuiPipeline()) + return false; + InitializeState(); return true; } void GSDeviceVK::Destroy() { - if (!g_vulkan_context) + GSDevice::Destroy(); + + if (g_vulkan_context) + { + EndRenderPass(); + ExecuteCommandBuffer(true); + DestroyResources(); + + g_vulkan_context->WaitForGPUIdle(); + m_swap_chain.reset(); + + Vulkan::ShaderCache::Destroy(); + Vulkan::Context::Destroy(); + } +} + +bool GSDeviceVK::ChangeWindow(const WindowInfo& new_wi) +{ + if (new_wi.type == WindowInfo::Type::Surfaceless) + { + ExecuteCommandBuffer(true); + m_swap_chain.reset(); + m_window_info = new_wi; + return true; + } + + // make sure previous frames are presented + g_vulkan_context->WaitForGPUIdle(); + + // recreate surface in existing swap chain if it already exists + if (m_swap_chain) + { + if (m_swap_chain->RecreateSurface(new_wi)) + { + m_window_info = m_swap_chain->GetWindowInfo(); + return true; + } + + m_swap_chain.reset(); + } + + WindowInfo wi_copy(new_wi); + VkSurfaceKHR surface = Vulkan::SwapChain::CreateVulkanSurface( + g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), &wi_copy); + if (surface == VK_NULL_HANDLE) + { + Console.Error("Failed to create new surface for swap chain"); + return false; + } + + m_swap_chain = Vulkan::SwapChain::Create(wi_copy, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode)); + if (!m_swap_chain) + { + Console.Error("Failed to create swap chain"); + Vulkan::SwapChain::DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), &wi_copy, surface); + return false; + } + + m_window_info = m_swap_chain->GetWindowInfo(); + return true; +} + +void GSDeviceVK::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +{ + if (m_swap_chain->GetWidth() == static_cast(new_window_width) && + m_swap_chain->GetHeight() == static_cast(new_window_height)) + { + // skip unnecessary resizes + m_window_info.surface_scale = new_window_scale; + return; + } + + // make sure previous frames are presented + g_vulkan_context->WaitForGPUIdle(); + + if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale)) + { + // AcquireNextImage() will fail, and we'll recreate the surface. + Console.Error("Failed to resize swap chain. Next present will fail."); + return; + } + + m_window_info = m_swap_chain->GetWindowInfo(); +} + +bool GSDeviceVK::SupportsExclusiveFullscreen() const +{ + return false; +} + +bool GSDeviceVK::IsExclusiveFullscreen() +{ + return false; +} + +bool GSDeviceVK::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) +{ + return false; +} + +void GSDeviceVK::DestroySurface() +{ + g_vulkan_context->WaitForGPUIdle(); + m_swap_chain.reset(); +} + +std::string GSDeviceVK::GetDriverInfo() const +{ + std::string ret; + const u32 api_version = g_vulkan_context->GetDeviceProperties().apiVersion; + const u32 driver_version = g_vulkan_context->GetDeviceProperties().driverVersion; + if (g_vulkan_context->GetOptionalExtensions().vk_khr_driver_properties) + { + const VkPhysicalDeviceDriverProperties& props = g_vulkan_context->GetDeviceDriverProperties(); + ret = StringUtil::StdStringFromFormat( + "Driver %u.%u.%u\nVulkan %u.%u.%u\nConformance Version %u.%u.%u.%u\n%s\n%s\n%s", + VK_VERSION_MAJOR(driver_version), VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version), + VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version), + props.conformanceVersion.major, props.conformanceVersion.minor, props.conformanceVersion.subminor, + props.conformanceVersion.patch, props.driverInfo, props.driverName, + g_vulkan_context->GetDeviceProperties().deviceName); + } + else + { + ret = StringUtil::StdStringFromFormat("Driver %u.%u.%u\nVulkan %u.%u.%u\n%s", VK_VERSION_MAJOR(driver_version), + VK_VERSION_MINOR(driver_version), VK_VERSION_PATCH(driver_version), VK_API_VERSION_MAJOR(api_version), + VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version), + g_vulkan_context->GetDeviceProperties().deviceName); + } + + return ret; +} + +void GSDeviceVK::SetVSync(VsyncMode mode) +{ + if (!m_swap_chain || m_vsync_mode == mode) return; - EndRenderPass(); - ExecuteCommandBuffer(true); - DestroyResources(); - GSDevice::Destroy(); + // This swap chain should not be used by the current buffer, thus safe to destroy. + g_vulkan_context->WaitForGPUIdle(); + m_swap_chain->SetVSync(GetPreferredPresentModeForVsyncMode(mode)); + m_vsync_mode = mode; } -void GSDeviceVK::ResetAPIState() +GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip) { EndRenderPass(); + + if (frame_skip || !m_swap_chain) + return PresentResult::FrameSkipped; + + // Previous frame needs to be presented before we can acquire the swap chain. + g_vulkan_context->WaitForPresentComplete(); + + // Check if the device was lost. + if (g_vulkan_context->CheckLastSubmitFail()) + return PresentResult::DeviceLost; + + VkResult res = m_swap_chain->AcquireNextImage(); + if (res != VK_SUCCESS) + { + m_swap_chain->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(); + } + else if (res == VK_ERROR_SURFACE_LOST_KHR) + { + Console.Warning("Surface lost, attempting to recreate"); + if (!m_swap_chain->RecreateSurface(m_window_info)) + { + Console.Error("Failed to recreate surface after loss"); + g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None); + return PresentResult::FrameSkipped; + } + + res = m_swap_chain->AcquireNextImage(); + } + + // This can happen when multiple resize events happen in quick succession. + // In this case, just wait until the next frame to try again. + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + { + // Still submit the command buffer, otherwise we'll end up with several frames waiting. + LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); + g_vulkan_context->ExecuteCommandBuffer(Vulkan::Context::WaitType::None); + return PresentResult::FrameSkipped; + } + } + + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + + // Swap chain images start in undefined + Vulkan::Texture& swap_chain_texture = m_swap_chain->GetCurrentTexture(); + swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED); + swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + const VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, + m_swap_chain->GetClearRenderPass(), m_swap_chain->GetCurrentFramebuffer(), + {{0, 0}, {swap_chain_texture.GetWidth(), swap_chain_texture.GetHeight()}}, 1u, &clear_value}; + vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); + + const VkViewport vp{0.0f, 0.0f, static_cast(swap_chain_texture.GetWidth()), + static_cast(swap_chain_texture.GetHeight()), 0.0f, 1.0f}; + const VkRect2D scissor{ + {0, 0}, {static_cast(swap_chain_texture.GetWidth()), static_cast(swap_chain_texture.GetHeight())}}; + vkCmdSetViewport(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &vp); + vkCmdSetScissor(g_vulkan_context->GetCurrentCommandBuffer(), 0, 1, &scissor); + return PresentResult::OK; } -void GSDeviceVK::RestoreAPIState() +void GSDeviceVK::EndPresent() { + RenderImGui(); + + VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer(); + vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer()); + m_swap_chain->GetCurrentTexture().TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + g_vulkan_context->SubmitCommandBuffer(m_swap_chain.get(), !m_swap_chain->IsPresentModeSynchronizing()); + g_vulkan_context->MoveToNextCommandBuffer(); + InvalidateCachedState(); } +bool GSDeviceVK::SetGPUTimingEnabled(bool enabled) +{ + return g_vulkan_context->SetEnableGPUTiming(enabled); +} + +float GSDeviceVK::GetAndResetAccumulatedGPUTime() +{ + return g_vulkan_context->GetAndResetAccumulatedGPUTime(); +} + #ifdef ENABLE_OGL_DEBUG static std::array Palette(float phase, const std::array& a, const std::array& b, const std::array& c, const std::array& d) @@ -551,7 +865,7 @@ void GSDeviceVK::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* { DisplayConstantBuffer cb; cb.SetSource(sRect, sTex->GetSize()); - cb.SetTarget(dRect, dTex ? dTex->GetSize() : GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight())); + cb.SetTarget(dRect, dTex ? dTex->GetSize() : GSVector2i(GetWindowWidth(), GetWindowHeight())); cb.SetTime(shaderTime); SetUtilityPushConstants(&cb, sizeof(cb)); @@ -728,7 +1042,7 @@ void GSDeviceVK::DoStretchRect(GSTextureVK* sTex, const GSVector4& sRect, GSText const bool is_present = (!dTex); const bool depth = (dTex && dTex->GetType() == GSTexture::Type::DepthStencil); const GSVector2i size( - is_present ? GSVector2i(g_host_display->GetWindowWidth(), g_host_display->GetWindowHeight()) : dTex->GetSize()); + is_present ? GSVector2i(GetWindowWidth(), GetWindowHeight()) : dTex->GetSize()); const GSVector4i dtex_rc(0, 0, size.x, size.y); const GSVector4i dst_rc(GSVector4i(dRect).rintersect(dtex_rc)); @@ -1007,7 +1321,6 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou m_vertex.start = m_vertex_stream_buffer.GetCurrentOffset() / stride; m_vertex.count = count; - SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0); GSVector4i::storent(m_vertex_stream_buffer.GetCurrentHostPointer(), vertex, count * stride); m_vertex_stream_buffer.CommitMemory(size); @@ -1025,7 +1338,6 @@ void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count) m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u32); m_index.count = count; - SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT32); std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), index, size); m_index_stream_buffer.CommitMemory(size); @@ -1242,6 +1554,9 @@ bool GSDeviceVK::CreateBuffers() return false; } + SetVertexBuffer(m_vertex_stream_buffer.GetBuffer(), 0); + SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT32); + return true; } @@ -1384,16 +1699,6 @@ bool GSDeviceVK::CreateRenderPasses() bool GSDeviceVK::CompileConvertPipelines() { - // we may not have a swap chain if running in headless mode. - Vulkan::SwapChain* swapchain = static_cast(g_host_display->GetSurface()); - if (swapchain) - { - m_swap_chain_render_pass = - g_vulkan_context->GetRenderPass(swapchain->GetSurfaceFormat().format, VK_FORMAT_UNDEFINED); - if (!m_swap_chain_render_pass) - return false; - } - std::optional shader = Host::ReadResourceFileToString("shaders/vulkan/convert.glsl"); if (!shader) { @@ -1583,11 +1888,10 @@ bool GSDeviceVK::CompileConvertPipelines() bool GSDeviceVK::CompilePresentPipelines() { // we may not have a swap chain if running in headless mode. - Vulkan::SwapChain* swapchain = static_cast(g_host_display->GetSurface()); - m_swap_chain_render_pass = swapchain ? - swapchain->GetClearRenderPass() : - g_vulkan_context->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED); - if (!m_swap_chain_render_pass) + m_swap_chain_render_pass = m_swap_chain ? + m_swap_chain->GetClearRenderPass() : + g_vulkan_context->GetRenderPass(VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED); + if (m_swap_chain_render_pass == VK_NULL_HANDLE) return false; std::optional shader = Host::ReadResourceFileToString("shaders/vulkan/present.glsl"); @@ -1880,6 +2184,155 @@ bool GSDeviceVK::CompileCASPipelines() return true; } +bool GSDeviceVK::CompileImGuiPipeline() +{ + const std::optional glsl = Host::ReadResourceFileToString("shaders/vulkan/imgui.glsl"); + if (!glsl.has_value()) + { + Console.Error("Failed to read imgui.glsl"); + return false; + } + + VkShaderModule vs = GetUtilityVertexShader(glsl.value(), "vs_main"); + if (vs == VK_NULL_HANDLE) + { + Console.Error("Failed to compile ImGui vertex shader"); + return false; + } + ScopedGuard vs_guard([&vs]() { Vulkan::Util::SafeDestroyShaderModule(vs); }); + + VkShaderModule ps = GetUtilityFragmentShader(glsl.value(), "ps_main"); + if (ps == VK_NULL_HANDLE) + { + Console.Error("Failed to compile ImGui pixel shader"); + return false; + } + ScopedGuard ps_guard([&ps]() { Vulkan::Util::SafeDestroyShaderModule(ps); }); + + Vulkan::GraphicsPipelineBuilder gpb; + gpb.SetPipelineLayout(m_utility_pipeline_layout); + gpb.SetRenderPass(m_swap_chain_render_pass, 0); + gpb.AddVertexBuffer(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX); + gpb.AddVertexAttribute(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)); + gpb.AddVertexAttribute(1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)); + gpb.AddVertexAttribute(2, 0, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)); + gpb.SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + gpb.SetVertexShader(vs); + gpb.SetFragmentShader(ps); + gpb.SetNoCullRasterizationState(); + gpb.SetNoDepthTestState(); + gpb.SetBlendAttachment(0, true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, + VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD); + gpb.SetDynamicViewportAndScissorState(); + gpb.AddDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS); + + m_imgui_pipeline = gpb.Create(g_vulkan_context->GetDevice(), g_vulkan_shader_cache->GetPipelineCache(), false); + if (!m_imgui_pipeline) + { + Console.Error("Failed to compile ImGui pipeline"); + return false; + } + + Vulkan::Util::SetObjectName(g_vulkan_context->GetDevice(), m_imgui_pipeline, "ImGui pipeline"); + return true; +} + +void GSDeviceVK::RenderImGui() +{ + ImGui::Render(); + const ImDrawData* draw_data = ImGui::GetDrawData(); + if (draw_data->CmdListsCount == 0) + return; + + const float uniforms[2][2] = {{ + 2.0f / static_cast(m_window_info.surface_width), + 2.0f / static_cast(m_window_info.surface_height), + }, + { + -1.0f, + -1.0f, + }}; + + SetUtilityPushConstants(uniforms, sizeof(uniforms)); + SetPipeline(m_imgui_pipeline); + + if (m_utility_sampler != m_linear_sampler) + { + m_utility_sampler = m_linear_sampler; + m_dirty_flags |= DIRTY_FLAG_UTILITY_TEXTURE; + } + + // imgui uses 16-bit indices + SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT16); + + // this is for presenting, we don't want to screw with the viewport/scissor set by display + m_dirty_flags &= ~(DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR); + + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + u32 vertex_offset; + { + const u32 size = sizeof(ImDrawVert) * static_cast(cmd_list->VtxBuffer.Size); + if (!m_vertex_stream_buffer.ReserveMemory(size, sizeof(ImDrawVert))) + { + Console.Warning("Skipping ImGui draw because of no vertex buffer space"); + return; + } + + vertex_offset = m_vertex_stream_buffer.GetCurrentOffset() / sizeof(ImDrawVert); + std::memcpy(m_vertex_stream_buffer.GetCurrentHostPointer(), cmd_list->VtxBuffer.Data, size); + m_vertex_stream_buffer.CommitMemory(size); + } + + u32 index_offset; + { + const u32 size = sizeof(ImDrawIdx) * static_cast(cmd_list->IdxBuffer.Size); + if (!m_index_stream_buffer.ReserveMemory(size, sizeof(ImDrawIdx))) + { + Console.Warning("Skipping ImGui draw because of no vertex buffer space"); + return; + } + + index_offset = m_index_stream_buffer.GetCurrentOffset() / sizeof(ImDrawIdx); + std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), cmd_list->IdxBuffer.Data, size); + m_index_stream_buffer.CommitMemory(size); + } + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + pxAssert(!pcmd->UserCallback); + + const GSVector4 clip = GSVector4::load(&pcmd->ClipRect); + if ((clip.zwzw() <= clip.xyxy()).mask() != 0) + continue; + + SetScissor(GSVector4i(clip)); + + // Since we don't have the GSTexture... + Vulkan::Texture* tex = static_cast(pcmd->GetTexID()); + if (m_utility_texture != tex) + { + m_utility_texture = tex; + m_dirty_flags |= DIRTY_FLAG_UTILITY_TEXTURE; + } + + if (ApplyUtilityState()) + { + vkCmdDrawIndexed(g_vulkan_context->GetCurrentCommandBuffer(), pcmd->ElemCount, 1, + index_offset + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset, 0); + } + } + + g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size); + } + + // normal draws use 32-bit indices + SetIndexBuffer(m_index_stream_buffer.GetBuffer(), 0, VK_INDEX_TYPE_UINT32); +} + bool GSDeviceVK::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array& constants) { EndRenderPass(); @@ -1994,6 +2447,7 @@ void GSDeviceVK::DestroyResources() Vulkan::Util::SafeDestroyPipeline(it); Vulkan::Util::SafeDestroyPipelineLayout(m_cas_pipeline_layout); Vulkan::Util::SafeDestroyDescriptorSetLayout(m_cas_ds_layout); + Vulkan::Util::SafeDestroyPipeline(m_imgui_pipeline); for (auto& it : m_samplers) Vulkan::Util::SafeDestroySampler(it.second); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 3458a99296..0c75d99a0a 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -24,6 +24,11 @@ #include #include +namespace Vulkan +{ +class SwapChain; +} + class GSDeviceVK final : public GSDevice { public: @@ -104,6 +109,8 @@ public: }; private: + std::unique_ptr m_swap_chain; + VkDescriptorSetLayout m_utility_ds_layout = VK_NULL_HANDLE; VkPipelineLayout m_utility_pipeline_layout = VK_NULL_HANDLE; @@ -153,6 +160,7 @@ private: VkDescriptorSetLayout m_cas_ds_layout = VK_NULL_HANDLE; VkPipelineLayout m_cas_pipeline_layout = VK_NULL_HANDLE; std::array m_cas_pipelines = {}; + VkPipeline m_imgui_pipeline = VK_NULL_HANDLE; GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache; GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache; @@ -196,6 +204,9 @@ private: bool CompilePostProcessingPipelines(); bool CompileCASPipelines(); + bool CompileImGuiPipeline(); + void RenderImGui(); + void DestroyResources(); public: @@ -204,6 +215,8 @@ public: __fi static GSDeviceVK* GetInstance() { return static_cast(g_gs_device.get()); } + static void GetAdaptersAndFullscreenModes(std::vector* adapters, std::vector* fullscreen_modes); + __fi VkRenderPass GetTFXRenderPass(bool rt, bool ds, bool hdr, DATE_RENDER_PASS date, bool fbl, bool dsp, VkAttachmentLoadOp rt_op, VkAttachmentLoadOp ds_op) const { @@ -212,11 +225,27 @@ public: __fi VkSampler GetPointSampler() const { return m_point_sampler; } __fi VkSampler GetLinearSampler() const { return m_linear_sampler; } - bool Create() override; + RenderAPI GetRenderAPI() const override; + bool HasSurface() const override; + + bool Create(const WindowInfo& wi, VsyncMode vsync) override; void Destroy() override; - void ResetAPIState() override; - void RestoreAPIState() override; + bool ChangeWindow(const WindowInfo& new_wi) override; + void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; + bool SupportsExclusiveFullscreen() const override; + bool IsExclusiveFullscreen() override; + bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override; + void DestroySurface() override; + std::string GetDriverInfo() const override; + + void SetVSync(VsyncMode mode) override; + + PresentResult BeginPresent(bool frame_skip) override; + void EndPresent() override; + + bool SetGPUTimingEnabled(bool enabled) override; + float GetAndResetAccumulatedGPUTime() override; void PushDebugGroup(const char* fmt, ...) override; void PopDebugGroup() override; diff --git a/pcsx2/Host.h b/pcsx2/Host.h index 4398029c53..d8b4315f80 100644 --- a/pcsx2/Host.h +++ b/pcsx2/Host.h @@ -24,6 +24,8 @@ #include #include +enum class VsyncMode; + namespace Host { /// Typical durations for OSD messages. @@ -87,10 +89,4 @@ namespace Host /// Requests shut down of the current virtual machine. void RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state); - - /// Returns true if the hosting application is currently fullscreen. - bool IsFullscreen(); - - /// Alters fullscreen state of hosting application. - void SetFullscreen(bool enabled); } // namespace Host diff --git a/pcsx2/HostDisplay.cpp b/pcsx2/HostDisplay.cpp deleted file mode 100644 index 13b1bd74bf..0000000000 --- a/pcsx2/HostDisplay.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#include "PrecompiledHeader.h" - -#include "HostDisplay.h" -#include "VMManager.h" - -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/StringUtil.h" - -#include -#include -#include -#include -#include -#include - -std::unique_ptr g_host_display; - -HostDisplayTexture::~HostDisplayTexture() = default; - -HostDisplay::~HostDisplay() = default; - -const char* HostDisplay::RenderAPIToString(RenderAPI api) -{ - switch (api) - { -#define CASE(x) case RenderAPI::x: return #x - CASE(None); - CASE(D3D11); - CASE(D3D12); - CASE(Metal); - CASE(Vulkan); - CASE(OpenGL); - CASE(OpenGLES); -#undef CASE - default: - return "Unknown"; - } -} - -bool HostDisplay::UsesLowerLeftOrigin() const -{ - const RenderAPI api = GetRenderAPI(); - return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES); -} - -bool HostDisplay::GetHostRefreshRate(float* refresh_rate) -{ - if (m_window_info.surface_refresh_rate > 0.0f) - { - *refresh_rate = m_window_info.surface_refresh_rate; - return true; - } - - return WindowInfo::QueryRefreshRateForWindow(m_window_info, refresh_rate); -} - -bool HostDisplay::SetGPUTimingEnabled(bool enabled) -{ - return false; -} - -float HostDisplay::GetAndResetAccumulatedGPUTime() -{ - return 0.0f; -} - -bool HostDisplay::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate) -{ - if (!mode.empty()) - { - std::string_view::size_type sep1 = mode.find('x'); - if (sep1 != std::string_view::npos) - { - std::optional owidth = StringUtil::FromChars(mode.substr(0, sep1)); - sep1++; - - while (sep1 < mode.length() && std::isspace(mode[sep1])) - sep1++; - - if (owidth.has_value() && sep1 < mode.length()) - { - std::string_view::size_type sep2 = mode.find('@', sep1); - if (sep2 != std::string_view::npos) - { - std::optional oheight = StringUtil::FromChars(mode.substr(sep1, sep2 - sep1)); - sep2++; - - while (sep2 < mode.length() && std::isspace(mode[sep2])) - sep2++; - - if (oheight.has_value() && sep2 < mode.length()) - { - std::optional orefresh_rate = StringUtil::FromChars(mode.substr(sep2)); - if (orefresh_rate.has_value()) - { - *width = owidth.value(); - *height = oheight.value(); - *refresh_rate = orefresh_rate.value(); - return true; - } - } - } - } - } - } - - *width = 0; - *height = 0; - *refresh_rate = 0; - return false; -} - -std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float refresh_rate) -{ - return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate); -} - -VsyncMode Host::GetEffectiveVSyncMode() -{ - const bool has_vm = VMManager::GetState() != VMState::Shutdown; - - // Force vsync off when not running at 100% speed. - if (has_vm && EmuConfig.GS.LimitScalar != 1.0f) - return VsyncMode::Off; - - // Otherwise use the config setting. - return EmuConfig.GS.VsyncEnable; -} - -#ifdef ENABLE_OPENGL -#include "Frontend/OpenGLHostDisplay.h" -#endif - -#ifdef ENABLE_VULKAN -#include "Frontend/VulkanHostDisplay.h" -#endif - -#ifdef _WIN32 -#include "Frontend/D3D11HostDisplay.h" -#include "Frontend/D3D12HostDisplay.h" -#endif -#include "GS/Renderers/Metal/GSMetalCPPAccessible.h" - -std::unique_ptr HostDisplay::CreateForAPI(RenderAPI api) -{ - switch (api) - { -#ifdef _WIN32 - case RenderAPI::D3D11: - return std::make_unique(); - case RenderAPI::D3D12: - return std::make_unique(); -#endif -#ifdef __APPLE__ - case RenderAPI::Metal: - return std::unique_ptr(MakeMetalHostDisplay()); -#endif - -#ifdef ENABLE_OPENGL - case RenderAPI::OpenGL: - case RenderAPI::OpenGLES: - return std::make_unique(); -#endif - -#ifdef ENABLE_VULKAN - case RenderAPI::Vulkan: - return std::make_unique(); -#endif - - default: - Console.Error("Unknown render API %u", static_cast(api)); - return {}; - } -} diff --git a/pcsx2/HostDisplay.h b/pcsx2/HostDisplay.h deleted file mode 100644 index e8befe59a4..0000000000 --- a/pcsx2/HostDisplay.h +++ /dev/null @@ -1,206 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 PCSX2 Dev Team - * - * PCSX2 is free software: you can redistribute it and/or modify it under the terms - * of the GNU Lesser General Public License as published by the Free Software Found- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "common/Pcsx2Defs.h" -#include "common/WindowInfo.h" - -#include -#include -#include -#include -#include - -#include "Host.h" -#include "Config.h" - -enum class RenderAPI -{ - None, - D3D11, - Metal, - D3D12, - Vulkan, - OpenGL, - OpenGLES -}; - -/// An abstracted RGBA8 texture. -class HostDisplayTexture -{ -public: - virtual ~HostDisplayTexture(); - - virtual void* GetHandle() const = 0; - virtual u32 GetWidth() const = 0; - virtual u32 GetHeight() const = 0; -}; - -/// Interface to the frontend's renderer. -class HostDisplay -{ -public: - enum class Alignment - { - LeftOrTop, - Center, - RightOrBottom - }; - - enum class PresentResult - { - OK, - FrameSkipped, - DeviceLost - }; - - struct AdapterAndModeList - { - std::vector adapter_names; - std::vector fullscreen_modes; - }; - - virtual ~HostDisplay(); - - /// Returns a string representing the specified API. - static const char* RenderAPIToString(RenderAPI api); - - /// Creates a display for the specified API. - static std::unique_ptr CreateForAPI(RenderAPI api); - - /// Parses a fullscreen mode into its components (width * height @ refresh hz) - static bool ParseFullscreenMode(const std::string_view& mode, 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); - - __fi const WindowInfo& GetWindowInfo() const { return m_window_info; } - __fi s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } - __fi s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } - __fi float GetWindowScale() const { return m_window_info.surface_scale; } - __fi VsyncMode GetVsyncMode() const { return m_vsync_mode; } - - /// Changes the alignment for this display (screen positioning). - __fi Alignment GetDisplayAlignment() const { return m_display_alignment; } - __fi void SetDisplayAlignment(Alignment alignment) { m_display_alignment = alignment; } - - virtual RenderAPI GetRenderAPI() const = 0; - virtual void* GetDevice() const = 0; - virtual void* GetContext() const = 0; - virtual void* GetSurface() const = 0; - - virtual bool HasDevice() const = 0; - virtual bool HasSurface() const = 0; - - /// Creates the rendering/GPU device. This should be called on the thread which owns the window. - virtual bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) = 0; - - /// Fully initializes the rendering device. This should be called on the GS thread. - virtual bool SetupDevice() = 0; - - /// Sets the device for the current thread. Only needed for OpenGL. - virtual bool MakeCurrent() = 0; - - /// Clears the device for the current thread. Only needed for OpenGL. - virtual bool DoneCurrent() = 0; - - /// Destroys the surface we're currently drawing to. - virtual void DestroySurface() = 0; - - /// Switches to a new window/surface. - virtual bool ChangeWindow(const WindowInfo& wi) = 0; - - /// 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; - - /// Returns true if exclusive fullscreen is supported. - virtual bool SupportsFullscreen() const = 0; - - /// Returns true if exclusive fullscreen is active. - virtual bool IsFullscreen() = 0; - - /// Attempts to switch to the specified mode in exclusive fullscreen. - virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0; - - virtual AdapterAndModeList GetAdapterAndModeList() = 0; - virtual std::string GetDriverInfo() const = 0; - - /// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below. - virtual std::unique_ptr CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) = 0; - virtual void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) = 0; - - /// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be - /// displayed, but the GPU command queue will still be flushed. - virtual PresentResult BeginPresent(bool frame_skip) = 0; - - /// Presents the frame to the display, and renders OSD elements. - virtual void EndPresent() = 0; - - /// Changes vsync mode for this display. - virtual void SetVSync(VsyncMode mode) = 0; - - /// ImGui context management, usually called by derived classes. - virtual bool CreateImGuiContext() = 0; - virtual void DestroyImGuiContext() = 0; - virtual bool UpdateImGuiFontTexture() = 0; - - /// Returns the effective refresh rate of this display. - virtual bool GetHostRefreshRate(float* refresh_rate); - - /// Enables/disables GPU frame timing. - virtual bool SetGPUTimingEnabled(bool enabled); - - /// Returns the amount of GPU time utilized since the last time this method was called. - virtual float GetAndResetAccumulatedGPUTime(); - - /// Returns true if it's an OpenGL-based renderer. - bool UsesLowerLeftOrigin() const; - -protected: - WindowInfo m_window_info; - Alignment m_display_alignment = Alignment::Center; - VsyncMode m_vsync_mode = VsyncMode::Off; -}; - -/// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been caled. -extern std::unique_ptr g_host_display; - -namespace Host -{ - /// Creates the host display. This may create a new window. The API used depends on the current configuration. - bool AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail); - - /// Destroys the host display. This may close the display window. - void ReleaseHostDisplay(bool clear_state); - - /// Returns the desired vsync mode, depending on the runtime environment. - VsyncMode GetEffectiveVSyncMode(); - - /// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be - /// displayed, but the GPU command queue will still be flushed. - HostDisplay::PresentResult BeginPresentFrame(bool frame_skip); - - /// Presents the frame to the display, and renders OSD elements. - void EndPresentFrame(); - - /// Called on the MTGS thread when a resize request is received. - void ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale); - - /// Called on the MTGS thread when a request to update the display is received. - /// This could be a fullscreen transition, for example. - void UpdateHostDisplay(); -} // namespace Host - diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index b724228851..5eb7178244 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -27,7 +27,6 @@ #include "Elfheader.h" #include "Host.h" -#include "HostDisplay.h" #include "IconsFontAwesome5.h" #include "VMManager.h" @@ -276,7 +275,7 @@ bool SysMtgsThread::TryOpenGS() if (!GSopen(EmuConfig.GS, EmuConfig.GS.Renderer, RingBuffer.Regs)) return false; - GSsetGameCRC(ElfCRC); + GSSetGameCRC(ElfCRC); return true; } @@ -511,7 +510,7 @@ void SysMtgsThread::MainLoop() break; case GS_RINGTYPE_CRC: - GSsetGameCRC(tag.data[0]); + GSSetGameCRC(tag.data[0]); break; case GS_RINGTYPE_INIT_AND_READ_FIFO: @@ -915,7 +914,7 @@ void SysMtgsThread::ApplySettings() RunOnGSThread([opts = EmuConfig.GS]() { GSUpdateConfig(opts); - g_host_display->SetVSync(Host::GetEffectiveVSyncMode()); + GSSetVSyncMode(Host::GetEffectiveVSyncMode()); }); // We need to synchronize the thread when changing any settings when the download mode @@ -929,9 +928,7 @@ void SysMtgsThread::ResizeDisplayWindow(int width, int height, float scale) { pxAssertRel(IsOpen(), "MTGS is running"); RunOnGSThread([width, height, scale]() { - GSResetAPIState(); - Host::ResizeHostDisplay(width, height, scale); - GSRestoreAPIState(); + GSResizeDisplayWindow(width, height, scale); // If we're paused, re-present the current frame at the new window size. if (VMManager::GetState() == VMState::Paused) @@ -943,9 +940,7 @@ void SysMtgsThread::UpdateDisplayWindow() { pxAssertRel(IsOpen(), "MTGS is running"); RunOnGSThread([]() { - GSResetAPIState(); - Host::UpdateHostDisplay(); - GSRestoreAPIState(); + GSUpdateDisplayWindow(); // If we're paused, re-present the current frame at the new window size. if (VMManager::GetState() == VMState::Paused) @@ -959,7 +954,7 @@ void SysMtgsThread::SetVSyncMode(VsyncMode mode) RunOnGSThread([mode]() { Console.WriteLn("Vsync is %s", mode == VsyncMode::Off ? "OFF" : (mode == VsyncMode::Adaptive ? "ADAPTIVE" : "ON")); - g_host_display->SetVSync(mode); + GSSetVSyncMode(mode); }); } diff --git a/pcsx2/PAD/Host/PAD.cpp b/pcsx2/PAD/Host/PAD.cpp index 0ee59b05b9..9e39bff527 100644 --- a/pcsx2/PAD/Host/PAD.cpp +++ b/pcsx2/PAD/Host/PAD.cpp @@ -72,7 +72,7 @@ void PADshutdown() { } -s32 PADopen(const WindowInfo& wi) +s32 PADopen() { g_key_status.Init(); return 0; diff --git a/pcsx2/PAD/Host/PAD.h b/pcsx2/PAD/Host/PAD.h index d5b4feb0ac..ec91ab9df8 100644 --- a/pcsx2/PAD/Host/PAD.h +++ b/pcsx2/PAD/Host/PAD.h @@ -30,7 +30,7 @@ enum class GenericInputBinding : u8; s32 PADinit(); void PADshutdown(); -s32 PADopen(const WindowInfo& wi); +s32 PADopen(); void PADclose(); s32 PADsetSlot(u8 port, u8 slot); s32 PADfreeze(FreezeAction mode, freezeData* data); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index cb9cb098a1..9ed6bdde7c 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -22,7 +22,6 @@ #include "common/StringUtil.h" #include "Config.h" #include "GS.h" -#include "HostDisplay.h" #include "CDVD/CDVDcommon.h" #include "MemoryCardFile.h" #include "USB/USB.h" diff --git a/pcsx2/USB/usb-lightgun/guncon2.cpp b/pcsx2/USB/usb-lightgun/guncon2.cpp index 335e1dd616..5b105a1799 100644 --- a/pcsx2/USB/usb-lightgun/guncon2.cpp +++ b/pcsx2/USB/usb-lightgun/guncon2.cpp @@ -20,7 +20,6 @@ #include "USB/qemu-usb/USBinternal.h" #include "USB/USB.h" #include "GS/GS.h" -#include "HostDisplay.h" #include "StateWrapper.h" #include "VMManager.h" diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index b5947d75e1..c157b5c7eb 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -39,7 +39,7 @@ #include "GameDatabase.h" #include "GS.h" #include "GSDumpReplayer.h" -#include "HostDisplay.h" +#include "Host.h" #include "HostSettings.h" #include "INISettingsInterface.h" #include "IopBios.h" @@ -993,7 +993,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) ScopedGuard close_spu2(&SPU2::Close); Console.WriteLn("Opening PAD..."); - if (PADinit() != 0 || PADopen(g_host_display->GetWindowInfo()) != 0) + if (PADinit() != 0 || PADopen() != 0) { Host::ReportErrorAsync("Startup Error", "Failed to initialize PAD."); return false; @@ -1591,6 +1591,18 @@ void VMManager::SetPaused(bool paused) SetState(paused ? VMState::Paused : VMState::Running); } +VsyncMode Host::GetEffectiveVSyncMode() +{ + const bool has_vm = VMManager::GetState() != VMState::Shutdown; + + // Force vsync off when not running at 100% speed. + if (has_vm && EmuConfig.GS.LimitScalar != 1.0f) + return VsyncMode::Off; + + // Otherwise use the config setting. + return EmuConfig.GS.VsyncEnable; +} + const std::string& VMManager::Internal::GetElfOverride() { return s_elf_override; diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index 1b995deb48..b5b06e8342 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -194,27 +194,19 @@ - - - - - - - - @@ -228,7 +220,6 @@ - @@ -543,28 +534,20 @@ - - - - - - - - @@ -576,7 +559,6 @@ - diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index 9a1068f960..3e01fcd8ba 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -1157,24 +1157,12 @@ System\Ps2\SPU2 - - Host - - - Host - - - Host - Host Host - - Host - System\Ps2\GS\Renderers\Vulkan @@ -1235,21 +1223,6 @@ System\Ps2\GS\Renderers\Direct3D12 - - Host - - - Host - - - Host - - - Host - - - Host - Tools\Input Recording @@ -2074,21 +2047,9 @@ Host - - Host - - - Host - - - Host - Host - - Host - System\Ps2\GS\Renderers\Vulkan @@ -2147,21 +2108,6 @@ System\Ps2\GS\Renderers\Direct3D12 - - Host - - - Host - - - Host - - - Host - - - Host - Tools\Input Recording diff --git a/tests/ctest/core/StubHost.cpp b/tests/ctest/core/StubHost.cpp index 74768ecad1..d26ab8c174 100644 --- a/tests/ctest/core/StubHost.cpp +++ b/tests/ctest/core/StubHost.cpp @@ -17,8 +17,8 @@ #include "pcsx2/Frontend/ImGuiManager.h" #include "pcsx2/Frontend/InputManager.h" #include "pcsx2/GS.h" +#include "pcsx2/GS/GS.h" #include "pcsx2/Host.h" -#include "pcsx2/HostDisplay.h" #include "pcsx2/HostSettings.h" #include "pcsx2/VMManager.h" @@ -49,7 +49,7 @@ void Host::SetDefaultUISettings(SettingsInterface& si) std::optional> Host::ReadResourceFile(const char* filename) { - return std::nullopt; + return std::nullopt; } std::optional Host::ReadResourceFileToString(const char* filename) @@ -90,7 +90,7 @@ void Host::EndTextInput() std::optional Host::GetTopLevelWindowInfo() { - return std::nullopt; + return std::nullopt; } void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) @@ -105,29 +105,21 @@ void Host::SetRelativeMouseMode(bool enabled) { } -bool Host::AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail) +std::optional Host::AcquireRenderWindow(RenderAPI api) { - return false; + return std::nullopt; } -void Host::ReleaseHostDisplay(bool clear_state) +std::optional Host::UpdateRenderWindow() +{ + return std::nullopt; +} + +void Host::ReleaseRenderWindow() { } -HostDisplay::PresentResult Host::BeginPresentFrame(bool frame_skip) -{ - return HostDisplay::PresentResult::FrameSkipped; -} - -void Host::EndPresentFrame() -{ -} - -void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale) -{ -} - -void Host::UpdateHostDisplay() +void Host::BeginPresentFrame() { } @@ -227,7 +219,7 @@ std::optional InputManager::ConvertHostKeyboardCodeToString(u32 cod SysMtgsThread& GetMTGS() { - throw std::exception(); + throw std::exception(); } //////////////////////////////////////////////////////////////////////////