GS: Combine HostDisplay with GSDevice

GS/DX11: Don't throw bad_alloc on surface creation fail

GS: Link device and host display construction/destruction

FullscreenUI: Replace HostDisplayTexture with GSTexture

GS: Purge HostDisplayTexture

GS: Move everything in HostDisplay to GSDevice

GS: Move ImGui rendering to GSDevice

GS: Get rid of reset/store API state
This commit is contained in:
Stenzek 2023-04-01 15:54:04 +10:00 committed by refractionpcsx2
parent 77f8a0f5f6
commit 398cf43782
78 changed files with 4149 additions and 7396 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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()))

View File

@ -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<ID3DBlob> 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<ID3DBlob> CompileAndAddShaderBlob(const CacheIndexKey& key, const std::string_view& shader_code,
const D3D_SHADER_MACRO* macros, const char* entry_point);

View File

@ -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());

View File

@ -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();

View File

@ -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<ID3DBlob> 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<ID3DBlob> CompileAndAddShaderBlob(const CacheIndexKey& key, std::string_view shader_code,
const D3D_SHADER_MACRO* macros, const char* entry_point);

View File

@ -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<size_t>(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<size_t>(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<size_t>(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)

View File

@ -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);

View File

@ -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()

View File

@ -36,6 +36,7 @@ namespace GL
~ShaderCache();
bool Open(bool is_gles, std::string_view base_path, u32 version);
void Close();
std::optional<Program> 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<u8>& prog_data, u32 prog_format);

View File

@ -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<WindowInfo> Host::AcquireRenderWindow(RenderAPI api)
{
const std::optional<WindowInfo> 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<WindowInfo> 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)

View File

@ -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<WindowInfo> MainWindow::createDisplayWindow(bool fullscreen, bool render_to_main)
{
DevCon.WriteLn("createDisplay(%u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(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<WindowInfo> 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<QWidget*>(m_display_container) : static_cast<QWidget*>(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<WindowInfo> 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)

View File

@ -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<WindowInfo> createDisplayWindow(bool fullscreen, bool render_to_main);
std::optional<WindowInfo> 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);

View File

@ -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<WindowInfo> 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<WindowInfo> 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<WindowInfo> 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<WindowInfo> 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();

View File

@ -21,7 +21,6 @@
#include <optional>
#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<WindowInfo> acquireRenderWindow();
std::optional<WindowInfo> 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<WindowInfo> onCreateDisplayRequested(bool fullscreen, bool render_to_main);
std::optional<WindowInfo> onUpdateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeDisplayRequested(qint32 width, qint32 height);
void onDestroyDisplayRequested();
void onRelativeMouseModeRequested(bool enabled);

View File

@ -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<std::string> adapters;
std::vector<std::string> 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)

View File

@ -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

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "Frontend/D3D11HostDisplay.h"
#include "GS/Renderers/DX11/D3D.h"
#include "imgui.h"
#include "imgui_impl_dx11.h"
#include <array>
#include <dxgi1_5.h>
#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<ID3D11Texture2D> texture, wil::com_ptr_nothrow<ID3D11ShaderResourceView> 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<ID3D11Texture2D> m_texture;
wil::com_ptr_nothrow<ID3D11ShaderResourceView> 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<bool>(m_device);
}
bool D3D11HostDisplay::HasSurface() const
{
return static_cast<bool>(m_swap_chain);
}
std::unique_ptr<HostDisplayTexture> 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<ID3D11Texture2D> 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<ID3D11ShaderResourceView> srv;
hr = m_device->CreateShaderResourceView(texture.get(), &srv_desc, srv.addressof());
if (FAILED(hr))
return {};
return std::make_unique<D3D11HostDisplayTexture>(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<D3D11HostDisplayTexture*>(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<char*>(sr.pData) + (y * sr.RowPitch) + (x * sizeof(u32));
const char* src_ptr = static_cast<const char*>(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<float>(desc.BufferDesc.RefreshRate.Numerator) / static_cast<float>(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<IDXGIAdapter1> dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), EmuConfig.GS.Adapter);
static constexpr std::array<D3D_FEATURE_LEVEL, 3> 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<UINT>(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<ID3D11InfoQueue> info;
if (m_device.try_query_to(&info))
{
info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, TRUE);
}
}
ComPtr<IDXGIDevice> 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<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
const u32 height = static_cast<u32>(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<ID3D11Texture2D> 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<float>(desc.BufferDesc.RefreshRate.Numerator) / static_cast<float>(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<std::tuple<D3D_FEATURE_LEVEL, const char*>, 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<IDXGIDevice> dxgi_dev;
if (m_device.try_query_to(&dxgi_dev))
{
ComPtr<IDXGIAdapter> 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(&current_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<UINT>(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<float, 4> 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<float>(m_window_info.surface_width), static_cast<float>(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<UINT>(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<float>(static_cast<double>(end - start) / (static_cast<double>(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<IDXGIOutput> 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<DXGI_MODE_DESC> 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<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
}
}
}
}
}
return adapter_info;
}
HostDisplay::AdapterAndModeList D3D11HostDisplay::GetAdapterAndModeList()
{
return GetAdapterAndModeList(m_dxgi_factory.get());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "HostDisplay.h"
#include "common/RedtapeWindows.h"
#include "common/RedtapeWilCom.h"
#include "common/WindowInfo.h"
#include <array>
#include <d3d11.h>
#include <dxgi1_5.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
class D3D11HostDisplay final : public HostDisplay
{
public:
template <typename T>
using ComPtr = wil::com_ptr_nothrow<T>;
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<HostDisplayTexture> 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<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context;
ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
ComPtr<ID3D11RenderTargetView> 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<std::array<ComPtr<ID3D11Query>, 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;
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <array>
#include <dxgi1_5.h>
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<D3D12::Texture*>(&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<bool>(g_d3d12_context);
}
bool D3D12HostDisplay::HasSurface() const
{
return static_cast<bool>(m_swap_chain);
}
std::unique_ptr<HostDisplayTexture> 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<D3D12HostDisplayTexture>(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<D3D12HostDisplayTexture*>(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<float>(desc.BufferDesc.RefreshRate.Numerator) / static_cast<float>(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<IDXGIAdapter1> 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<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
const u32 height = static_cast<u32>(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<ID3D12Resource> 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<float>(desc.BufferDesc.RefreshRate.Numerator) / static_cast<float>(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<std::tuple<D3D_FEATURE_LEVEL, const char*>, 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(&current_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<UINT>(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<float>(m_window_info.surface_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(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<float, 4> 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<float>(m_window_info.surface_width), static_cast<float>(m_window_info.surface_height), 0.0f, 1.0f};
const D3D12_RECT scissor{0, 0, static_cast<LONG>(m_window_info.surface_width), static_cast<LONG>(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<u32>(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<UINT>(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<UINT>(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<IDXGIOutput> 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<DXGI_MODE_DESC> 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<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
}
}
}
}
}
return adapter_info;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <d3d12.h>
#include <dxgi1_5.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wil/com.h>
class D3D12HostDisplay : public HostDisplay
{
public:
template <typename T>
using ComPtr = wil::com_ptr_nothrow<T>;
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<HostDisplayTexture> 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<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
std::vector<D3D12::Texture> 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;
};

View File

@ -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<GSTexture>& tex);
static std::string TimeToPrintableString(time_t t);
static void StartAsyncOp(std::function<void(::ProgressCallback*)> callback, std::string name);
static void AsyncOpThreadEntryPoint(std::function<void(::ProgressCallback*)> callback, FullscreenUI::ProgressCallback* progress);
@ -244,12 +246,12 @@ namespace FullscreenUI
static bool LoadResources();
static void DestroyResources();
static std::shared_ptr<HostDisplayTexture> s_app_icon_texture;
static std::array<std::shared_ptr<HostDisplayTexture>, static_cast<u32>(GameDatabaseSchema::Compatibility::Perfect)>
static std::shared_ptr<GSTexture> s_app_icon_texture;
static std::array<std::shared_ptr<GSTexture>, static_cast<u32>(GameDatabaseSchema::Compatibility::Perfect)>
s_game_compatibility_textures;
static std::shared_ptr<HostDisplayTexture> s_fallback_disc_texture;
static std::shared_ptr<HostDisplayTexture> s_fallback_exe_texture;
static std::vector<std::unique_ptr<HostDisplayTexture>> s_cleanup_textures;
static std::shared_ptr<GSTexture> s_fallback_disc_texture;
static std::shared_ptr<GSTexture> s_fallback_exe_texture;
static std::vector<std::unique_ptr<GSTexture>> s_cleanup_textures;
//////////////////////////////////////////////////////////////////////////
// Landing
@ -397,7 +399,7 @@ namespace FullscreenUI
std::string title;
std::string summary;
std::string path;
std::unique_ptr<HostDisplayTexture> preview_texture;
std::unique_ptr<GSTexture> 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<std::string, std::string> s_cover_image_map;
@ -465,6 +467,12 @@ namespace FullscreenUI
// Utility
//////////////////////////////////////////////////////////////////////////
void FullscreenUI::ReleaseTexture(std::unique_ptr<GSTexture>& 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<HostDisplayTexture>& tex : s_cleanup_textures)
tex.reset();
for (std::unique_ptr<GSTexture>& 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<int>(CRCHackLevel::Automatic), s_crc_fix_options, std::size(s_crc_fix_options), -1);
"EmuCore/GS", "crc_hack_level", static_cast<int>(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<float>(cover->GetWidth()), static_cast<float>(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<u32> 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<GSTexture>(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<float>(screenshot->GetWidth()), static_cast<float>(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<float>(image->GetWidth()) / static_cast<float>(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<ImTextureID>(entry.preview_texture ? entry.preview_texture->GetHandle() : GetPlaceholderTexture()->GetHandle()),
ImGui::GetWindowDrawList()->AddImage(static_cast<ImTextureID>(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<float>(cover_texture->GetWidth()), static_cast<float>(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<float>(cover_texture->GetWidth()), static_cast<float>(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<u32>(selected_entry->compatibility_rating) - 1]->GetHandle(),
ImGui::Image(s_game_compatibility_textures[static_cast<u32>(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<float>(cover_texture->GetWidth()), static_cast<float>(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));
}
}

View File

@ -19,8 +19,6 @@
#include <string>
#include <memory>
class HostDisplayTexture;
struct Pcsx2Config;
namespace FullscreenUI

View File

@ -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 <array>
@ -43,7 +45,7 @@ namespace ImGuiFullscreen
using MessageDialogCallbackVariant = std::variant<InfoMessageDialogCallback, ConfirmMessageDialogCallback>;
static std::optional<Common::RGBA8Image> LoadTextureImage(const char* path);
static std::shared_ptr<HostDisplayTexture> UploadTexture(const char* path, const Common::RGBA8Image& image);
static std::shared_ptr<GSTexture> 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<std::string, std::shared_ptr<HostDisplayTexture>> s_texture_cache(128, true);
static std::shared_ptr<HostDisplayTexture> s_placeholder_texture;
static LRUCache<std::string, std::shared_ptr<GSTexture>> s_texture_cache(128, true);
static std::shared_ptr<GSTexture> 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<HostDisplayTexture>& ImGuiFullscreen::GetPlaceholderTexture()
const std::shared_ptr<GSTexture>& ImGuiFullscreen::GetPlaceholderTexture()
{
return s_placeholder_texture;
}
@ -278,26 +280,32 @@ std::optional<Common::RGBA8Image> ImGuiFullscreen::LoadTextureImage(const char*
return image;
}
std::shared_ptr<HostDisplayTexture> ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image)
std::shared_ptr<GSTexture> ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image)
{
std::unique_ptr<HostDisplayTexture> 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<HostDisplayTexture>(std::move(texture));
return std::shared_ptr<GSTexture>(texture, [](GSTexture* tex) { g_gs_device->Recycle(tex); });
}
std::shared_ptr<HostDisplayTexture> ImGuiFullscreen::LoadTexture(const char* path)
std::shared_ptr<GSTexture> ImGuiFullscreen::LoadTexture(const char* path)
{
std::optional<Common::RGBA8Image> image(LoadTextureImage(path));
if (image.has_value())
{
std::shared_ptr<HostDisplayTexture> ret(UploadTexture(path, image.value()));
std::shared_ptr<GSTexture> ret(UploadTexture(path, image.value()));
if (ret)
return ret;
}
@ -305,21 +313,21 @@ std::shared_ptr<HostDisplayTexture> ImGuiFullscreen::LoadTexture(const char* pat
return s_placeholder_texture;
}
HostDisplayTexture* ImGuiFullscreen::GetCachedTexture(const char* name)
GSTexture* ImGuiFullscreen::GetCachedTexture(const char* name)
{
std::shared_ptr<HostDisplayTexture>* tex_ptr = s_texture_cache.Lookup(name);
std::shared_ptr<GSTexture>* tex_ptr = s_texture_cache.Lookup(name);
if (!tex_ptr)
{
std::shared_ptr<HostDisplayTexture> tex(LoadTexture(name));
std::shared_ptr<GSTexture> 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<HostDisplayTexture>* tex_ptr = s_texture_cache.Lookup(name);
std::shared_ptr<GSTexture>* 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<HostDisplayTexture> tex = UploadTexture(it.first.c_str(), it.second);
std::shared_ptr<GSTexture> 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<ImTextureID>(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);

View File

@ -25,7 +25,7 @@
#include <string_view>
#include <vector>
class HostDisplayTexture;
class GSTexture;
namespace ImGuiFullscreen
{
@ -116,10 +116,10 @@ namespace ImGuiFullscreen
void Shutdown(bool clear_state);
/// Texture cache.
const std::shared_ptr<HostDisplayTexture>& GetPlaceholderTexture();
std::shared_ptr<HostDisplayTexture> LoadTexture(const char* path);
HostDisplayTexture* GetCachedTexture(const char* name);
HostDisplayTexture* GetCachedTextureAsync(const char* name);
const std::shared_ptr<GSTexture>& GetPlaceholderTexture();
std::shared_ptr<GSTexture> LoadTexture(const char* path);
GSTexture* GetCachedTexture(const char* name);
GSTexture* GetCachedTextureAsync(const char* name);
bool InvalidateCachedTexture(const std::string& path);
void UploadAsyncTextures();

View File

@ -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<float>(g_host_display->GetWindowWidth());
io.DisplaySize.y = static_cast<float>(g_host_display->GetWindowHeight());
io.DisplaySize.x = static_cast<float>(g_gs_device->GetWindowWidth());
io.DisplaySize.y = static_cast<float>(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<float>(new_width), static_cast<float>(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();

View File

@ -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"

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <mutex>
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
class MetalHostDisplay final : public HostDisplay
{
enum class UsePresentDrawable : u8
{
Never = 0,
Always = 1,
IfVsync = 2,
};
MRCOwned<NSView*> m_view;
MRCOwned<CAMetalLayer*> m_layer;
GSMTLDevice m_dev;
MRCOwned<id<MTLCommandQueue>> m_queue;
MRCOwned<id<MTLTexture>> m_font_tex;
MRCOwned<id<CAMetalDrawable>> m_current_drawable;
MRCOwned<MTLRenderPassDescriptor*> 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<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, bool dynamic = false) override;
void UpdateTexture(id<MTLTexture> 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<MTLCommandBuffer> buffer);
};
#endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "MetalHostDisplay.h"
#include "GS/Renderers/Metal/GSMetalCPPAccessible.h"
#include "GS/Renderers/Metal/GSDeviceMTL.h"
#include <imgui.h>
#ifdef __APPLE__
class MetalHostDisplayTexture final : public HostDisplayTexture
{
MRCOwned<id<MTLTexture>> m_tex;
u32 m_width, m_height;
public:
MetalHostDisplayTexture(MRCOwned<id<MTLTexture>> 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<MTLDevice> dev in devs.Get())
list.adapter_names.push_back([[dev name] UTF8String]);
return list;
}}
template <typename Fn>
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<void*>(static_cast<const void*>(&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<bool>(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<MTLDevice> 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<UsePresentDrawable>(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<u32>(new_window_width) && m_window_info.surface_height == static_cast<u32>(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<HostDisplayTexture> 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<id<MTLTexture>> 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<MetalHostDisplayTexture>(std::move(tex), width, height);
}}
void MetalHostDisplay::UpdateTexture(id<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride)
{
id<MTLCommandBuffer> cmdbuf = [m_queue commandBuffer];
id<MTLBlitCommandEncoder> enc = [cmdbuf blitCommandEncoder];
size_t bytes = data_stride * height;
MRCOwned<id<MTLBuffer>> 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<MTLTexture>)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<GSDeviceMTL*>(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<MTLCommandBuffer> 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<MTLRenderCommandEncoder> 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<GSDeviceMTL*>(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<MTLCommandBuffer>){
[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<bool>(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<std::mutex> 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<std::mutex> l(m_mtx);
float time = m_accumulated_gpu_time * 1000;
m_accumulated_gpu_time = 0;
return time;
}
void MetalHostDisplay::AccumulateCommandBufferTime(id<MTLCommandBuffer> buffer)
{
std::lock_guard<std::mutex> 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__

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <array>
#include <tuple>
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<void*>(static_cast<uintptr_t>(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<HostDisplayTexture> 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<OpenGLHostDisplayTexture>(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, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
m_gl_context->SetSwapInterval(static_cast<s32>(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<bool>(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<s32>(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<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(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<const char*>(glGetString(GL_VENDOR));
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const char* gl_version = reinterpret_cast<const char*>(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<u32>(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<u32>(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<float>(static_cast<double>(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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <glad.h>
#include "HostDisplay.h"
#include "common/GL/Context.h"
#include "common/WindowInfo.h"
#include <array>
#include <bitset>
#include <memory>
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<HostDisplayTexture> 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<GL::Context> m_gl_context;
std::array<GLuint, 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;
};

View File

@ -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 <array>
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<Vulkan::Texture*>(&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<u32>(new_window_width) && m_swap_chain->GetHeight() == static_cast<u32>(new_window_height))
{
// skip unnecessary resizes
m_window_info.surface_scale = new_window_scale;
return;
}
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<HostDisplayTexture> 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<VulkanHostDisplayTexture>(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<VulkanHostDisplayTexture*>(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<bool>(g_vulkan_context);
}
bool VulkanHostDisplay::HasSurface() const
{
return static_cast<bool>(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<float>(swap_chain_texture.GetWidth()), static_cast<float>(swap_chain_texture.GetHeight()), 0.0f, 1.0f};
const VkRect2D scissor{{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(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<Vulkan::SwapChain::FullscreenModeInfo> 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;
}

View File

@ -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 <memory>
#include <string_view>
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<HostDisplayTexture> 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<Vulkan::SwapChain> m_swap_chain;
};

View File

@ -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 <stdio.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#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.h> / 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);
}

View File

@ -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();

View File

@ -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 <d3d12.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#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<typename T>
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.h> / 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();
}

View File

@ -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();

View File

@ -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 <stdio.h>
#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
#include <stddef.h> // intptr_t
#else
#include <stdint.h> // 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 <glad.h>
// 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 "<major>.<minor>"
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<char> 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<char> 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

View File

@ -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();

View File

@ -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 <cstdio>
#include <cstring>
// 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<u32>(width) || bd->FontTexture.GetHeight() != static_cast<u32>(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<VkDeviceSize>(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);
}

View File

@ -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();

View File

@ -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<WindowInfo> 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<unsigned>(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<GSRendererNull>();
}
else if (renderer != GSRendererType::SW)
{
g_gs_renderer = std::make_unique<GSRendererHW>();
}
else
{
g_gs_renderer = std::unique_ptr<GSRenderer>(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<GSRendererNull>();
}
else if (renderer != GSRendererType::SW)
{
g_gs_renderer = std::make_unique<GSRendererHW>();
}
else
{
g_gs_renderer = std::unique_ptr<GSRenderer>(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<u8[]> 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<u64>(static_cast<double>(GetTickFrequency()) / static_cast<double>(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<WindowInfo> 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<std::string>* adapters, std::vector<std::string>* 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<int>(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<u32>* pixels)
{

View File

@ -20,8 +20,21 @@
#include "pcsx2/Config.h"
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
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<std::string>* adapters, std::vector<std::string>* 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<u32>* 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<WindowInfo> 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<WindowInfo> 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();
}

View File

@ -20,6 +20,8 @@
#include "Host.h"
#include "common/StringUtil.h"
#include "imgui.h"
#include <algorithm>
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<u32> owidth = StringUtil::FromChars<u32>(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<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2]))
sep2++;
if (oheight.has_value() && sep2 < mode.length())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(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<GSDownloadTexture> 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);

View File

@ -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 <array>
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<GSTexture*> m_pool;
u64 m_pool_memory_usage = 0;
static const std::array<HWBlend, 3*3*3*3> m_blendMap;
static const std::array<u8, 16> 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<s32>(m_window_info.surface_width); }
__fi s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
__fi GSVector2i GetWindowSize() const { return GSVector2i(static_cast<s32>(m_window_info.surface_width), static_cast<s32>(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<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format);
virtual std::unique_ptr<GSDownloadTexture> 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

View File

@ -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 <algorithm>
#include <array>
#include <deque>
@ -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<u32>(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<float>(window_width);
const float f_height = static_cast<float>(window_height);
@ -318,7 +326,7 @@ static GSVector4 CalculateDrawDstRect(s32 window_width, s32 window_height, const
const GSVector2i fs = GSVector2i(static_cast<int>(static_cast<float>(resolution.x) * g_gs_renderer->GetUpscaleMultiplier()),
static_cast<int>(static_cast<float>(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<float>(fs.x) / src_rect.width();
t_height *= static_cast<float>(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<u32>(draw_rect.z - draw_rect.x);
const u32 draw_height = static_cast<u32>(draw_rect.w - draw_rect.y);

View File

@ -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;

View File

@ -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<std::string> D3D::GetAdapterNames(IDXGIFactory5* factory)
return adapter_names;
}
std::vector<std::string> D3D::GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name)
{
std::vector<std::string> modes;
HRESULT hr;
wil::com_ptr_nothrow<IDXGIAdapter1> adapter = GetChosenOrFirstAdapter(factory, adapter_name);
if (!adapter)
return modes;
wil::com_ptr_nothrow<IDXGIOutput> 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<DXGI_MODE_DESC> 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<float>(mode.RefreshRate.Numerator) / static_cast<float>(mode.RefreshRate.Denominator)));
}
return modes;
}
wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name)
{
if (name.empty())

View File

@ -33,6 +33,9 @@ namespace D3D
// returns a list of all adapter names
std::vector<std::string> GetAdapterNames(IDXGIFactory5* factory);
// returns a list of fullscreen modes for the specified adapter
std::vector<std::string> GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name);
// get an adapter based on name
wil::com_ptr_nothrow<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name);

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
#include "common/D3D11/ShaderCache.h"
#include <unordered_map>
#include <wil/com.h>
#include <dxgi1_3.h>
#include <dxgi1_5.h>
#include <d3d11_1.h>
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<GSDownloadTexture> 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<u32, NUM_CAS_CONSTANTS>& constants) final;
bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) override;
wil::com_ptr_nothrow<ID3D11Device> m_dev;
wil::com_ptr_nothrow<ID3D11DeviceContext> m_ctx;
bool CreateImGuiResources();
void RenderImGui();
wil::com_ptr_nothrow<IDXGIFactory5> m_dxgi_factory;
wil::com_ptr_nothrow<ID3D11Device1> m_dev;
wil::com_ptr_nothrow<ID3D11DeviceContext1> m_ctx;
wil::com_ptr_nothrow<ID3DUserDefinedAnnotation> m_annotation;
wil::com_ptr_nothrow<IDXGISwapChain1> m_swapchain;
wil::com_ptr_nothrow<IDXGISwapChain1> m_swap_chain;
wil::com_ptr_nothrow<ID3D11RenderTargetView> m_swap_chain_rtv;
wil::com_ptr_nothrow<ID3D11Buffer> m_vb;
wil::com_ptr_nothrow<ID3D11Buffer> 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<std::array<wil::com_ptr_nothrow<ID3D11Query>, 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<ID3D11ComputeShader> cs_sharpen;
} m_cas;
struct
{
wil::com_ptr_nothrow<ID3D11InputLayout> il;
wil::com_ptr_nothrow<ID3D11VertexShader> vs;
wil::com_ptr_nothrow<ID3D11PixelShader> ps;
wil::com_ptr_nothrow<ID3D11BlendState> bs;
wil::com_ptr_nothrow<ID3D11Buffer> vs_cb;
} m_imgui;
// Shaders...
std::unordered_map<u32, GSVertexShader11> m_vs;
@ -237,8 +268,6 @@ private:
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
std::unique_ptr<GSTexture11> 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<GSDevice11*>(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<GSDownloadTexture> 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(); }
};

View File

@ -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 <sstream>
#include <limits>
@ -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<bool>(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<IDXGIAdapter1> 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<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(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<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
const u32 height = static_cast<u32>(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<ID3D12Resource> 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<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(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<std::tuple<D3D_FEATURE_LEVEL, const char*>, 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(&current_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<UINT>(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<float, 4> 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<float>(m_window_info.surface_width),
static_cast<float>(m_window_info.surface_height), 0.0f, 1.0f};
const D3D12_RECT scissor{
0, 0, static_cast<LONG>(m_window_info.surface_width), static_cast<LONG>(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<u32>(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<UINT>(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<UINT>(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<std::string> hlsl = Host::ReadResourceFileToString("shaders/dx11/imgui.fx");
if (!hlsl.has_value())
{
Console.Error("Failed to read imgui.fx");
return false;
}
const ComPtr<ID3DBlob> vs = m_shader_cache.GetVertexShader(hlsl.value(), nullptr, "vs_main");
const ComPtr<ID3DBlob> 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<float>(m_window_info.surface_width);
const float T = 0.0f;
const float B = static_cast<float>(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<u32>(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<u32>(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<false>(&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<D3D12::Texture*>(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<u32, NUM_CAS_CONSTANTS>& 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>(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)

View File

@ -138,6 +138,15 @@ public:
};
private:
ComPtr<IDXGIFactory5> m_dxgi_factory;
ComPtr<IDXGISwapChain1> m_swap_chain;
std::vector<D3D12::Texture> 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<ID3D12RootSignature> m_tfx_root_signature;
ComPtr<ID3D12RootSignature> m_utility_root_signature;
@ -161,6 +170,7 @@ private:
std::array<std::array<ComPtr<ID3D12PipelineState>, 2>, 2> m_date_image_setup_pipelines{}; // [depth][datm]
ComPtr<ID3D12PipelineState> m_fxaa_pipeline;
ComPtr<ID3D12PipelineState> m_shadeboost_pipeline;
ComPtr<ID3D12PipelineState> m_imgui_pipeline;
std::unordered_map<u32, ComPtr<ID3DBlob>> m_tfx_vertex_shaders;
std::unordered_map<u32, ComPtr<ID3DBlob>> 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<GSDevice12*>(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;

View File

@ -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<id<MTLFence>> m_draw_sync_fence;
MRCOwned<MTLFunctionConstantValues*> m_fn_constants;
MRCOwned<MTLVertexDescriptor*> m_hw_vertex;
std::unique_ptr<GSTextureMTL> m_font;
// Previously in MetalHostDisplay.
MRCOwned<NSView*> m_view;
MRCOwned<CAMetalLayer*> m_layer;
MRCOwned<id<CAMetalDrawable>> m_current_drawable;
MRCOwned<MTLRenderPassDescriptor*> 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<id<MTLFunction>> LoadShader(NSString* name);
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
MRCOwned<id<MTLComputePipelineState>> MakeComputePipeline(id<MTLFunction> 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<MTLTexture> 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<MTLCommandBuffer> 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<GSDownloadTexture> 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<MTLRenderPipelineState> 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);

View File

@ -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 <imgui.h>
#include "imgui.h"
#ifdef __APPLE__
#include "GSMTLSharedHeader.h"
@ -36,6 +36,15 @@ GSDevice* MakeGSDeviceMTL()
return new GSDeviceMTL();
}
std::vector<std::string> GetMetalAdapterList()
{ @autoreleasepool {
std::vector<std::string> list;
auto devs = MRCTransfer(MTLCopyAllDevices());
for (id<MTLDevice> 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<std::mutex> 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<MTLCommandBuffer> 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<MetalHostDisplay*>(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 <typename Fn>
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<bool>(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<MTLDevice> 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<UsePresentDrawable>(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<const GSMTLDevice*>(g_host_display->GetDevice());
m_queue = MRCRetain((__bridge id<MTLCommandQueue>)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<std::mutex> 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<u32>(new_window_width) && m_window_info.surface_height == static_cast<u32>(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<MTLTexture> texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride)
{
id<MTLCommandBuffer> cmdbuf = [m_queue commandBuffer];
id<MTLBlitCommandEncoder> enc = [cmdbuf blitCommandEncoder];
size_t bytes = data_stride * height;
MRCOwned<id<MTLBuffer>> 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<GSDeviceMTL*>(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<MTLCommandBuffer> 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<MTLRenderCommandEncoder> 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<GSDeviceMTL*>(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<MTLCommandBuffer>){
[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<std::mutex> 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<std::mutex> l(m_mtx);
float time = m_accumulated_gpu_time * 1000;
m_accumulated_gpu_time = 0;
return time;
}
void GSDeviceMTL::AccumulateCommandBufferTime(id<MTLCommandBuffer> buffer)
{
std::lock_guard<std::mutex> 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<GSTextureMTL*>(t)->RequestStencilClear(c);
}
void GSDeviceMTL::InvalidateRenderTarget(GSTexture* t)
{
// TODO: Implement me
}
std::unique_ptr<GSDownloadTexture> 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<MTLTexture>)tex atIndex:0];
if (!m_dev.features.texture_swizzle)
{
bool a8 = [(__bridge id<MTLTexture>)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];

View File

@ -14,15 +14,16 @@
*/
#pragma once
#include <string>
#include <vector>
// 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<std::string> GetMetalAdapterList();
#endif

View File

@ -362,12 +362,6 @@ fragment half4 ps_imgui(ImGuiShaderData data [[stage_in]], texture2d<half> textu
return data.c * texture.sample(s, data.t);
}
fragment half4 ps_imgui_a8(ImGuiShaderData data [[stage_in]], texture2d<half> 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<float> tex, constant float3& cb [[buffer(GSMTLBufferIndexUniforms)]])
{
const float brt = cb.x;

View File

@ -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 <cinttypes>
#include <fstream>
#include <sstream>
@ -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, &current_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
if (mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
m_gl_context->SetSwapInterval(static_cast<s32>(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<float>(GLState::viewport.x), static_cast<float>(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<float>(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<s32>(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<u32>(new_window_width) &&
m_window_info.surface_height == static_cast<u32>(new_window_height))
{
return;
}
m_gl_context->ResizeSurface(static_cast<u32>(new_window_width), static_cast<u32>(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<const char*>(glGetString(GL_VENDOR));
const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const char* gl_version = reinterpret_cast<const char*>(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<u32>(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<u32>(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<float>(static_cast<double>(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<int>(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<std::string> 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<GL::Program> 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<std::string> 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<std::string> glsl = Host::ReadResourceFileToString("shaders/opengl/imgui.glsl");
if (!glsl.has_value())
{
Console.Error("Failed to read imgui.glsl");
return false;
}
std::optional<GL::Program> 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<float>(m_window_info.surface_width);
constexpr float T = 0.0f;
const float B = static_cast<float>(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<u32>(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<u32>(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<false>(&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<GLuint>(reinterpret_cast<uintptr_t>(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;

View File

@ -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<GL::Context> 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<ProgramSelector, GL::Program, ProgramSelectorHash> m_programs;
@ -269,21 +276,47 @@ private:
GLuint m_palette_ss = 0;
std::array<GLuint, 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;
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<u32, NUM_CAS_CONSTANTS>& constants) final;
bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& 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<GSDeviceOGL*>(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<GSDownloadTexture> 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());

View File

@ -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 <sstream>
#include <limits>
@ -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<std::string>* adapters, std::vector<std::string>* fullscreen_modes)
{
std::vector<Vulkan::SwapChain::FullscreenModeInfo> 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<bool>(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<std::string> 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<u32>(new_window_width) &&
m_swap_chain->GetHeight() == static_cast<u32>(new_window_height))
{
// skip unnecessary resizes
m_window_info.surface_scale = new_window_scale;
return;
}
// make sure previous frames are presented
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<float>(swap_chain_texture.GetWidth()),
static_cast<float>(swap_chain_texture.GetHeight()), 0.0f, 1.0f};
const VkRect2D scissor{
{0, 0}, {static_cast<u32>(swap_chain_texture.GetWidth()), static_cast<u32>(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<float, 3> Palette(float phase, const std::array<float, 3>& a, const std::array<float, 3>& b,
const std::array<float, 3>& c, const std::array<float, 3>& 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<Vulkan::SwapChain*>(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<std::string> 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<Vulkan::SwapChain*>(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<std::string> shader = Host::ReadResourceFileToString("shaders/vulkan/present.glsl");
@ -1880,6 +2184,155 @@ bool GSDeviceVK::CompileCASPipelines()
return true;
}
bool GSDeviceVK::CompileImGuiPipeline()
{
const std::optional<std::string> 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<float>(m_window_info.surface_width),
2.0f / static_cast<float>(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<u32>(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<u32>(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<false>(&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<Vulkan::Texture*>(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<u32, NUM_CAS_CONSTANTS>& 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);

View File

@ -24,6 +24,11 @@
#include <array>
#include <unordered_map>
namespace Vulkan
{
class SwapChain;
}
class GSDeviceVK final : public GSDevice
{
public:
@ -104,6 +109,8 @@ public:
};
private:
std::unique_ptr<Vulkan::SwapChain> 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<VkPipeline, NUM_CAS_PIPELINES> 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<GSDeviceVK*>(g_gs_device.get()); }
static void GetAdaptersAndFullscreenModes(std::vector<std::string>* adapters, std::vector<std::string>* 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;

View File

@ -24,6 +24,8 @@
#include <optional>
#include <vector>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "HostDisplay.h"
#include "VMManager.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include <cerrno>
#include <cmath>
#include <cstring>
#include <chrono>
#include <thread>
#include <vector>
std::unique_ptr<HostDisplay> 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<u32> owidth = StringUtil::FromChars<u32>(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<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
sep2++;
while (sep2 < mode.length() && std::isspace(mode[sep2]))
sep2++;
if (oheight.has_value() && sep2 < mode.length())
{
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(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> HostDisplay::CreateForAPI(RenderAPI api)
{
switch (api)
{
#ifdef _WIN32
case RenderAPI::D3D11:
return std::make_unique<D3D11HostDisplay>();
case RenderAPI::D3D12:
return std::make_unique<D3D12HostDisplay>();
#endif
#ifdef __APPLE__
case RenderAPI::Metal:
return std::unique_ptr<HostDisplay>(MakeMetalHostDisplay());
#endif
#ifdef ENABLE_OPENGL
case RenderAPI::OpenGL:
case RenderAPI::OpenGLES:
return std::make_unique<OpenGLHostDisplay>();
#endif
#ifdef ENABLE_VULKAN
case RenderAPI::Vulkan:
return std::make_unique<VulkanHostDisplay>();
#endif
default:
Console.Error("Unknown render API %u", static_cast<unsigned>(api));
return {};
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/Pcsx2Defs.h"
#include "common/WindowInfo.h"
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#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<std::string> adapter_names;
std::vector<std::string> 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<HostDisplay> 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<s32>(m_window_info.surface_width); }
__fi s32 GetWindowHeight() const { return static_cast<s32>(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<HostDisplayTexture> 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<HostDisplay> 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

View File

@ -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);
});
}

View File

@ -72,7 +72,7 @@ void PADshutdown()
{
}
s32 PADopen(const WindowInfo& wi)
s32 PADopen()
{
g_key_status.Init();
return 0;

View File

@ -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);

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -194,27 +194,19 @@
<ClCompile Include="DEV9\Win32\tap-win32.cpp" />
<ClCompile Include="Frontend\CommonHost.cpp" />
<ClCompile Include="Frontend\CommonHotkeys.cpp" />
<ClCompile Include="Frontend\D3D11HostDisplay.cpp" />
<ClCompile Include="Frontend\D3D12HostDisplay.cpp" />
<ClCompile Include="Frontend\DInputSource.cpp" />
<ClCompile Include="Frontend\FullscreenUI.cpp" />
<ClCompile Include="Frontend\GameList.cpp" />
<ClCompile Include="Frontend\ImGuiFullscreen.cpp" />
<ClCompile Include="Frontend\ImGuiManager.cpp" />
<ClCompile Include="Frontend\ImGuiOverlays.cpp" />
<ClCompile Include="Frontend\imgui_impl_dx11.cpp" />
<ClCompile Include="Frontend\imgui_impl_dx12.cpp" />
<ClCompile Include="Frontend\imgui_impl_opengl3.cpp" />
<ClCompile Include="Frontend\imgui_impl_vulkan.cpp" />
<ClCompile Include="INISettingsInterface.cpp" />
<ClCompile Include="Frontend\InputManager.cpp" />
<ClCompile Include="Frontend\InputSource.cpp" />
<ClCompile Include="Frontend\LayeredSettingsInterface.cpp" />
<ClCompile Include="Frontend\LogSink.cpp" />
<ClCompile Include="Frontend\OpenGLHostDisplay.cpp" />
<ClCompile Include="Frontend\Achievements.cpp" />
<ClCompile Include="Frontend\SDLInputSource.cpp" />
<ClCompile Include="Frontend\VulkanHostDisplay.cpp" />
<ClCompile Include="Frontend\XInputSource.cpp" />
<ClCompile Include="GameDatabase.cpp" />
<ClCompile Include="Gif_Logger.cpp" />
@ -228,7 +220,6 @@
<ClCompile Include="GS\Renderers\Vulkan\GSDeviceVK.cpp" />
<ClCompile Include="GS\Renderers\Vulkan\GSTextureVK.cpp" />
<ClCompile Include="Host.cpp" />
<ClCompile Include="HostDisplay.cpp" />
<ClCompile Include="Frontend\HostSettings.cpp" />
<ClCompile Include="IopGte.cpp" />
<ClCompile Include="MemoryCardProtocol.cpp" />
@ -543,28 +534,20 @@
<ClInclude Include="DEV9\Win32\pcap_io_win32_funcs.h" />
<ClInclude Include="DEV9\Win32\tap.h" />
<ClInclude Include="Frontend\CommonHost.h" />
<ClInclude Include="Frontend\D3D11HostDisplay.h" />
<ClInclude Include="Frontend\D3D12HostDisplay.h" />
<ClInclude Include="Frontend\DInputSource.h" />
<ClInclude Include="Frontend\FullscreenUI.h" />
<ClInclude Include="Frontend\GameList.h" />
<ClInclude Include="Frontend\ImGuiFullscreen.h" />
<ClInclude Include="Frontend\ImGuiManager.h" />
<ClInclude Include="Frontend\ImGuiOverlays.h" />
<ClInclude Include="Frontend\imgui_impl_dx11.h" />
<ClInclude Include="Frontend\imgui_impl_dx12.h" />
<ClInclude Include="Frontend\imgui_impl_opengl3.h" />
<ClInclude Include="Frontend\imgui_impl_vulkan.h" />
<ClInclude Include="GS\Renderers\HW\GSHwHack.h" />
<ClInclude Include="INISettingsInterface.h" />
<ClInclude Include="Frontend\InputManager.h" />
<ClInclude Include="Frontend\InputSource.h" />
<ClInclude Include="Frontend\LayeredSettingsInterface.h" />
<ClInclude Include="Frontend\LogSink.h" />
<ClInclude Include="Frontend\OpenGLHostDisplay.h" />
<ClInclude Include="Frontend\Achievements.h" />
<ClInclude Include="Frontend\SDLInputSource.h" />
<ClInclude Include="Frontend\VulkanHostDisplay.h" />
<ClInclude Include="Frontend\XInputSource.h" />
<ClInclude Include="GameDatabase.h" />
<ClInclude Include="Gif_Unit.h" />
@ -576,7 +559,6 @@
<ClInclude Include="GS\Renderers\Vulkan\GSDeviceVK.h" />
<ClInclude Include="GS\Renderers\Vulkan\GSTextureVK.h" />
<ClInclude Include="Host.h" />
<ClInclude Include="HostDisplay.h" />
<ClInclude Include="HostSettings.h" />
<ClInclude Include="IopGte.h" />
<ClInclude Include="IPU\mpeg2_vlc.h" />

View File

@ -1157,24 +1157,12 @@
<ClCompile Include="SPU2\SndOut_Cubeb.cpp">
<Filter>System\Ps2\SPU2</Filter>
</ClCompile>
<ClCompile Include="HostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\OpenGLHostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\D3D11HostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\ImGuiManager.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Host.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\VulkanHostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="GS\Renderers\Vulkan\GSTextureVK.cpp">
<Filter>System\Ps2\GS\Renderers\Vulkan</Filter>
</ClCompile>
@ -1235,21 +1223,6 @@
<ClCompile Include="GS\Renderers\DX12\GSDevice12.cpp">
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
</ClCompile>
<ClCompile Include="Frontend\D3D12HostDisplay.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\imgui_impl_dx11.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\imgui_impl_dx12.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\imgui_impl_opengl3.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Frontend\imgui_impl_vulkan.cpp">
<Filter>Host</Filter>
</ClCompile>
<ClCompile Include="Recording\InputRecording.cpp">
<Filter>Tools\Input Recording</Filter>
</ClCompile>
@ -2074,21 +2047,9 @@
<ClInclude Include="Host.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="HostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\OpenGLHostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\D3D11HostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\ImGuiManager.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\VulkanHostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="GS\Renderers\Vulkan\GSTextureVK.h">
<Filter>System\Ps2\GS\Renderers\Vulkan</Filter>
</ClInclude>
@ -2147,21 +2108,6 @@
<ClInclude Include="GS\Renderers\DX12\GSDevice12.h">
<Filter>System\Ps2\GS\Renderers\Direct3D12</Filter>
</ClInclude>
<ClInclude Include="Frontend\D3D12HostDisplay.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\imgui_impl_dx11.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\imgui_impl_dx12.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\imgui_impl_opengl3.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Frontend\imgui_impl_vulkan.h">
<Filter>Host</Filter>
</ClInclude>
<ClInclude Include="Recording\InputRecording.h">
<Filter>Tools\Input Recording</Filter>
</ClInclude>

View File

@ -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<std::vector<u8>> Host::ReadResourceFile(const char* filename)
{
return std::nullopt;
return std::nullopt;
}
std::optional<std::string> Host::ReadResourceFileToString(const char* filename)
@ -90,7 +90,7 @@ void Host::EndTextInput()
std::optional<WindowInfo> 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<WindowInfo> Host::AcquireRenderWindow(RenderAPI api)
{
return false;
return std::nullopt;
}
void Host::ReleaseHostDisplay(bool clear_state)
std::optional<WindowInfo> 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<std::string> InputManager::ConvertHostKeyboardCodeToString(u32 cod
SysMtgsThread& GetMTGS()
{
throw std::exception();
throw std::exception();
}
//////////////////////////////////////////////////////////////////////////