Move frontend-common to util/core

This commit is contained in:
Stenzek
2023-08-13 16:28:28 +10:00
parent 5b980dafa5
commit bb60170d9a
144 changed files with 2506 additions and 3180 deletions

View File

@ -21,8 +21,18 @@ add_library(util
cd_xa.h
cue_parser.cpp
cue_parser.h
host_display.cpp
host_display.h
imgui_fullscreen.cpp
imgui_fullscreen.h
imgui_manager.cpp
imgui_manager.h
ini_settings_interface.cpp
ini_settings_interface.h
input_manager.cpp
input_manager.h
input_source.cpp
input_source.h
iso_reader.cpp
iso_reader.h
jit_code_buffer.cpp
@ -31,6 +41,15 @@ add_library(util
memory_arena.h
page_fault_handler.cpp
page_fault_handler.h
platform_misc.h
postprocessing_chain.cpp
postprocessing_chain.h
postprocessing_shader.cpp
postprocessing_shader.h
postprocessing_shadergen.cpp
postprocessing_shadergen.h
shadergen.cpp
shadergen.h
shiftjis.cpp
shiftjis.h
state_wrapper.cpp
@ -41,5 +60,95 @@ add_library(util
target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(util PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(util PUBLIC common simpleini)
target_link_libraries(util PRIVATE libchdr zlib soundtouch)
target_link_libraries(util PUBLIC common simpleini imgui)
target_link_libraries(util PRIVATE stb libchdr zlib soundtouch)
if(ENABLE_CUBEB)
target_sources(util PRIVATE
cubeb_audio_stream.cpp
cubeb_audio_stream.h
)
target_compile_definitions(util PUBLIC "WITH_CUBEB=1")
target_link_libraries(util PRIVATE cubeb)
endif()
if(ENABLE_OPENGL)
target_sources(util PRIVATE
opengl_host_display.cpp
opengl_host_display.h
imgui_impl_opengl3.cpp
imgui_impl_opengl3.h
)
target_link_libraries(util PRIVATE glad)
endif()
if(ENABLE_VULKAN)
target_sources(util PRIVATE
imgui_impl_vulkan.cpp
imgui_impl_vulkan.h
vulkan_host_display.cpp
vulkan_host_display.h
)
endif()
if(SDL2_FOUND)
target_sources(util PRIVATE
sdl_input_source.cpp
sdl_input_source.h
)
target_compile_definitions(util PUBLIC "WITH_SDL2=1")
target_include_directories(util PUBLIC ${SDL2_INCLUDE_DIRS})
target_link_libraries(util PUBLIC ${SDL2_LIBRARIES})
# Copy bundled SDL2 to output on Windows.
if(WIN32)
add_custom_command(TARGET util POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_DLL_PATH}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2.dll")
endif()
endif()
if(USE_X11)
target_compile_definitions(util PRIVATE "-DUSE_X11=1")
target_include_directories(util PRIVATE "${X11_INCLUDE_DIR}")
endif()
if(USE_DBUS)
target_compile_definitions(util PRIVATE USE_DBUS)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DBUS REQUIRED dbus-1)
target_include_directories(util PRIVATE ${DBUS_INCLUDE_DIRS})
target_link_libraries(util PRIVATE ${DBUS_LINK_LIBRARIES})
endif()
if(WIN32)
target_sources(util PRIVATE
d3d11_host_display.cpp
d3d11_host_display.h
d3d12_host_display.cpp
d3d12_host_display.h
dinput_source.cpp
dinput_source.h
imgui_impl_dx11.cpp
imgui_impl_dx11.h
imgui_impl_dx12.cpp
imgui_impl_dx12.h
platform_misc_win32.cpp
win32_raw_input_source.cpp
win32_raw_input_source.h
xaudio2_audio_stream.cpp
xaudio2_audio_stream.h
xinput_source.cpp
xinput_source.h
)
target_link_libraries(util PRIVATE d3d11.lib dxgi.lib winmm.lib)
elseif(APPLE)
find_library(IOK_LIBRARY IOKit REQUIRED)
target_link_libraries(util PRIVATE "${IOK_LIBRARY}")
target_sources(util PRIVATE
platform_misc_mac.mm
)
elseif(NOT ANDROID)
target_sources(util PRIVATE
platform_misc_unix.cpp
)
endif()

View File

@ -7,6 +7,8 @@
#include <atomic>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#ifdef _MSC_VER
#pragma warning(push)
@ -77,6 +79,17 @@ public:
static std::unique_ptr<AudioStream> CreateNullStream(u32 sample_rate, u32 channels, u32 buffer_ms);
#ifdef WITH_CUBEB
static std::unique_ptr<AudioStream> CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch);
static std::vector<std::string> GetCubebDriverNames();
static std::vector<std::pair<std::string, std::string>> GetCubebOutputDevices(const char* driver);
#endif
#ifdef _WIN32
static std::unique_ptr<AudioStream> CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms, u32 latency_ms,
AudioStretchMode stretch);
#endif
protected:
AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
void BaseInitialize();

View File

@ -0,0 +1,288 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "cubeb_audio_stream.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string_util.h"
#include "core/host.h"
#include "core/settings.h"
#include "cubeb/cubeb.h"
#include "fmt/format.h"
Log_SetChannel(CubebAudioStream);
#ifdef _WIN32
#include "common/windows_headers.h"
#include <objbase.h>
#pragma comment(lib, "Ole32.lib")
#endif
static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state);
CubebAudioStream::CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
: AudioStream(sample_rate, channels, buffer_ms, stretch)
{
}
CubebAudioStream::~CubebAudioStream()
{
DestroyContextAndStream();
}
void CubebAudioStream::LogCallback(const char* fmt, ...)
{
std::va_list ap;
va_start(ap, fmt);
std::string msg(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
Log_DevPrintf("(Cubeb): %s", msg.c_str());
}
void CubebAudioStream::DestroyContextAndStream()
{
if (stream)
{
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
stream = nullptr;
}
if (m_context)
{
cubeb_destroy(m_context);
m_context = nullptr;
}
#ifdef _WIN32
if (m_com_initialized_by_us)
{
CoUninitialize();
m_com_initialized_by_us = false;
}
#endif
}
bool CubebAudioStream::Initialize(u32 latency_ms)
{
#ifdef _WIN32
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
m_com_initialized_by_us = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
{
Host::ReportErrorAsync("Error", "Failed to initialize COM for Cubeb");
return false;
}
#endif
cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
int rv =
cubeb_init(&m_context, "DuckStation", g_settings.audio_driver.empty() ? nullptr : g_settings.audio_driver.c_str());
if (rv != CUBEB_OK)
{
Host::ReportFormattedErrorAsync("Error", "Could not initialize cubeb context: %d", rv);
return false;
}
cubeb_stream_params params = {};
params.format = CUBEB_SAMPLE_S16LE;
params.rate = m_sample_rate;
params.channels = m_channels;
params.layout = CUBEB_LAYOUT_UNDEFINED;
params.prefs = CUBEB_STREAM_PREF_NONE;
u32 latency_frames = GetBufferSizeForMS(m_sample_rate, (latency_ms == 0) ? m_buffer_ms : latency_ms);
u32 min_latency_frames = 0;
rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
if (rv == CUBEB_ERROR_NOT_SUPPORTED)
{
Log_DevPrintf("(Cubeb) Cubeb backend does not support latency queries, using latency of %d ms (%u frames).",
m_buffer_ms, latency_frames);
}
else
{
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("(Cubeb) Could not get minimum latency: %d", rv);
DestroyContextAndStream();
return false;
}
const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
Log_DevPrintf("(Cubeb) Minimum latency: %u ms (%u audio frames)", minimum_latency_ms, min_latency_frames);
if (latency_ms == 0)
{
// use minimum
latency_frames = min_latency_frames;
}
else if (minimum_latency_ms > latency_ms)
{
Log_WarningPrintf("(Cubeb) Minimum latency is above requested latency: %u vs %u, adjusting to compensate.",
min_latency_frames, latency_frames);
latency_frames = min_latency_frames;
}
}
cubeb_devid selected_device = nullptr;
const std::string& selected_device_name = g_settings.audio_output_device;
cubeb_device_collection devices;
bool devices_valid = false;
if (!selected_device_name.empty())
{
rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
devices_valid = (rv == CUBEB_OK);
if (rv == CUBEB_OK)
{
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (di.device_id && selected_device_name == di.device_id)
{
Log_InfoPrintf("Using output device '%s' (%s).", di.device_id,
di.friendly_name ? di.friendly_name : di.device_id);
selected_device = di.devid;
break;
}
}
if (!selected_device)
{
Host::AddOSDMessage(
fmt::format("Requested audio output device '{}' not found, using default.", selected_device_name), 10.0f);
}
}
else
{
Log_WarningPrintf("cubeb_enumerate_devices() returned %d, using default device.", rv);
}
}
BaseInitialize();
m_volume = 100;
m_paused = false;
char stream_name[32];
std::snprintf(stream_name, sizeof(stream_name), "%p", this);
rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, &params, latency_frames,
&CubebAudioStream::DataCallback, StateCallback, this);
if (devices_valid)
cubeb_device_collection_destroy(m_context, &devices);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("(Cubeb) Could not create stream: %d", rv);
DestroyContextAndStream();
return false;
}
rv = cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("(Cubeb) Could not start stream: %d", rv);
DestroyContextAndStream();
return false;
}
return true;
}
void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
{
// noop
}
long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes)
{
static_cast<CubebAudioStream*>(user_ptr)->ReadFrames(static_cast<s16*>(output_buffer), static_cast<u32>(nframes));
return nframes;
}
void CubebAudioStream::SetPaused(bool paused)
{
if (paused == m_paused || !stream)
return;
const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("Could not %s stream: %d", paused ? "pause" : "resume", rv);
return;
}
m_paused = paused;
}
void CubebAudioStream::SetOutputVolume(u32 volume)
{
if (volume == m_volume)
return;
int rv = cubeb_stream_set_volume(stream, static_cast<float>(volume) / 100.0f);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_stream_set_volume() failed: %d", rv);
return;
}
m_volume = volume;
}
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
std::unique_ptr<CubebAudioStream> stream(
std::make_unique<CubebAudioStream>(sample_rate, channels, buffer_ms, stretch));
if (!stream->Initialize(latency_ms))
stream.reset();
return stream;
}
std::vector<std::string> AudioStream::GetCubebDriverNames()
{
std::vector<std::string> names;
const char** cubeb_names = cubeb_get_backend_names();
for (u32 i = 0; cubeb_names[i] != nullptr; i++)
names.emplace_back(cubeb_names[i]);
return names;
}
std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebOutputDevices(const char* driver)
{
std::vector<std::pair<std::string, std::string>> ret;
ret.emplace_back(std::string(), Host::TranslateStdString("CommonHost", "Default Output Device"));
cubeb* context;
int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_init() failed: %d", rv);
return ret;
}
ScopedGuard context_cleanup([context]() { cubeb_destroy(context); });
cubeb_device_collection devices;
rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
if (rv != CUBEB_OK)
{
Log_ErrorPrintf("cubeb_enumerate_devices() failed: %d", rv);
return ret;
}
ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
for (size_t i = 0; i < devices.count; i++)
{
const cubeb_device_info& di = devices.device[i];
if (!di.device_id)
continue;
ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
}
return ret;
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "audio_stream.h"
#include <cstdint>
struct cubeb;
struct cubeb_stream;
class CubebAudioStream : public AudioStream
{
public:
CubebAudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
~CubebAudioStream();
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
bool Initialize(u32 latency_ms);
private:
static void LogCallback(const char* fmt, ...);
static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
long nframes);
void DestroyContextAndStream();
cubeb* m_context = nullptr;
cubeb_stream* stream = nullptr;
#ifdef _WIN32
bool m_com_initialized_by_us = false;
#endif
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/d3d11/stream_buffer.h"
#include "common/d3d11/texture.h"
#include "common/timer.h"
#include "common/window_info.h"
#include "common/windows_headers.h"
#include "host_display.h"
#include "postprocessing_chain.h"
#include <d3d11.h>
#include <dxgi.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wrl/client.h>
class D3D11HostDisplay final : public HostDisplay
{
public:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
D3D11HostDisplay();
~D3D11HostDisplay();
RenderAPI GetRenderAPI() const override;
void* GetDevice() const override;
void* GetContext() const override;
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool 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) 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;
bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Format format, const void* data, u32 data_stride,
bool dynamic = false) override;
bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override;
void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override;
bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch) override;
bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
bool GetHostRefreshRate(float* refresh_rate) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
void SetVSync(bool enabled) override;
bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
u32* out_stride, GPUTexture::Format* out_format) override;
static AdapterAndModeList StaticGetAdapterAndModeList();
protected:
static constexpr u32 DISPLAY_UNIFORM_BUFFER_SIZE = 16;
static constexpr u8 NUM_TIMESTAMP_QUERIES = 3;
static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory);
bool CheckStagingBufferSize(u32 width, u32 height, DXGI_FORMAT format);
void DestroyStagingBuffer();
bool CreateResources() override;
void DestroyResources() override;
bool CreateImGuiContext() override;
void DestroyImGuiContext() override;
bool UpdateImGuiFontTexture() override;
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChainRTV();
void RenderDisplay();
void RenderSoftwareCursor();
void RenderImGui();
void RenderDisplay(s32 left, s32 top, s32 width, s32 height, D3D11::Texture* texture, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle);
struct PostProcessingStage
{
ComPtr<ID3D11VertexShader> vertex_shader;
ComPtr<ID3D11PixelShader> pixel_shader;
D3D11::Texture output_texture;
u32 uniforms_size;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(ID3D11RenderTargetView* final_target, s32 final_left, s32 final_top, s32 final_width,
s32 final_height, D3D11::Texture* texture, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height, u32 target_width, u32 target_height);
bool CreateTimestampQueries();
void DestroyTimestampQueries();
void PopTimestampQuery();
void KickTimestampQuery();
ComPtr<ID3D11Device> m_device;
ComPtr<ID3D11DeviceContext> m_context;
ComPtr<IDXGIFactory> m_dxgi_factory;
ComPtr<IDXGISwapChain> m_swap_chain;
ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv;
ComPtr<ID3D11RasterizerState> m_display_rasterizer_state;
ComPtr<ID3D11DepthStencilState> m_display_depth_stencil_state;
ComPtr<ID3D11BlendState> m_display_blend_state;
ComPtr<ID3D11BlendState> m_software_cursor_blend_state;
ComPtr<ID3D11VertexShader> m_display_vertex_shader;
ComPtr<ID3D11PixelShader> m_display_pixel_shader;
ComPtr<ID3D11PixelShader> m_display_alpha_pixel_shader;
ComPtr<ID3D11SamplerState> m_point_sampler;
ComPtr<ID3D11SamplerState> m_linear_sampler;
ComPtr<ID3D11SamplerState> m_border_sampler;
D3D11::StreamBuffer m_display_uniform_buffer;
ComPtr<ID3D11Texture2D> m_readback_staging_texture;
DXGI_FORMAT m_readback_staging_texture_format = DXGI_FORMAT_UNKNOWN;
u32 m_readback_staging_texture_width = 0;
u32 m_readback_staging_texture_height = 0;
bool m_allow_tearing_supported = false;
bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false;
FrontendCommon::PostProcessingChain m_post_processing_chain;
D3D11::Texture m_post_processing_input_texture;
std::vector<PostProcessingStage> m_post_processing_stages;
Common::Timer m_post_processing_timer;
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;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/d3d12/descriptor_heap_manager.h"
#include "common/d3d12/staging_texture.h"
#include "common/d3d12/stream_buffer.h"
#include "common/d3d12/texture.h"
#include "common/timer.h"
#include "common/window_info.h"
#include "common/windows_headers.h"
#include "host_display.h"
#include "postprocessing_chain.h"
#include <d3d12.h>
#include <dxgi.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wrl/client.h>
class D3D12HostDisplay final : public HostDisplay
{
public:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
D3D12HostDisplay();
~D3D12HostDisplay();
RenderAPI GetRenderAPI() const override;
void* GetDevice() const override;
void* GetContext() const override;
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool 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) 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;
bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Format format, const void* data, u32 data_stride,
bool dynamic = false) override;
bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override;
void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override;
bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch) override;
bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
bool GetHostRefreshRate(float* refresh_rate) override;
void SetVSync(bool enabled) override;
bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
static AdapterAndModeList StaticGetAdapterAndModeList();
protected:
struct PostProcessingStage
{
PostProcessingStage() = default;
PostProcessingStage(PostProcessingStage&& move);
~PostProcessingStage();
ComPtr<ID3D12PipelineState> pipeline;
D3D12::Texture output_texture;
u32 uniforms_size = 0;
};
static AdapterAndModeList GetAdapterAndModeList(IDXGIFactory* dxgi_factory);
virtual bool CreateResources() override;
virtual void DestroyResources() override;
virtual bool CreateImGuiContext() override;
virtual void DestroyImGuiContext() override;
virtual bool UpdateImGuiFontTexture() override;
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChainRTV();
void DestroySwapChainRTVs();
void RenderDisplay(ID3D12GraphicsCommandList* cmdlist, D3D12::Texture* swap_chain_buf);
void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist);
void RenderImGui(ID3D12GraphicsCommandList* cmdlist);
void RenderDisplay(ID3D12GraphicsCommandList* cmdlist, s32 left, s32 top, s32 width, s32 height,
D3D12::Texture* texture, s32 texture_view_x, s32 texture_view_y, s32 texture_view_width,
s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(ID3D12GraphicsCommandList* cmdlist, s32 left, s32 top, s32 width, s32 height,
GPUTexture* texture_handle);
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(ID3D12GraphicsCommandList* cmdlist, D3D12::Texture* final_target, s32 final_left,
s32 final_top, s32 final_width, s32 final_height, D3D12::Texture* texture,
s32 texture_view_x, s32 texture_view_y, s32 texture_view_width, s32 texture_view_height,
u32 target_width, u32 target_height);
ComPtr<IDXGIFactory> m_dxgi_factory;
ComPtr<IDXGISwapChain> m_swap_chain;
std::vector<D3D12::Texture> m_swap_chain_buffers;
u32 m_current_swap_chain_buffer = 0;
ComPtr<ID3D12RootSignature> m_display_root_signature;
ComPtr<ID3D12PipelineState> m_display_pipeline;
ComPtr<ID3D12PipelineState> m_software_cursor_pipeline;
D3D12::DescriptorHandle m_point_sampler;
D3D12::DescriptorHandle m_linear_sampler;
D3D12::DescriptorHandle m_border_sampler;
D3D12::Texture m_display_pixels_texture;
D3D12::StagingTexture m_readback_staging_texture;
ComPtr<ID3D12RootSignature> m_post_processing_root_signature;
ComPtr<ID3D12RootSignature> m_post_processing_cb_root_signature;
FrontendCommon::PostProcessingChain m_post_processing_chain;
D3D12::StreamBuffer m_post_processing_cbuffer;
D3D12::Texture m_post_processing_input_texture;
std::vector<PostProcessingStage> m_post_processing_stages;
Common::Timer m_post_processing_timer;
bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false;
};

485
src/util/dinput_source.cpp Normal file
View File

@ -0,0 +1,485 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#define INITGUID
#include "dinput_source.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/make_array.h"
#include "common/string_util.h"
#include "core/host.h"
#include "fmt/format.h"
#include "input_manager.h"
#include <cmath>
#include <limits>
Log_SetChannel(DInputSource);
using PFNDIRECTINPUT8CREATE = HRESULT(WINAPI*)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut,
LPUNKNOWN punkOuter);
using PFNGETDFDIJOYSTICK = LPCDIDATAFORMAT(WINAPI*)();
DInputSource::DInputSource() = default;
DInputSource::~DInputSource()
{
m_controllers.clear();
m_dinput.Reset();
if (m_dinput_module)
FreeLibrary(m_dinput_module);
}
std::array<bool, DInputSource::NUM_HAT_DIRECTIONS> DInputSource::GetHatButtons(DWORD hat)
{
std::array<bool, NUM_HAT_DIRECTIONS> buttons = {};
const WORD hv = LOWORD(hat);
if (hv != 0xFFFF)
{
if ((hv >= 0 && hv < 9000) || hv >= 31500)
buttons[HAT_DIRECTION_UP] = true;
if (hv >= 4500 && hv < 18000)
buttons[HAT_DIRECTION_RIGHT] = true;
if (hv >= 13500 && hv < 27000)
buttons[HAT_DIRECTION_DOWN] = true;
if (hv >= 22500)
buttons[HAT_DIRECTION_LEFT] = true;
}
return buttons;
}
std::string DInputSource::GetDeviceIdentifier(u32 index)
{
return fmt::format("DInput-{}", index);
}
static constexpr std::array<const char*, DInputSource::NUM_HAT_DIRECTIONS> s_hat_directions = {
{"Up", "Down", "Left", "Right"}};
bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
m_dinput_module = LoadLibraryW(L"dinput8");
if (!m_dinput_module)
{
Log_ErrorPrintf("Failed to load DInput module.");
return false;
}
PFNDIRECTINPUT8CREATE create =
reinterpret_cast<PFNDIRECTINPUT8CREATE>(GetProcAddress(m_dinput_module, "DirectInput8Create"));
PFNGETDFDIJOYSTICK get_joystick_data_format =
reinterpret_cast<PFNGETDFDIJOYSTICK>(GetProcAddress(m_dinput_module, "GetdfDIJoystick"));
if (!create || !get_joystick_data_format)
{
Log_ErrorPrintf("Failed to get DInput function pointers.");
return false;
}
HRESULT hr = create(GetModuleHandleA(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W,
reinterpret_cast<LPVOID*>(m_dinput.GetAddressOf()), nullptr);
m_joystick_data_format = get_joystick_data_format();
if (FAILED(hr) || !m_joystick_data_format)
{
Log_ErrorPrintf("DirectInput8Create() failed: %08X", hr);
return false;
}
// need to release the lock while we're enumerating, because we call winId().
settings_lock.unlock();
const std::optional<WindowInfo> toplevel_wi(Host::GetTopLevelWindowInfo());
settings_lock.lock();
if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32)
{
Log_ErrorPrintf("Missing top level window, cannot add DInput devices.");
return false;
}
m_toplevel_window = static_cast<HWND>(toplevel_wi->window_handle);
ReloadDevices();
return true;
}
void DInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
// noop
}
static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef)
{
static_cast<std::vector<DIDEVICEINSTANCEW>*>(pvRef)->push_back(*lpddi);
return DIENUM_CONTINUE;
}
bool DInputSource::ReloadDevices()
{
// detect any removals
PollEvents();
// look for new devices
std::vector<DIDEVICEINSTANCEW> devices;
m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY);
Log_VerbosePrintf("Enumerated %zu devices", devices.size());
bool changed = false;
for (DIDEVICEINSTANCEW inst : devices)
{
// do we already have this one?
if (std::any_of(m_controllers.begin(), m_controllers.end(),
[&inst](const ControllerData& cd) { return inst.guidInstance == cd.guid; }))
{
// yup, so skip it
continue;
}
ControllerData cd;
cd.guid = inst.guidInstance;
HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr);
if (FAILED(hr))
{
Log_WarningPrintf("Failed to create instance of device [%s, %s]", inst.tszProductName, inst.tszInstanceName);
continue;
}
const std::string name(StringUtil::WideStringToUTF8String(inst.tszProductName));
if (AddDevice(cd, name))
{
const u32 index = static_cast<u32>(m_controllers.size());
m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(GetDeviceIdentifier(index), name);
changed = true;
}
}
return changed;
}
void DInputSource::Shutdown()
{
while (!m_controllers.empty())
{
InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1)));
m_controllers.pop_back();
}
}
bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
{
HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
if (FAILED(hr))
{
hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(hr))
{
Log_ErrorPrintf("Failed to set cooperative level for '%s'", name.c_str());
return false;
}
Log_WarningPrintf("Failed to set exclusive mode for '%s'", name.c_str());
}
hr = cd.device->SetDataFormat(m_joystick_data_format);
if (FAILED(hr))
{
Log_ErrorPrintf("Failed to set data format for '%s'", name.c_str());
return false;
}
hr = cd.device->Acquire();
if (FAILED(hr))
{
Log_ErrorPrintf("Failed to acquire device '%s'", name.c_str());
return false;
}
DIDEVCAPS caps = {};
caps.dwSize = sizeof(caps);
hr = cd.device->GetCapabilities(&caps);
if (FAILED(hr))
{
Log_ErrorPrintf("Failed to get capabilities for '%s'", name.c_str());
return false;
}
cd.num_buttons = caps.dwButtons;
static constexpr const u32 axis_offsets[] = {offsetof(DIJOYSTATE, lX), offsetof(DIJOYSTATE, lY),
offsetof(DIJOYSTATE, lZ), offsetof(DIJOYSTATE, lRz),
offsetof(DIJOYSTATE, lRx), offsetof(DIJOYSTATE, lRy),
offsetof(DIJOYSTATE, rglSlider[0]), offsetof(DIJOYSTATE, rglSlider[1])};
for (const u32 offset : axis_offsets)
{
// ask for 16 bits of axis range
DIPROPRANGE range = {};
range.diph.dwSize = sizeof(range);
range.diph.dwHeaderSize = sizeof(range.diph);
range.diph.dwHow = DIPH_BYOFFSET;
range.diph.dwObj = static_cast<DWORD>(offset);
range.lMin = std::numeric_limits<s16>::min();
range.lMax = std::numeric_limits<s16>::max();
hr = cd.device->SetProperty(DIPROP_RANGE, &range.diph);
// did it apply?
if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph)))
cd.axis_offsets.push_back(offset);
}
cd.num_hats = caps.dwPOVs;
hr = cd.device->Poll();
if (hr == DI_NOEFFECT)
cd.needs_poll = false;
else if (hr != DI_OK)
Log_WarningPrintf("Polling device '%s' failed: %08X", name.c_str(), hr);
hr = cd.device->GetDeviceState(sizeof(cd.last_state), &cd.last_state);
if (hr != DI_OK)
Log_WarningPrintf("GetDeviceState() for '%s' failed: %08X", name.c_str(), hr);
Log_InfoPrintf("%s has %u buttons, %u axes, %u hats", name.c_str(), cd.num_buttons,
static_cast<u32>(cd.axis_offsets.size()), cd.num_hats);
return (cd.num_buttons > 0 || !cd.axis_offsets.empty() || cd.num_hats > 0);
}
void DInputSource::PollEvents()
{
for (size_t i = 0; i < m_controllers.size();)
{
ControllerData& cd = m_controllers[i];
if (cd.needs_poll)
cd.device->Poll();
DIJOYSTATE js;
HRESULT hr = cd.device->GetDeviceState(sizeof(js), &js);
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
hr = cd.device->Acquire();
if (hr == DI_OK)
hr = cd.device->GetDeviceState(sizeof(js), &js);
if (hr != DI_OK)
{
InputManager::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast<u32>(i)));
m_controllers.erase(m_controllers.begin() + i);
continue;
}
}
else if (hr != DI_OK)
{
Log_WarningPrintf("GetDeviceState() failed: %08X", hr);
i++;
continue;
}
CheckForStateChanges(i, js);
i++;
}
}
std::vector<std::pair<std::string, std::string>> DInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
for (size_t i = 0; i < m_controllers.size(); i++)
{
DIDEVICEINSTANCEW dii = {sizeof(DIDEVICEINSTANCEW)};
std::string name;
if (SUCCEEDED(m_controllers[i].device->GetDeviceInfo(&dii)))
name = StringUtil::WideStringToUTF8String(dii.tszProductName);
if (name.empty())
name = "Unknown";
ret.emplace_back(GetDeviceIdentifier(static_cast<u32>(i)), std::move(name));
}
return ret;
}
std::vector<InputBindingKey> DInputSource::EnumerateMotors()
{
return {};
}
bool DInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping)
{
return {};
}
void DInputSource::UpdateMotorState(InputBindingKey key, float intensity)
{
// not supported
}
void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity)
{
// not supported
}
std::optional<InputBindingKey> DInputSource::ParseKeyString(const std::string_view& device,
const std::string_view& binding)
{
if (!StringUtil::StartsWith(device, "DInput-") || binding.empty())
return std::nullopt;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7));
if (!player_id.has_value() || player_id.value() < 0)
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::DInput;
key.source_index = static_cast<u32>(player_id.value());
if (StringUtil::StartsWith(binding, "+Axis") || StringUtil::StartsWith(binding, "-Axis"))
{
std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5), 10, &end);
if (!axis_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value();
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
else if (StringUtil::StartsWith(binding, "FullAxis"))
{
std::string_view end;
const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(8), 10, &end);
if (!axis_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = axis_index.value();
key.modifier = InputModifier::FullAxis;
key.invert = (end == "~");
return key;
}
else if (StringUtil::StartsWith(binding, "Hat"))
{
if (binding[3] < '0' || binding[3] > '9' || binding.length() < 5)
return std::nullopt;
const u32 hat_index = binding[3] - '0';
const std::string_view hat_dir(binding.substr(4));
for (u32 i = 0; i < NUM_HAT_DIRECTIONS; i++)
{
if (hat_dir == s_hat_directions[i])
{
key.source_subtype = InputSubclass::ControllerButton;
key.data = MAX_NUM_BUTTONS + hat_index * NUM_HAT_DIRECTIONS + i;
return key;
}
}
// bad direction
return std::nullopt;
}
else if (StringUtil::StartsWith(binding, "Button"))
{
const std::optional<u32> button_index = StringUtil::FromChars<u32>(binding.substr(6));
if (!button_index.has_value())
return std::nullopt;
key.source_subtype = InputSubclass::ControllerButton;
key.data = button_index.value();
return key;
}
// unknown axis/button
return std::nullopt;
}
std::string DInputSource::ConvertKeyToString(InputBindingKey key)
{
std::string ret;
if (key.source_type == InputSourceType::DInput)
{
if (key.source_subtype == InputSubclass::ControllerAxis)
{
const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
ret = fmt::format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), key.invert ? "~" : "");
}
else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS)
{
const u32 hat_num = (key.data - MAX_NUM_BUTTONS) / NUM_HAT_DIRECTIONS;
const u32 hat_dir = (key.data - MAX_NUM_BUTTONS) % NUM_HAT_DIRECTIONS;
ret = fmt::format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]);
}
else if (key.source_subtype == InputSubclass::ControllerButton)
{
ret = fmt::format("DInput-{}/Button{}", u32(key.source_index), u32(key.data));
}
}
return ret;
}
void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state)
{
ControllerData& cd = m_controllers[index];
DIJOYSTATE& last_state = cd.last_state;
for (size_t i = 0; i < cd.axis_offsets.size(); i++)
{
LONG new_value;
LONG old_value;
std::memcpy(&old_value, reinterpret_cast<const u8*>(&cd.last_state) + cd.axis_offsets[i], sizeof(old_value));
std::memcpy(&new_value, reinterpret_cast<const u8*>(&new_state) + cd.axis_offsets[i], sizeof(new_value));
if (old_value != new_value)
{
std::memcpy(reinterpret_cast<u8*>(&cd.last_state) + cd.axis_offsets[i], &new_value, sizeof(new_value));
// TODO: Use the range from caps?
const float value = static_cast<float>(new_value) / (new_value < 0 ? 32768.0f : 32767.0f);
InputManager::InvokeEvents(
MakeGenericControllerAxisKey(InputSourceType::DInput, static_cast<u32>(index), static_cast<u32>(i)), value,
GenericInputBinding::Unknown);
}
}
for (u32 i = 0; i < cd.num_buttons; i++)
{
if (last_state.rgbButtons[i] != new_state.rgbButtons[i])
{
last_state.rgbButtons[i] = new_state.rgbButtons[i];
const float value = (new_state.rgbButtons[i] != 0) ? 1.0f : 0.0f;
InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index), i),
value, GenericInputBinding::Unknown);
}
}
for (u32 i = 0; i < cd.num_hats; i++)
{
if (last_state.rgdwPOV[i] != new_state.rgdwPOV[i])
{
// map hats to the last buttons
const std::array<bool, NUM_HAT_DIRECTIONS> old_buttons(GetHatButtons(last_state.rgdwPOV[i]));
const std::array<bool, NUM_HAT_DIRECTIONS> new_buttons(GetHatButtons(new_state.rgdwPOV[i]));
last_state.rgdwPOV[i] = new_state.rgdwPOV[i];
for (u32 j = 0; j < NUM_HAT_DIRECTIONS; j++)
{
if (old_buttons[j] != new_buttons[j])
{
const float value = (new_buttons[j] ? 1.0f : 0.0f);
InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index),
cd.num_buttons + (i * NUM_HAT_DIRECTIONS) + j),
value, GenericInputBinding::Unknown);
}
}
}
}
}
std::unique_ptr<InputSource> InputSource::CreateDInputSource()
{
return std::make_unique<DInputSource>();
}

86
src/util/dinput_source.h Normal file
View File

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#define DIRECTINPUT_VERSION 0x0800
#include "common/windows_headers.h"
#include "core/types.h"
#include "input_source.h"
#include <array>
#include <dinput.h>
#include <functional>
#include <mutex>
#include <vector>
#include <wrl/client.h>
class DInputSource final : public InputSource
{
public:
enum HAT_DIRECTION : u32
{
HAT_DIRECTION_UP = 0,
HAT_DIRECTION_DOWN = 1,
HAT_DIRECTION_LEFT = 2,
HAT_DIRECTION_RIGHT = 3,
NUM_HAT_DIRECTIONS = 4,
};
enum : u32
{
MAX_NUM_BUTTONS = 32,
};
DInputSource();
~DInputSource() override;
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
const std::string_view& binding) override;
std::string ConvertKeyToString(InputBindingKey key) override;
private:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
struct ControllerData
{
ComPtr<IDirectInputDevice8W> device;
DIJOYSTATE last_state = {};
GUID guid = {};
std::vector<u32> axis_offsets;
u32 num_buttons = 0;
// NOTE: We expose hats as num_buttons + (hat_index * 4) + direction.
u32 num_hats = 0;
bool needs_poll = true;
};
using ControllerDataArray = std::vector<ControllerData>;
static std::array<bool, NUM_HAT_DIRECTIONS> GetHatButtons(DWORD hat);
static std::string GetDeviceIdentifier(u32 index);
bool AddDevice(ControllerData& cd, const std::string& name);
void CheckForStateChanges(size_t index, const DIJOYSTATE& new_state);
ControllerDataArray m_controllers;
HMODULE m_dinput_module{};
ComPtr<IDirectInput8W> m_dinput;
LPCDIDATAFORMAT m_joystick_data_format{};
HWND m_toplevel_window = NULL;
};

12
src/util/display_ps.hlsl Normal file
View File

@ -0,0 +1,12 @@
Texture2D samp0 : register(t0);
SamplerState samp0_ss : register(s0);
void main(in float2 v_tex0 : TEXCOORD0,
out float4 o_col0 : SV_Target)
{
#ifdef ALPHA
o_col0 = samp0.Sample(samp0_ss, v_tex0);
#else
o_col0 = float4(samp0.Sample(samp0_ss, v_tex0).rgb, 1.0);
#endif
}

142
src/util/display_ps.hlsl.h Normal file
View File

@ -0,0 +1,142 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// samp0_ss sampler NA NA s0 1
// samp0 texture float4 2d t0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// TEXCOORD 0 xy 0 NONE float xy
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_Target 0 xyzw 0 TARGET float xyzw
//
ps_4_0
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_input_ps linear v0.xy
dcl_output o0.xyzw
dcl_temps 1
sample r0.xyzw, v0.xyxx, t0.xyzw, s0
mov o0.xyz, r0.xyzx
mov o0.w, l(1.000000)
ret
// Approximately 4 instruction slots used
#endif
const BYTE static s_display_ps_bytecode[] =
{
68, 88, 66, 67, 192, 215,
150, 96, 210, 93, 209, 128,
113, 254, 100, 56, 49, 113,
128, 72, 1, 0, 0, 0,
80, 2, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
208, 0, 0, 0, 4, 1,
0, 0, 56, 1, 0, 0,
212, 1, 0, 0, 82, 68,
69, 70, 148, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
28, 0, 0, 0, 0, 4,
255, 255, 0, 129, 0, 0,
107, 0, 0, 0, 92, 0,
0, 0, 3, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0,
101, 0, 0, 0, 2, 0,
0, 0, 5, 0, 0, 0,
4, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
1, 0, 0, 0, 13, 0,
0, 0, 115, 97, 109, 112,
48, 95, 115, 115, 0, 115,
97, 109, 112, 48, 0, 77,
105, 99, 114, 111, 115, 111,
102, 116, 32, 40, 82, 41,
32, 72, 76, 83, 76, 32,
83, 104, 97, 100, 101, 114,
32, 67, 111, 109, 112, 105,
108, 101, 114, 32, 49, 48,
46, 49, 0, 171, 73, 83,
71, 78, 44, 0, 0, 0,
1, 0, 0, 0, 8, 0,
0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 0,
0, 0, 0, 0, 3, 3,
0, 0, 84, 69, 88, 67,
79, 79, 82, 68, 0, 171,
171, 171, 79, 83, 71, 78,
44, 0, 0, 0, 1, 0,
0, 0, 8, 0, 0, 0,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0,
83, 86, 95, 84, 97, 114,
103, 101, 116, 0, 171, 171,
83, 72, 68, 82, 148, 0,
0, 0, 64, 0, 0, 0,
37, 0, 0, 0, 90, 0,
0, 3, 0, 96, 16, 0,
0, 0, 0, 0, 88, 24,
0, 4, 0, 112, 16, 0,
0, 0, 0, 0, 85, 85,
0, 0, 98, 16, 0, 3,
50, 16, 16, 0, 0, 0,
0, 0, 101, 0, 0, 3,
242, 32, 16, 0, 0, 0,
0, 0, 104, 0, 0, 2,
1, 0, 0, 0, 69, 0,
0, 9, 242, 0, 16, 0,
0, 0, 0, 0, 70, 16,
16, 0, 0, 0, 0, 0,
70, 126, 16, 0, 0, 0,
0, 0, 0, 96, 16, 0,
0, 0, 0, 0, 54, 0,
0, 5, 114, 32, 16, 0,
0, 0, 0, 0, 70, 2,
16, 0, 0, 0, 0, 0,
54, 0, 0, 5, 130, 32,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 0, 0,
128, 63, 62, 0, 0, 1,
83, 84, 65, 84, 116, 0,
0, 0, 4, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
2, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0
};

View File

@ -0,0 +1,131 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// samp0_ss sampler NA NA s0 1
// samp0 texture float4 2d t0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// TEXCOORD 0 xy 0 NONE float xy
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_Target 0 xyzw 0 TARGET float xyzw
//
ps_4_0
dcl_sampler s0, mode_default
dcl_resource_texture2d (float,float,float,float) t0
dcl_input_ps linear v0.xy
dcl_output o0.xyzw
sample o0.xyzw, v0.xyxx, t0.xyzw, s0
ret
// Approximately 2 instruction slots used
#endif
const BYTE static s_display_ps_alpha_bytecode[] =
{
68, 88, 66, 67, 140, 134,
46, 29, 68, 36, 193, 23,
94, 171, 102, 123, 183, 66,
19, 177, 1, 0, 0, 0,
32, 2, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
208, 0, 0, 0, 4, 1,
0, 0, 56, 1, 0, 0,
164, 1, 0, 0, 82, 68,
69, 70, 148, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
28, 0, 0, 0, 0, 4,
255, 255, 0, 129, 0, 0,
107, 0, 0, 0, 92, 0,
0, 0, 3, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0,
101, 0, 0, 0, 2, 0,
0, 0, 5, 0, 0, 0,
4, 0, 0, 0, 255, 255,
255, 255, 0, 0, 0, 0,
1, 0, 0, 0, 13, 0,
0, 0, 115, 97, 109, 112,
48, 95, 115, 115, 0, 115,
97, 109, 112, 48, 0, 77,
105, 99, 114, 111, 115, 111,
102, 116, 32, 40, 82, 41,
32, 72, 76, 83, 76, 32,
83, 104, 97, 100, 101, 114,
32, 67, 111, 109, 112, 105,
108, 101, 114, 32, 49, 48,
46, 49, 0, 171, 73, 83,
71, 78, 44, 0, 0, 0,
1, 0, 0, 0, 8, 0,
0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 0,
0, 0, 0, 0, 3, 3,
0, 0, 84, 69, 88, 67,
79, 79, 82, 68, 0, 171,
171, 171, 79, 83, 71, 78,
44, 0, 0, 0, 1, 0,
0, 0, 8, 0, 0, 0,
32, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0,
83, 86, 95, 84, 97, 114,
103, 101, 116, 0, 171, 171,
83, 72, 68, 82, 100, 0,
0, 0, 64, 0, 0, 0,
25, 0, 0, 0, 90, 0,
0, 3, 0, 96, 16, 0,
0, 0, 0, 0, 88, 24,
0, 4, 0, 112, 16, 0,
0, 0, 0, 0, 85, 85,
0, 0, 98, 16, 0, 3,
50, 16, 16, 0, 0, 0,
0, 0, 101, 0, 0, 3,
242, 32, 16, 0, 0, 0,
0, 0, 69, 0, 0, 9,
242, 32, 16, 0, 0, 0,
0, 0, 70, 16, 16, 0,
0, 0, 0, 0, 70, 126,
16, 0, 0, 0, 0, 0,
0, 96, 16, 0, 0, 0,
0, 0, 62, 0, 0, 1,
83, 84, 65, 84, 116, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0
};

13
src/util/display_vs.hlsl Normal file
View File

@ -0,0 +1,13 @@
cbuffer UBOBlock : register(b0)
{
float4 u_src_rect;
};
void main(in uint vertex_id : SV_VertexID,
out float2 v_tex0 : TEXCOORD0,
out float4 o_pos : SV_Position)
{
float2 pos = float2(float((vertex_id << 1) & 2u), float(vertex_id & 2u));
v_tex0 = u_src_rect.xy + pos * u_src_rect.zw;
o_pos = float4(pos * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
}

197
src/util/display_vs.hlsl.h Normal file
View File

@ -0,0 +1,197 @@
#if 0
//
// Generated by Microsoft (R) HLSL Shader Compiler 10.1
//
//
// Buffer Definitions:
//
// cbuffer UBOBlock
// {
//
// float4 u_src_rect; // Offset: 0 Size: 16
//
// }
//
//
// Resource Bindings:
//
// Name Type Format Dim HLSL Bind Count
// ------------------------------ ---------- ------- ----------- -------------- ------
// UBOBlock cbuffer NA NA cb0 1
//
//
//
// Input signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// SV_VertexID 0 x 0 VERTID uint x
//
//
// Output signature:
//
// Name Index Mask Register SysValue Format Used
// -------------------- ----- ------ -------- -------- ------- ------
// TEXCOORD 0 xy 0 NONE float xy
// SV_Position 0 xyzw 1 POS float xyzw
//
vs_4_0
dcl_constantbuffer CB0[1], immediateIndexed
dcl_input_sgv v0.x, vertex_id
dcl_output o0.xy
dcl_output_siv o1.xyzw, position
dcl_temps 1
ishl r0.x, v0.x, l(1)
and r0.x, r0.x, l(2)
and r0.z, v0.x, l(2)
utof r0.xy, r0.xzxx
mad o0.xy, r0.xyxx, cb0[0].zwzz, cb0[0].xyxx
mad o1.xy, r0.xyxx, l(2.000000, -2.000000, 0.000000, 0.000000), l(-1.000000, 1.000000, 0.000000, 0.000000)
mov o1.zw, l(0,0,0,1.000000)
ret
// Approximately 8 instruction slots used
#endif
const BYTE static s_display_vs_bytecode[] =
{
68, 88, 66, 67, 37, 97,
157, 234, 112, 10, 38, 98,
114, 228, 143, 118, 71, 158,
122, 195, 1, 0, 0, 0,
72, 3, 0, 0, 5, 0,
0, 0, 52, 0, 0, 0,
248, 0, 0, 0, 44, 1,
0, 0, 132, 1, 0, 0,
204, 2, 0, 0, 82, 68,
69, 70, 188, 0, 0, 0,
1, 0, 0, 0, 72, 0,
0, 0, 1, 0, 0, 0,
28, 0, 0, 0, 0, 4,
254, 255, 0, 129, 0, 0,
148, 0, 0, 0, 60, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0,
85, 66, 79, 66, 108, 111,
99, 107, 0, 171, 171, 171,
60, 0, 0, 0, 1, 0,
0, 0, 96, 0, 0, 0,
16, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
120, 0, 0, 0, 0, 0,
0, 0, 16, 0, 0, 0,
2, 0, 0, 0, 132, 0,
0, 0, 0, 0, 0, 0,
117, 95, 115, 114, 99, 95,
114, 101, 99, 116, 0, 171,
1, 0, 3, 0, 1, 0,
4, 0, 0, 0, 0, 0,
0, 0, 0, 0, 77, 105,
99, 114, 111, 115, 111, 102,
116, 32, 40, 82, 41, 32,
72, 76, 83, 76, 32, 83,
104, 97, 100, 101, 114, 32,
67, 111, 109, 112, 105, 108,
101, 114, 32, 49, 48, 46,
49, 0, 73, 83, 71, 78,
44, 0, 0, 0, 1, 0,
0, 0, 8, 0, 0, 0,
32, 0, 0, 0, 0, 0,
0, 0, 6, 0, 0, 0,
1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 0, 0,
83, 86, 95, 86, 101, 114,
116, 101, 120, 73, 68, 0,
79, 83, 71, 78, 80, 0,
0, 0, 2, 0, 0, 0,
8, 0, 0, 0, 56, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 3, 0,
0, 0, 0, 0, 0, 0,
3, 12, 0, 0, 65, 0,
0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 3, 0,
0, 0, 1, 0, 0, 0,
15, 0, 0, 0, 84, 69,
88, 67, 79, 79, 82, 68,
0, 83, 86, 95, 80, 111,
115, 105, 116, 105, 111, 110,
0, 171, 171, 171, 83, 72,
68, 82, 64, 1, 0, 0,
64, 0, 1, 0, 80, 0,
0, 0, 89, 0, 0, 4,
70, 142, 32, 0, 0, 0,
0, 0, 1, 0, 0, 0,
96, 0, 0, 4, 18, 16,
16, 0, 0, 0, 0, 0,
6, 0, 0, 0, 101, 0,
0, 3, 50, 32, 16, 0,
0, 0, 0, 0, 103, 0,
0, 4, 242, 32, 16, 0,
1, 0, 0, 0, 1, 0,
0, 0, 104, 0, 0, 2,
1, 0, 0, 0, 41, 0,
0, 7, 18, 0, 16, 0,
0, 0, 0, 0, 10, 16,
16, 0, 0, 0, 0, 0,
1, 64, 0, 0, 1, 0,
0, 0, 1, 0, 0, 7,
18, 0, 16, 0, 0, 0,
0, 0, 10, 0, 16, 0,
0, 0, 0, 0, 1, 64,
0, 0, 2, 0, 0, 0,
1, 0, 0, 7, 66, 0,
16, 0, 0, 0, 0, 0,
10, 16, 16, 0, 0, 0,
0, 0, 1, 64, 0, 0,
2, 0, 0, 0, 86, 0,
0, 5, 50, 0, 16, 0,
0, 0, 0, 0, 134, 0,
16, 0, 0, 0, 0, 0,
50, 0, 0, 11, 50, 32,
16, 0, 0, 0, 0, 0,
70, 0, 16, 0, 0, 0,
0, 0, 230, 138, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 70, 128, 32, 0,
0, 0, 0, 0, 0, 0,
0, 0, 50, 0, 0, 15,
50, 32, 16, 0, 1, 0,
0, 0, 70, 0, 16, 0,
0, 0, 0, 0, 2, 64,
0, 0, 0, 0, 0, 64,
0, 0, 0, 192, 0, 0,
0, 0, 0, 0, 0, 0,
2, 64, 0, 0, 0, 0,
128, 191, 0, 0, 128, 63,
0, 0, 0, 0, 0, 0,
0, 0, 54, 0, 0, 8,
194, 32, 16, 0, 1, 0,
0, 0, 2, 64, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 128, 63, 62, 0,
0, 1, 83, 84, 65, 84,
116, 0, 0, 0, 8, 0,
0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 3, 0,
0, 0, 2, 0, 0, 0,
1, 0, 0, 0, 2, 0,
0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};

702
src/util/host_display.cpp Normal file
View File

@ -0,0 +1,702 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "host_display.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "core/settings.h" // TODO FIXME
#include "stb_image.h"
#include "stb_image_resize.h"
#include "stb_image_write.h"
#include <cerrno>
#include <cmath>
#include <cstring>
#include <thread>
#include <vector>
Log_SetChannel(HostDisplay);
std::unique_ptr<HostDisplay> g_host_display;
HostDisplay::~HostDisplay() = default;
RenderAPI HostDisplay::GetPreferredAPI()
{
#ifdef _WIN32
return RenderAPI::D3D11;
#else
return RenderAPI::OpenGL;
#endif
}
void HostDisplay::DestroyResources()
{
m_cursor_texture.reset();
}
bool HostDisplay::UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch)
{
void* map_ptr;
u32 map_pitch;
if (!BeginTextureUpdate(texture, width, height, &map_ptr, &map_pitch))
return false;
StringUtil::StrideMemCpy(map_ptr, map_pitch, data, pitch, std::min(pitch, map_pitch), height);
EndTextureUpdate(texture, x, y, width, height);
return true;
}
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);
}
bool HostDisplay::UsesLowerLeftOrigin() const
{
const RenderAPI api = GetRenderAPI();
return (api == RenderAPI::OpenGL || api == RenderAPI::OpenGLES);
}
void HostDisplay::SetDisplayMaxFPS(float max_fps)
{
m_display_frame_interval = (max_fps > 0.0f) ? (1.0f / max_fps) : 0.0f;
}
bool HostDisplay::ShouldSkipDisplayingFrame()
{
if (m_display_frame_interval == 0.0f)
return false;
const u64 now = Common::Timer::GetCurrentValue();
const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time);
if (diff < m_display_frame_interval)
return true;
m_last_frame_displayed_time = now;
return false;
}
void HostDisplay::ThrottlePresentation()
{
const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f;
const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast<double>(throttle_rate));
const u64 current_ts = Common::Timer::GetCurrentValue();
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
// allow time for the actual rendering.
const u64 max_variance = sleep_period * 2;
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - m_last_frame_displayed_time))) > max_variance)
m_last_frame_displayed_time = current_ts + sleep_period;
else
m_last_frame_displayed_time += sleep_period;
Common::Timer::SleepUntil(m_last_frame_displayed_time, false);
}
bool 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;
}
void HostDisplay::SetSoftwareCursor(std::unique_ptr<GPUTexture> texture, float scale /*= 1.0f*/)
{
m_cursor_texture = std::move(texture);
m_cursor_texture_scale = scale;
}
bool HostDisplay::SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale /*= 1.0f*/)
{
std::unique_ptr<GPUTexture> tex =
CreateTexture(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, pixels, stride, false);
if (!tex)
return false;
SetSoftwareCursor(std::move(tex), scale);
return true;
}
bool HostDisplay::SetSoftwareCursor(const char* path, float scale /*= 1.0f*/)
{
auto fp = FileSystem::OpenManagedCFile(path, "rb");
if (!fp)
{
return false;
}
int width, height, file_channels;
u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4);
if (!pixel_data)
{
const char* error_reason = stbi_failure_reason();
Log_ErrorPrintf("Failed to load image from '%s': %s", path, error_reason ? error_reason : "unknown error");
return false;
}
std::unique_ptr<GPUTexture> tex =
CreateTexture(static_cast<u32>(width), static_cast<u32>(height), 1, 1, 1, GPUTexture::Format::RGBA8, pixel_data,
sizeof(u32) * static_cast<u32>(width), false);
stbi_image_free(pixel_data);
if (!tex)
return false;
Log_InfoPrintf("Loaded %dx%d image from '%s' for software cursor", width, height, path);
SetSoftwareCursor(std::move(tex), scale);
return true;
}
void HostDisplay::ClearSoftwareCursor()
{
m_cursor_texture.reset();
m_cursor_texture_scale = 1.0f;
}
bool HostDisplay::IsUsingLinearFiltering() const
{
return g_settings.display_linear_filtering;
}
void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top,
float* out_width, float* out_height, float* out_left_padding,
float* out_top_padding, float* out_scale, float* out_x_scale,
bool apply_aspect_ratio /* = true */) const
{
const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
const float display_aspect_ratio = g_settings.display_stretch ? window_ratio : m_display_aspect_ratio;
const float x_scale =
apply_aspect_ratio ?
(display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
1.0f;
const float display_width = g_settings.display_stretch_vertically ? static_cast<float>(m_display_width) :
static_cast<float>(m_display_width) * x_scale;
const float display_height = g_settings.display_stretch_vertically ? static_cast<float>(m_display_height) / x_scale :
static_cast<float>(m_display_height);
const float active_left = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_left) :
static_cast<float>(m_display_active_left) * x_scale;
const float active_top = g_settings.display_stretch_vertically ? static_cast<float>(m_display_active_top) / x_scale :
static_cast<float>(m_display_active_top);
const float active_width = g_settings.display_stretch_vertically ?
static_cast<float>(m_display_active_width) :
static_cast<float>(m_display_active_width) * x_scale;
const float active_height = g_settings.display_stretch_vertically ?
static_cast<float>(m_display_active_height) / x_scale :
static_cast<float>(m_display_active_height);
if (out_x_scale)
*out_x_scale = x_scale;
// now fit it within the window
float scale;
if ((display_width / display_height) >= window_ratio)
{
// align in middle vertically
scale = static_cast<float>(window_width) / display_width;
if (g_settings.display_integer_scaling)
scale = std::max(std::floor(scale), 1.0f);
if (out_left_padding)
{
if (g_settings.display_integer_scaling)
*out_left_padding = std::max<float>((static_cast<float>(window_width) - display_width * scale) / 2.0f, 0.0f);
else
*out_left_padding = 0.0f;
}
if (out_top_padding)
{
switch (g_settings.display_alignment)
{
case DisplayAlignment::RightOrBottom:
*out_top_padding = std::max<float>(static_cast<float>(window_height) - (display_height * scale), 0.0f);
break;
case DisplayAlignment::Center:
*out_top_padding =
std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
break;
case DisplayAlignment::LeftOrTop:
default:
*out_top_padding = 0.0f;
break;
}
}
}
else
{
// align in middle horizontally
scale = static_cast<float>(window_height) / display_height;
if (g_settings.display_integer_scaling)
scale = std::max(std::floor(scale), 1.0f);
if (out_left_padding)
{
switch (g_settings.display_alignment)
{
case DisplayAlignment::RightOrBottom:
*out_left_padding = std::max<float>(static_cast<float>(window_width) - (display_width * scale), 0.0f);
break;
case DisplayAlignment::Center:
*out_left_padding =
std::max<float>((static_cast<float>(window_width) - (display_width * scale)) / 2.0f, 0.0f);
break;
case DisplayAlignment::LeftOrTop:
default:
*out_left_padding = 0.0f;
break;
}
}
if (out_top_padding)
{
if (g_settings.display_integer_scaling)
*out_top_padding = std::max<float>((static_cast<float>(window_height) - (display_height * scale)) / 2.0f, 0.0f);
else
*out_top_padding = 0.0f;
}
}
*out_width = active_width * scale;
*out_height = active_height * scale;
*out_left = active_left * scale;
*out_top = active_top * scale;
if (out_scale)
*out_scale = scale;
}
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height,
bool apply_aspect_ratio /* = true */) const
{
float left, top, width, height, left_padding, top_padding;
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, nullptr,
nullptr, apply_aspect_ratio);
return std::make_tuple(static_cast<s32>(left + left_padding), static_cast<s32>(top + top_padding),
static_cast<s32>(width), static_cast<s32>(height));
}
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect() const
{
return CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y);
}
std::tuple<s32, s32, s32, s32> HostDisplay::CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const
{
const float scale = m_window_info.surface_scale * m_cursor_texture_scale;
const u32 cursor_extents_x = static_cast<u32>(static_cast<float>(m_cursor_texture->GetWidth()) * scale * 0.5f);
const u32 cursor_extents_y = static_cast<u32>(static_cast<float>(m_cursor_texture->GetHeight()) * scale * 0.5f);
const s32 out_left = cursor_x - cursor_extents_x;
const s32 out_top = cursor_y - cursor_extents_y;
const s32 out_width = cursor_extents_x * 2u;
const s32 out_height = cursor_extents_y * 2u;
return std::tie(out_left, out_top, out_width, out_height);
}
std::tuple<float, float> HostDisplay::ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y,
s32 window_width,
s32 window_height) const
{
float left, top, width, height, left_padding, top_padding;
float scale, x_scale;
CalculateDrawRect(window_width, window_height, &left, &top, &width, &height, &left_padding, &top_padding, &scale,
&x_scale);
// convert coordinates to active display region, then to full display region
const float scaled_display_x = static_cast<float>(window_x) - left_padding;
const float scaled_display_y = static_cast<float>(window_y) - top_padding;
// scale back to internal resolution
const float display_x = scaled_display_x / scale / x_scale;
const float display_y = scaled_display_y / scale;
return std::make_tuple(display_x, display_y);
}
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
std::vector<u32> texture_data, u32 texture_data_stride,
GPUTexture::Format texture_format)
{
const char* extension = std::strrchr(filename.c_str(), '.');
if (!extension)
{
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
return false;
}
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
return false;
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000;
}
if (flip_y)
GPUTexture::FlipTextureDataRGBA8(width, height, texture_data, texture_data_stride);
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
{
std::vector<u32> resized_texture_data(resize_width * resize_height);
u32 resized_texture_stride = sizeof(u32) * resize_width;
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
resized_texture_stride, 4))
{
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
return false;
}
width = resize_width;
height = resize_height;
texture_data = std::move(resized_texture_data);
texture_data_stride = resized_texture_stride;
}
const auto write_func = [](void* context, void* data, int size) {
std::fwrite(data, 1, size, static_cast<std::FILE*>(context));
};
bool result = false;
if (StringUtil::Strcasecmp(extension, ".png") == 0)
{
result =
(stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0);
}
else if (StringUtil::Strcasecmp(extension, ".jpg") == 0)
{
result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), 95) != 0);
}
else if (StringUtil::Strcasecmp(extension, ".tga") == 0)
{
result = (stbi_write_tga_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
}
else if (StringUtil::Strcasecmp(extension, ".bmp") == 0)
{
result = (stbi_write_bmp_to_func(write_func, fp.get(), width, height, 4, texture_data.data()) != 0);
}
if (!result)
{
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
return false;
}
return true;
}
bool HostDisplay::WriteTextureToFile(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, std::string filename,
bool clear_alpha /* = true */, bool flip_y /* = false */,
u32 resize_width /* = 0 */, u32 resize_height /* = 0 */,
bool compress_on_thread /* = false */)
{
std::vector<u32> texture_data(width * height);
u32 texture_data_stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(texture->GetFormat()) * width, 4);
if (!DownloadTexture(texture, x, y, width, height, texture_data.data(), texture_data_stride))
{
Log_ErrorPrintf("Texture download failed");
return false;
}
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
if (!fp)
{
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
return false;
}
if (!compress_on_thread)
{
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), clear_alpha, flip_y,
resize_width, resize_height, std::move(texture_data), texture_data_stride,
texture->GetFormat());
}
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp),
clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data),
texture_data_stride, texture->GetFormat());
compress_thread.detach();
return true;
}
bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */,
bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */)
{
if (!m_display_texture)
return false;
s32 resize_width = 0;
s32 resize_height = std::abs(m_display_texture_view_height);
if (apply_aspect_ratio)
{
const float ss_width_scale = static_cast<float>(m_display_active_width) / static_cast<float>(m_display_width);
const float ss_height_scale = static_cast<float>(m_display_active_height) / static_cast<float>(m_display_height);
const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale;
resize_width = g_settings.display_stretch_vertically ?
m_display_texture_view_width :
static_cast<s32>(static_cast<float>(resize_height) * ss_aspect_ratio);
resize_height = g_settings.display_stretch_vertically ?
static_cast<s32>(static_cast<float>(resize_height) /
(m_display_aspect_ratio /
(static_cast<float>(m_display_width) / static_cast<float>(m_display_height)))) :
resize_height;
}
else
{
resize_width = m_display_texture_view_width;
}
if (!full_resolution)
{
const s32 resolution_scale = std::abs(m_display_texture_view_height) / m_display_active_height;
resize_height /= resolution_scale;
resize_width /= resolution_scale;
}
if (resize_width <= 0 || resize_height <= 0)
return false;
const bool flip_y = (m_display_texture_view_height < 0);
s32 read_height = m_display_texture_view_height;
s32 read_y = m_display_texture_view_y;
if (flip_y)
{
read_height = -m_display_texture_view_height;
read_y =
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
}
return WriteTextureToFile(m_display_texture, m_display_texture_view_x, read_y, m_display_texture_view_width,
read_height, std::move(filename), true, flip_y, static_cast<u32>(resize_width),
static_cast<u32>(resize_height), compress_on_thread);
}
bool HostDisplay::WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width /* = 0 */,
u32 resize_height /* = 0 */, bool clear_alpha /* = true */)
{
if (!m_display_texture)
return false;
const bool flip_y = (m_display_texture_view_height < 0);
s32 read_width = m_display_texture_view_width;
s32 read_height = m_display_texture_view_height;
s32 read_x = m_display_texture_view_x;
s32 read_y = m_display_texture_view_y;
if (flip_y)
{
read_height = -m_display_texture_view_height;
read_y =
(m_display_texture->GetHeight() - read_height) - (m_display_texture->GetHeight() - m_display_texture_view_y);
}
u32 width = static_cast<u32>(read_width);
u32 height = static_cast<u32>(read_height);
std::vector<u32> texture_data(width * height);
u32 texture_data_stride = Common::AlignUpPow2(m_display_texture->GetPixelSize() * width, 4);
if (!DownloadTexture(m_display_texture, read_x, read_y, width, height, texture_data.data(), texture_data_stride))
{
Log_ErrorPrintf("Failed to download texture from GPU.");
return false;
}
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride,
m_display_texture->GetFormat()))
{
return false;
}
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000;
}
if (flip_y)
{
std::vector<u32> temp(width);
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
{
u32* top_ptr = &texture_data[flip_row * width];
u32* bottom_ptr = &texture_data[((height - 1) - flip_row) * width];
std::memcpy(temp.data(), top_ptr, texture_data_stride);
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
std::memcpy(bottom_ptr, temp.data(), texture_data_stride);
}
}
if (resize_width > 0 && resize_height > 0 && (resize_width != width || resize_height != height))
{
std::vector<u32> resized_texture_data(resize_width * resize_height);
u32 resized_texture_stride = sizeof(u32) * resize_width;
if (!stbir_resize_uint8(reinterpret_cast<u8*>(texture_data.data()), width, height, texture_data_stride,
reinterpret_cast<u8*>(resized_texture_data.data()), resize_width, resize_height,
resized_texture_stride, 4))
{
Log_ErrorPrintf("Failed to resize texture data from %ux%u to %ux%u", width, height, resize_width, resize_height);
return false;
}
width = resize_width;
height = resize_height;
*buffer = std::move(resized_texture_data);
texture_data_stride = resized_texture_stride;
}
else
{
*buffer = texture_data;
}
return true;
}
bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_resolution /* = false */,
bool compress_on_thread /* = false */)
{
u32 width = m_window_info.surface_width;
u32 height = m_window_info.surface_height;
auto [draw_left, draw_top, draw_width, draw_height] = CalculateDrawRect(width, height);
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
{
// If internal res, scale the computed draw rectangle to the internal res.
// We re-use the draw rect because it's already been AR corrected.
const float sar =
static_cast<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
if (sar >= dar)
{
// stretch height, preserve width
const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
width = m_display_texture_view_width;
height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
}
else
{
// stretch width, preserve height
const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
height = m_display_texture_view_height;
}
// DX11 won't go past 16K texture size.
constexpr u32 MAX_TEXTURE_SIZE = 16384;
if (width > MAX_TEXTURE_SIZE)
{
height = static_cast<u32>(static_cast<float>(height) /
(static_cast<float>(width) / static_cast<float>(MAX_TEXTURE_SIZE)));
width = MAX_TEXTURE_SIZE;
}
if (height > MAX_TEXTURE_SIZE)
{
height = MAX_TEXTURE_SIZE;
width = static_cast<u32>(static_cast<float>(width) /
(static_cast<float>(height) / static_cast<float>(MAX_TEXTURE_SIZE)));
}
// Remove padding, it's not part of the framebuffer.
draw_left = 0;
draw_top = 0;
draw_width = static_cast<s32>(width);
draw_height = static_cast<s32>(height);
}
if (width == 0 || height == 0)
return false;
std::vector<u32> pixels;
u32 pixels_stride;
GPUTexture::Format pixels_format;
if (!RenderScreenshot(width, height,
Common::Rectangle<s32>::FromExtents(draw_left, draw_top, draw_width, draw_height), &pixels,
&pixels_stride, &pixels_format))
{
Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height);
return false;
}
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb");
if (!fp)
{
Log_ErrorPrintf("Can't open file '%s': errno %d", filename.c_str(), errno);
return false;
}
if (!compress_on_thread)
{
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true, UsesLowerLeftOrigin(),
width, height, std::move(pixels), pixels_stride, pixels_format);
}
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true,
UsesLowerLeftOrigin(), width, height, std::move(pixels), pixels_stride, pixels_format);
compress_thread.detach();
return true;
}

277
src/util/host_display.h Normal file
View File

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/gpu_texture.h"
#include "common/rectangle.h"
#include "common/window_info.h"
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
enum class RenderAPI : u32
{
None,
D3D11,
D3D12,
Vulkan,
OpenGL,
OpenGLES
};
// Interface to the frontend's renderer.
class HostDisplay
{
public:
struct AdapterAndModeList
{
std::vector<std::string> adapter_names;
std::vector<std::string> fullscreen_modes;
};
virtual ~HostDisplay();
/// Returns the default/preferred API for the system.
static RenderAPI GetPreferredAPI();
/// 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);
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
// Position is relative to the top-left corner of the window.
ALWAYS_INLINE s32 GetMousePositionX() const { return m_mouse_position_x; }
ALWAYS_INLINE s32 GetMousePositionY() const { return m_mouse_position_y; }
ALWAYS_INLINE void SetMousePosition(s32 x, s32 y)
{
m_mouse_position_x = x;
m_mouse_position_y = y;
}
ALWAYS_INLINE const void* GetDisplayTextureHandle() const { return m_display_texture; }
ALWAYS_INLINE s32 GetDisplayWidth() const { return m_display_width; }
ALWAYS_INLINE s32 GetDisplayHeight() const { return m_display_height; }
ALWAYS_INLINE float GetDisplayAspectRatio() const { return m_display_aspect_ratio; }
ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; }
virtual RenderAPI GetRenderAPI() const = 0;
virtual void* GetDevice() const = 0;
virtual void* GetContext() const = 0;
virtual bool HasDevice() const = 0;
virtual bool HasSurface() const = 0;
virtual bool CreateDevice(const WindowInfo& wi, bool vsync) = 0;
virtual bool SetupDevice() = 0;
virtual bool MakeCurrent() = 0;
virtual bool DoneCurrent() = 0;
virtual void DestroySurface() = 0;
virtual bool ChangeWindow(const WindowInfo& wi) = 0;
virtual bool SupportsFullscreen() const = 0;
virtual bool IsFullscreen() = 0;
virtual bool SetFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0;
virtual AdapterAndModeList GetAdapterAndModeList() = 0;
virtual bool CreateResources() = 0;
virtual void DestroyResources();
virtual bool SetPostProcessingChain(const std::string_view& config) = 0;
/// Call when the window size changes externally to recreate any resources.
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height) = 0;
/// Creates an abstracted RGBA8 texture. If dynamic, the texture can be updated with UpdateTexture() below.
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Format format, const void* data, u32 data_stride,
bool dynamic = false) = 0;
virtual bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) = 0;
virtual void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) = 0;
virtual bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch);
virtual bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) = 0;
/// Returns false if the window was completely occluded.
virtual bool Render(bool skip_present) = 0;
/// Renders the display with postprocessing to the specified image.
virtual bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect,
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format) = 0;
ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; }
virtual void SetVSync(bool enabled) = 0;
/// ImGui context management, usually called by derived classes.
virtual bool CreateImGuiContext() = 0;
virtual void DestroyImGuiContext() = 0;
virtual bool UpdateImGuiFontTexture() = 0;
bool UsesLowerLeftOrigin() const;
void SetDisplayMaxFPS(float max_fps);
bool ShouldSkipDisplayingFrame();
void ThrottlePresentation();
void ClearDisplayTexture()
{
m_display_texture = nullptr;
m_display_texture_view_x = 0;
m_display_texture_view_y = 0;
m_display_texture_view_width = 0;
m_display_texture_view_height = 0;
m_display_changed = true;
}
void SetDisplayTexture(GPUTexture* texture, s32 view_x, s32 view_y, s32 view_width, s32 view_height)
{
m_display_texture = texture;
m_display_texture_view_x = view_x;
m_display_texture_view_y = view_y;
m_display_texture_view_width = view_width;
m_display_texture_view_height = view_height;
m_display_changed = true;
}
void SetDisplayTextureRect(s32 view_x, s32 view_y, s32 view_width, s32 view_height)
{
m_display_texture_view_x = view_x;
m_display_texture_view_y = view_y;
m_display_texture_view_width = view_width;
m_display_texture_view_height = view_height;
m_display_changed = true;
}
void SetDisplayParameters(s32 display_width, s32 display_height, s32 active_left, s32 active_top, s32 active_width,
s32 active_height, float display_aspect_ratio)
{
m_display_width = display_width;
m_display_height = display_height;
m_display_active_left = active_left;
m_display_active_top = active_top;
m_display_active_width = active_width;
m_display_active_height = active_height;
m_display_aspect_ratio = display_aspect_ratio;
m_display_changed = true;
}
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
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();
/// Sets the software cursor to the specified texture. Ownership of the texture is transferred.
void SetSoftwareCursor(std::unique_ptr<GPUTexture> texture, float scale = 1.0f);
/// Sets the software cursor to the specified image.
bool SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale = 1.0f);
/// Sets the software cursor to the specified path (png image).
bool SetSoftwareCursor(const char* path, float scale = 1.0f);
/// Disables the software cursor.
void ClearSoftwareCursor();
/// Helper function for computing the draw rectangle in a larger window.
std::tuple<s32, s32, s32, s32> CalculateDrawRect(s32 window_width, s32 window_height,
bool apply_aspect_ratio = true) const;
/// Helper function for converting window coordinates to display coordinates.
std::tuple<float, float> ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y, s32 window_width,
s32 window_height) const;
/// Helper function to save texture data to a PNG. If flip_y is set, the image will be flipped aka OpenGL.
bool WriteTextureToFile(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, std::string filename,
bool clear_alpha = true, bool flip_y = false, u32 resize_width = 0, u32 resize_height = 0,
bool compress_on_thread = false);
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string filename, bool full_resolution = true, bool apply_aspect_ratio = true,
bool compress_on_thread = false);
/// Helper function to save current display texture to a buffer.
bool WriteDisplayTextureToBuffer(std::vector<u32>* buffer, u32 resize_width = 0, u32 resize_height = 0,
bool clear_alpha = true);
/// Helper function to save screenshot to PNG.
bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);
protected:
ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast<bool>(m_cursor_texture); }
ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture != nullptr); }
bool IsUsingLinearFiltering() const;
void CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top, float* out_width,
float* out_height, float* out_left_padding, float* out_top_padding, float* out_scale,
float* out_x_scale, bool apply_aspect_ratio = true) const;
std::tuple<s32, s32, s32, s32> CalculateSoftwareCursorDrawRect() const;
std::tuple<s32, s32, s32, s32> CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const;
WindowInfo m_window_info;
u64 m_last_frame_displayed_time = 0;
s32 m_mouse_position_x = 0;
s32 m_mouse_position_y = 0;
s32 m_display_width = 0;
s32 m_display_height = 0;
s32 m_display_active_left = 0;
s32 m_display_active_top = 0;
s32 m_display_active_width = 0;
s32 m_display_active_height = 0;
float m_display_aspect_ratio = 1.0f;
float m_display_frame_interval = 0.0f;
GPUTexture* m_display_texture = nullptr;
s32 m_display_texture_view_x = 0;
s32 m_display_texture_view_y = 0;
s32 m_display_texture_view_width = 0;
s32 m_display_texture_view_height = 0;
std::unique_ptr<GPUTexture> m_cursor_texture;
float m_cursor_texture_scale = 1.0f;
bool m_display_changed = false;
bool m_gpu_timing_enabled = false;
bool m_vsync_enabled = false;
};
/// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been called.
extern std::unique_ptr<HostDisplay> g_host_display;
namespace Host {
std::unique_ptr<HostDisplay> CreateDisplayForAPI(RenderAPI api);
/// Creates the host display. This may create a new window. The API used depends on the current configuration.
bool AcquireHostDisplay(RenderAPI api);
/// Destroys the host display. This may close the display window.
void ReleaseHostDisplay();
/// 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.
// bool BeginPresentFrame(bool frame_skip);
/// Presents the frame to the display, and renders OSD elements.
// void EndPresentFrame();
/// Provided by the host; renders the display.
void RenderDisplay(bool skip_present);
void InvalidateDisplay();
} // namespace Host

File diff suppressed because it is too large Load Diff

294
src/util/imgui_fullscreen.h Normal file
View File

@ -0,0 +1,294 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "IconsFontAwesome5.h"
#include "common/types.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
class GPUTexture;
namespace ImGuiFullscreen {
#define HEX_TO_IMVEC4(hex, alpha) \
ImVec4(static_cast<float>((hex >> 16) & 0xFFu) / 255.0f, static_cast<float>((hex >> 8) & 0xFFu) / 255.0f, \
static_cast<float>(hex & 0xFFu) / 255.0f, static_cast<float>(alpha) / 255.0f)
static constexpr float LAYOUT_SCREEN_WIDTH = 1280.0f;
static constexpr float LAYOUT_SCREEN_HEIGHT = 720.0f;
static constexpr float LAYOUT_LARGE_FONT_SIZE = 26.0f;
static constexpr float LAYOUT_MEDIUM_FONT_SIZE = 16.0f;
static constexpr float LAYOUT_SMALL_FONT_SIZE = 10.0f;
static constexpr float LAYOUT_MENU_BUTTON_HEIGHT = 50.0f;
static constexpr float LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY = 26.0f;
static constexpr float LAYOUT_MENU_BUTTON_X_PADDING = 15.0f;
static constexpr float LAYOUT_MENU_BUTTON_Y_PADDING = 10.0f;
extern ImFont* g_standard_font;
extern ImFont* g_medium_font;
extern ImFont* g_large_font;
extern float g_layout_scale;
extern float g_layout_padding_left;
extern float g_layout_padding_top;
extern ImVec4 UIBackgroundColor;
extern ImVec4 UIBackgroundTextColor;
extern ImVec4 UIBackgroundLineColor;
extern ImVec4 UIBackgroundHighlightColor;
extern ImVec4 UIDisabledColor;
extern ImVec4 UIPrimaryColor;
extern ImVec4 UIPrimaryLightColor;
extern ImVec4 UIPrimaryDarkColor;
extern ImVec4 UIPrimaryTextColor;
extern ImVec4 UITextHighlightColor;
extern ImVec4 UIPrimaryLineColor;
extern ImVec4 UISecondaryColor;
extern ImVec4 UISecondaryLightColor;
extern ImVec4 UISecondaryDarkColor;
extern ImVec4 UISecondaryTextColor;
static ALWAYS_INLINE float DPIScale(float v)
{
return ImGui::GetIO().DisplayFramebufferScale.x * v;
}
static ALWAYS_INLINE float DPIScale(int v)
{
return ImGui::GetIO().DisplayFramebufferScale.x * static_cast<float>(v);
}
static ALWAYS_INLINE ImVec2 DPIScale(const ImVec2& v)
{
const ImVec2& fbs = ImGui::GetIO().DisplayFramebufferScale;
return ImVec2(v.x * fbs.x, v.y * fbs.y);
}
static ALWAYS_INLINE float WindowWidthScale(float v)
{
return ImGui::GetWindowWidth() * v;
}
static ALWAYS_INLINE float WindowHeightScale(float v)
{
return ImGui::GetWindowHeight() * v;
}
static ALWAYS_INLINE float LayoutScale(float v)
{
return g_layout_scale * v;
}
static ALWAYS_INLINE ImVec2 LayoutScale(const ImVec2& v)
{
return ImVec2(v.x * g_layout_scale, v.y * g_layout_scale);
}
static ALWAYS_INLINE ImVec2 LayoutScale(float x, float y)
{
return ImVec2(x * g_layout_scale, y * g_layout_scale);
}
static ALWAYS_INLINE ImVec2 LayoutScaleAndOffset(float x, float y)
{
return ImVec2(g_layout_padding_left + x * g_layout_scale, g_layout_padding_top + y * g_layout_scale);
}
static ALWAYS_INLINE ImVec4 ModAlpha(const ImVec4& v, float a)
{
return ImVec4(v.x, v.y, v.z, a);
}
static ALWAYS_INLINE ImVec4 MulAlpha(const ImVec4& v, float a)
{
return ImVec4(v.x, v.y, v.z, v.w * a);
}
static ALWAYS_INLINE std::string_view RemoveHash(const std::string_view& s)
{
const std::string_view::size_type pos = s.find('#');
return (pos != std::string_view::npos) ? s.substr(0, pos) : s;
}
/// Centers an image within the specified bounds, scaling up or down as needed.
ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size);
ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size);
/// Initializes, setting up any state.
bool Initialize(const char* placeholder_image_path);
void SetTheme(bool light);
void SetFonts(ImFont* standard_font, ImFont* medium_font, ImFont* large_font);
bool UpdateLayoutScale();
/// Shuts down, clearing all state.
void Shutdown();
/// Texture cache.
const std::shared_ptr<GPUTexture>& GetPlaceholderTexture();
std::shared_ptr<GPUTexture> LoadTexture(const std::string_view& path);
GPUTexture* GetCachedTexture(const std::string_view& name);
GPUTexture* GetCachedTextureAsync(const std::string_view& name);
bool InvalidateCachedTexture(const std::string& path);
void UploadAsyncTextures();
void BeginLayout();
void EndLayout();
void PushResetLayout();
void PopResetLayout();
void QueueResetFocus();
bool ResetFocusHere();
bool WantsToCloseMenu();
void ResetCloseMenuIfNeeded();
void PushPrimaryColor();
void PopPrimaryColor();
void PushSecondaryColor();
void PopSecondaryColor();
void DrawWindowTitle(const char* title);
bool BeginFullscreenColumns(const char* title = nullptr, float pos_y = 0.0f, bool expand_to_screen_width = false);
void EndFullscreenColumns();
bool BeginFullscreenColumnWindow(float start, float end, const char* name,
const ImVec4& background = UIBackgroundColor);
void EndFullscreenColumnWindow();
bool BeginFullscreenWindow(float left, float top, float width, float height, const char* name,
const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f,
float padding = 0.0f, ImGuiWindowFlags flags = 0);
bool BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name,
const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f,
float padding = 0.0f, ImGuiWindowFlags flags = 0);
void EndFullscreenWindow();
void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding = LAYOUT_MENU_BUTTON_X_PADDING,
float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT);
void EndMenuButtons();
bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min,
ImVec2* max, ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f);
void MenuHeading(const char* title, bool draw_line = true);
bool MenuHeadingButton(const char* title, const char* value = nullptr, bool enabled = true, bool draw_line = true);
bool ActiveButton(const char* title, bool is_active, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
bool MenuButton(const char* title, const char* summary, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool MenuButtonWithoutSummary(const char* title, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font,
const ImVec2& text_align = ImVec2(0.0f, 0.0f));
bool MenuButtonWithValue(const char* title, const char* summary, const char* value, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
bool MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size,
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
const ImVec2& uv0 = ImVec2(0.0f, 0.0f), const ImVec2& uv1 = ImVec2(1.0f, 1.0f),
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool FloatingButton(const char* text, float x, float y, float width = -1.0f,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, float anchor_x = 0.0f, float anchor_y = 0.0f,
bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr,
bool repeat_button = false);
bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
bool ThreeWayToggleButton(const char* title, const char* summary, std::optional<bool>* v, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
bool RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment,
const char* format = "%d", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment,
const char* format = "%f", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer,
const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count,
bool enabled, float height, ImFont* font, ImFont* summary_font);
template<typename DataType, typename CountType>
ALWAYS_INLINE static bool EnumChoiceButton(const char* title, const char* summary, DataType* value_pointer,
const char* (*to_display_name_function)(DataType value), CountType count,
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font)
{
s32 value = static_cast<s32>(*value_pointer);
auto to_display_name_wrapper = [](s32 value, void* opaque) -> const char* {
return (*static_cast<decltype(to_display_name_function)*>(opaque))(static_cast<DataType>(value));
};
if (EnumChoiceButtonImpl(title, summary, &value, to_display_name_wrapper, &to_display_name_function,
static_cast<u32>(count), enabled, height, font, summary_font))
{
*value_pointer = static_cast<DataType>(value);
return true;
}
else
{
return false;
}
}
void DrawShadowedText(ImDrawList* dl, ImFont* font, const ImVec2& pos, u32 col, const char* text,
const char* text_end = nullptr, float wrap_width = 0.0f);
void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING);
void EndNavBar();
void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
void RightAlignNavButtons(u32 num_items = 0, float item_width = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
using FileSelectorCallback = std::function<void(const std::string& path)>;
using FileSelectorFilters = std::vector<std::string>;
bool IsFileSelectorOpen();
void OpenFileSelector(const char* title, bool select_directory, FileSelectorCallback callback,
FileSelectorFilters filters = FileSelectorFilters(),
std::string initial_directory = std::string());
void CloseFileSelector();
using ChoiceDialogCallback = std::function<void(s32 index, const std::string& title, bool checked)>;
using ChoiceDialogOptions = std::vector<std::pair<std::string, bool>>;
bool IsChoiceDialogOpen();
void OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback);
void CloseChoiceDialog();
using InputStringDialogCallback = std::function<void(std::string text)>;
bool IsInputDialogOpen();
void OpenInputStringDialog(std::string title, std::string message, std::string caption, std::string ok_button_text,
InputStringDialogCallback callback);
void CloseInputDialog();
using ConfirmMessageDialogCallback = std::function<void(bool)>;
using InfoMessageDialogCallback = std::function<void()>;
using MessageDialogCallback = std::function<void(s32)>;
bool IsMessageBoxDialogOpen();
void OpenConfirmMessageDialog(std::string title, std::string message, ConfirmMessageDialogCallback callback,
std::string yes_button_text = ICON_FA_CHECK " Yes",
std::string no_button_text = ICON_FA_TIMES " No");
void OpenInfoMessageDialog(std::string title, std::string message, InfoMessageDialogCallback callback = {},
std::string button_text = ICON_FA_WINDOW_CLOSE " Close");
void OpenMessageDialog(std::string title, std::string message, MessageDialogCallback callback,
std::string first_button_text, std::string second_button_text, std::string third_button_text);
void CloseMessageDialog();
float GetNotificationVerticalPosition();
float GetNotificationVerticalDirection();
void SetNotificationVerticalPosition(float position, float direction);
void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
void CloseBackgroundProgressDialog(const char* str_id);
void AddNotification(float duration, std::string title, std::string text, std::string image_path);
void ClearNotifications();
void ShowToast(std::string title, std::string message, float duration = 10.0f);
void ClearToast();
} // namespace ImGuiFullscreen

View File

@ -0,0 +1,499 @@
// 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 "imgui.h"
#include "imgui_impl_dx11.h"
#include "common/d3d11/texture.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;
ID3D11RasterizerState* pRasterizerState;
ID3D11BlendState* pBlendState;
ID3D11DepthStencilState* pDepthStencilState;
int VertexBufferSize;
int IndexBufferSize;
D3D11::Texture FontTexture;
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
const D3D11::Texture* tex = static_cast<D3D11::Texture*>(pcmd->GetTexID());
ID3D11ShaderResourceView* texture_srv = tex ? tex->GetD3DSRV() : nullptr;
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;
}
}
bool 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);
const u32 stride = sizeof(u32) * width;
if (!bd->FontTexture.Create(bd->pd3dDevice, width, height, 1, 1, 1, GPUTexture::Format::RGBA8, D3D11_BIND_SHADER_RESOURCE, pixels, stride))
return false;
// Store our identifier
io.Fonts->SetTexID((ImTextureID)&bd->FontTexture);
// 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);
}
return true;
}
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);
}
return ImGui_ImplDX11_CreateFontsTexture();
}
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->FontTexture) { bd->FontTexture.Destroy(); 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

@ -0,0 +1,17 @@
// 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();
bool ImGui_ImplDX11_CreateFontsTexture();

View File

@ -0,0 +1,545 @@
// 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 "common/windows_headers.h"
#include "common/assert.h"
#include "common/d3d12/context.h"
#include "common/d3d12/texture.h"
#include "common/d3d12/stream_buffer.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(false);
if (!bd->VertexStreamBuffer.ReserveMemory(needed_vb, sizeof(ImDrawVert)) ||
!bd->IndexStreamBuffer.ReserveMemory(needed_ib, sizeof(ImDrawIdx)))
{
Panic("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)
{
#if 0
// for when we redo the descriptor stuff
D3D12::DescriptorHandle handle;
if (!g_d3d12_context->GetDescriptorAllocator().Allocate(1, &handle))
{
// ugh.
g_d3d12_context->ExecuteCommandList(false);
ctx = g_d3d12_context->GetCommandList();
ImGui_ImplDX12_SetupRenderState(draw_data, ctx);
if (!g_d3d12_context->GetDescriptorAllocator().Allocate(1, &handle))
Panic("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);
#else
ctx->SetGraphicsRootDescriptorTable(1, tex->GetSRVDescriptor());
#endif
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() != static_cast<u32>(width) || bd->FontTexture.GetHeight() != static_cast<u32>(height))
{
if (!bd->FontTexture.Create(width, height, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN,
D3D12_RESOURCE_FLAG_NONE))
{
return false;
}
}
#if 0
if (!bd->FontTexture.LoadData(g_d3d12_context->GetInitCommandList(), 0, 0, 0, width, height, pixels, width * sizeof(u32)))
return false;
#else
if (!bd->FontTexture.LoadData(0, 0, width, height, pixels, width * sizeof(u32)))
return false;
#endif
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

@ -0,0 +1,14 @@
// 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

@ -0,0 +1,569 @@
// 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 "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 "common/gl/loader.h"
#include "common/gl/texture.h"
#include "common/log.h"
Log_SetChannel(ImGui_ImplOpenGL3);
// OpenGL Data
struct ImGui_ImplOpenGL3_Data
{
GLuint GlVersion = 0; // 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.
GL::Texture FontTexture;
GLuint ShaderHandle = 0;
GLint AttribLocationTex = 0; // Uniforms location
GLint AttribLocationProjMtx = 0;
GLuint AttribLocationVtxPos = 0; // Vertex attributes location
GLuint AttribLocationVtxUV = 0;
GLuint AttribLocationVtxColor = 0;
unsigned int VboHandle = 0, ElementsHandle = 0, VaoHandle = 0;
GLsizeiptr VertexBufferSize = 0;
GLsizeiptr IndexBufferSize = 0;
ImGui_ImplOpenGL3_Data() = default;
};
// 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);
// 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");
if (glDrawElementsBaseVertex)
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
else
Log_WarningPrintf("Missing glDrawElementsBaseVertex()");
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]);
// Bind vertex/index buffers and setup attributes for ImDrawVert
if (bd->VaoHandle)
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
const GL::Texture* tex = static_cast<const GL::Texture*>(pcmd->GetTexID());
if (tex)
tex->Bind();
if (glDrawElementsBaseVertex)
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);
else
glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));
}
}
}
if (bd->VaoHandle)
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)
bd->FontTexture.Create(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, pixels);
bd->FontTexture.SetLinearFilter(true);
// Store our identifier
io.Fonts->SetTexID(&bd->FontTexture);
return true;
}
void ImGui_ImplOpenGL3_DestroyFontsTexture()
{
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->FontTexture.IsValid())
bd->FontTexture.Destroy();
}
// 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);
if (glGenVertexArrays)
glGenVertexArrays(1, &bd->VaoHandle);
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; }
ImGui_ImplOpenGL3_DestroyFontsTexture();
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

View File

@ -0,0 +1,16 @@
// 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

@ -0,0 +1,685 @@
// 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/stream_buffer.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 u32 vertex_size = static_cast<u32>(draw_data->TotalVtxCount) * static_cast<u32>(sizeof(ImDrawVert));
const u32 index_size = static_cast<u32>(draw_data->TotalIdxCount) * static_cast<u32>(sizeof(ImDrawIdx));
if (!bd->VertexStreamBuffer.ReserveMemory(vertex_size, static_cast<u32>(sizeof(ImDrawVert))) ||
!bd->IndexStreamBuffer.ReserveMemory(index_size, static_cast<u32>(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, static_cast<u32>(cmd_list->VtxBuffer.Size) * static_cast<u32>(sizeof(ImDrawVert)));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, static_cast<u32>(cmd_list->IdxBuffer.Size) * static_cast<u32>(sizeof(ImDrawIdx)));
vtx_dst += static_cast<u32>(cmd_list->VtxBuffer.Size);
idx_dst += static_cast<u32>(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;
}
}
// Store our identifier
bd->FontTexture.Update(0, 0, width, height, 0, 0, pixels, sizeof(u32) * width);
bd->FontTexture.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
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_EDGE;
info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
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

@ -0,0 +1,12 @@
// 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();

850
src/util/imgui_manager.cpp Normal file
View File

@ -0,0 +1,850 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "imgui_manager.h"
#include "IconsFontAwesome5.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "core/gpu.h"
#include "core/host.h"
#include "host_display.h"
#include "core/system.h"
#include "fmt/format.h"
#include "imgui.h"
#include "imgui_fullscreen.h"
#include "imgui_internal.h"
#include "input_manager.h"
#include <atomic>
#include <chrono>
#include <cmath>
#include <deque>
#include <mutex>
#include <unordered_map>
Log_SetChannel(ImGuiManager);
namespace ImGuiManager {
static void SetStyle();
static void SetKeyMap();
static bool LoadFontData();
static bool AddImGuiFonts(bool fullscreen_fonts);
static ImFont* AddTextFont(float size);
static ImFont* AddFixedFont(float size);
static bool AddIconFonts(float size);
static void AcquirePendingOSDMessages();
static void DrawOSDMessages();
} // namespace ImGuiManager
static float s_global_scale = 1.0f;
static std::string s_font_path;
static const ImWchar* s_font_range = nullptr;
static ImFont* s_standard_font;
static ImFont* s_fixed_font;
static ImFont* s_medium_font;
static ImFont* s_large_font;
static std::vector<u8> s_standard_font_data;
static std::vector<u8> s_fixed_font_data;
static std::vector<u8> s_icon_font_data;
static Common::Timer s_last_render_time;
// cached copies of WantCaptureKeyboard/Mouse, used to know when to dispatch events
static std::atomic_bool s_imgui_wants_keyboard{false};
static std::atomic_bool s_imgui_wants_mouse{false};
// mapping of host key -> imgui key
static std::unordered_map<u32, ImGuiKey> s_imgui_key_map;
void ImGuiManager::SetFontPath(std::string path)
{
s_font_path = std::move(path);
s_standard_font_data = {};
}
void ImGuiManager::SetFontRange(const u16* range)
{
s_font_range = range;
s_standard_font_data = {};
}
bool ImGuiManager::Initialize()
{
if (!LoadFontData())
{
Panic("Failed to load font data");
return false;
}
s_global_scale =
std::max(g_host_display->GetWindowScale() * static_cast<float>(g_settings.display_osd_scale / 100.0f), 1.0f);
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendUsingLegacyKeyArrays = 0;
io.BackendUsingLegacyNavInputArray = 0;
#ifndef __ANDROID__
// Android has no keyboard, nor are we using ImGui for any actual user-interactable windows.
io.ConfigFlags |=
ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_NoMouseCursorChange;
#else
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad;
#endif
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());
SetKeyMap();
SetStyle();
if (!g_host_display->CreateImGuiContext())
{
Panic("Failed to create ImGui device context");
g_host_display->DestroyImGuiContext();
ImGui::DestroyContext();
return false;
}
if (!AddImGuiFonts(false) || !g_host_display->UpdateImGuiFontTexture())
{
Panic("Failed to create ImGui font text");
g_host_display->DestroyImGuiContext();
ImGui::DestroyContext();
return false;
}
// don't need the font data anymore, save some memory
ImGui::GetIO().Fonts->ClearTexData();
NewFrame();
return true;
}
void ImGuiManager::Shutdown()
{
if (g_host_display)
g_host_display->DestroyImGuiContext();
if (ImGui::GetCurrentContext())
ImGui::DestroyContext();
s_standard_font = nullptr;
s_fixed_font = nullptr;
s_medium_font = nullptr;
s_large_font = nullptr;
ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr);
}
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;
ImGui::GetIO().DisplaySize = ImVec2(static_cast<float>(new_width), static_cast<float>(new_height));
UpdateScale();
// restart imgui frame on the new window size to pick it up, otherwise we draw to the old size
ImGui::EndFrame();
NewFrame();
}
void ImGuiManager::UpdateScale()
{
const float window_scale = g_host_display ? g_host_display->GetWindowScale() : 1.0f;
const float scale = std::max(window_scale * static_cast<float>(g_settings.display_osd_scale / 100.0f), 1.0f);
if (scale == s_global_scale && (!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()))
return;
// This is assumed to be called mid-frame.
ImGui::EndFrame();
s_global_scale = scale;
ImGui::GetStyle() = ImGuiStyle();
ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f);
SetStyle();
ImGui::GetStyle().ScaleAllSizes(scale);
if (!AddImGuiFonts(HasFullscreenFonts()))
Panic("Failed to create ImGui font text");
if (!g_host_display->UpdateImGuiFontTexture())
Panic("Failed to recreate font texture after scale+resize");
NewFrame();
}
void ImGuiManager::NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
io.DeltaTime = static_cast<float>(s_last_render_time.GetTimeSecondsAndReset());
ImGui::NewFrame();
// Disable nav input on the implicit (Debug##Default) window. Otherwise we end up requesting keyboard
// focus when there's nothing there. We use GetCurrentWindowRead() because otherwise it'll make it visible.
ImGui::GetCurrentWindowRead()->Flags |= ImGuiWindowFlags_NoNavInputs;
s_imgui_wants_keyboard.store(io.WantCaptureKeyboard, std::memory_order_relaxed);
s_imgui_wants_mouse.store(io.WantCaptureMouse, std::memory_order_release);
}
void ImGuiManager::SetStyle()
{
ImGuiStyle& style = ImGui::GetStyle();
style = ImGuiStyle();
style.WindowMinSize = ImVec2(1.0f, 1.0f);
ImVec4* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.33f, 0.38f, 0.46f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.27f, 0.32f, 0.38f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
style.ScaleAllSizes(s_global_scale);
}
void ImGuiManager::SetKeyMap()
{
struct KeyMapping
{
int index;
const char* name;
const char* alt_name;
};
static constexpr KeyMapping mapping[] = {{ImGuiKey_LeftArrow, "Left"},
{ImGuiKey_RightArrow, "Right"},
{ImGuiKey_UpArrow, "Up"},
{ImGuiKey_DownArrow, "Down"},
{ImGuiKey_PageUp, "PageUp"},
{ImGuiKey_PageDown, "PageDown"},
{ImGuiKey_Home, "Home"},
{ImGuiKey_End, "End"},
{ImGuiKey_Insert, "Insert"},
{ImGuiKey_Delete, "Delete"},
{ImGuiKey_Backspace, "Backspace"},
{ImGuiKey_Space, "Space"},
{ImGuiKey_Enter, "Return"},
{ImGuiKey_Escape, "Escape"},
{ImGuiKey_LeftCtrl, "LeftCtrl", "Ctrl"},
{ImGuiKey_LeftShift, "LeftShift", "Shift"},
{ImGuiKey_LeftAlt, "LeftAlt", "Alt"},
{ImGuiKey_LeftSuper, "LeftSuper", "Super"},
{ImGuiKey_RightCtrl, "RightCtrl"},
{ImGuiKey_RightShift, "RightShift"},
{ImGuiKey_RightAlt, "RightAlt"},
{ImGuiKey_RightSuper, "RightSuper"},
{ImGuiKey_Menu, "Menu"},
{ImGuiKey_0, "0"},
{ImGuiKey_1, "1"},
{ImGuiKey_2, "2"},
{ImGuiKey_3, "3"},
{ImGuiKey_4, "4"},
{ImGuiKey_5, "5"},
{ImGuiKey_6, "6"},
{ImGuiKey_7, "7"},
{ImGuiKey_8, "8"},
{ImGuiKey_9, "9"},
{ImGuiKey_A, "A"},
{ImGuiKey_B, "B"},
{ImGuiKey_C, "C"},
{ImGuiKey_D, "D"},
{ImGuiKey_E, "E"},
{ImGuiKey_F, "F"},
{ImGuiKey_G, "G"},
{ImGuiKey_H, "H"},
{ImGuiKey_I, "I"},
{ImGuiKey_J, "J"},
{ImGuiKey_K, "K"},
{ImGuiKey_L, "L"},
{ImGuiKey_M, "M"},
{ImGuiKey_N, "N"},
{ImGuiKey_O, "O"},
{ImGuiKey_P, "P"},
{ImGuiKey_Q, "Q"},
{ImGuiKey_R, "R"},
{ImGuiKey_S, "S"},
{ImGuiKey_T, "T"},
{ImGuiKey_U, "U"},
{ImGuiKey_V, "V"},
{ImGuiKey_W, "W"},
{ImGuiKey_X, "X"},
{ImGuiKey_Y, "Y"},
{ImGuiKey_Z, "Z"},
{ImGuiKey_F1, "F1"},
{ImGuiKey_F2, "F2"},
{ImGuiKey_F3, "F3"},
{ImGuiKey_F4, "F4"},
{ImGuiKey_F5, "F5"},
{ImGuiKey_F6, "F6"},
{ImGuiKey_F7, "F7"},
{ImGuiKey_F8, "F8"},
{ImGuiKey_F9, "F9"},
{ImGuiKey_F10, "F10"},
{ImGuiKey_F11, "F11"},
{ImGuiKey_F12, "F12"},
{ImGuiKey_Apostrophe, "Apostrophe"},
{ImGuiKey_Comma, "Comma"},
{ImGuiKey_Minus, "Minus"},
{ImGuiKey_Period, "Period"},
{ImGuiKey_Slash, "Slash"},
{ImGuiKey_Semicolon, "Semicolon"},
{ImGuiKey_Equal, "Equal"},
{ImGuiKey_LeftBracket, "BracketLeft"},
{ImGuiKey_Backslash, "Backslash"},
{ImGuiKey_RightBracket, "BracketRight"},
{ImGuiKey_GraveAccent, "QuoteLeft"},
{ImGuiKey_CapsLock, "CapsLock"},
{ImGuiKey_ScrollLock, "ScrollLock"},
{ImGuiKey_NumLock, "NumLock"},
{ImGuiKey_PrintScreen, "PrintScreen"},
{ImGuiKey_Pause, "Pause"},
{ImGuiKey_Keypad0, "Keypad0"},
{ImGuiKey_Keypad1, "Keypad1"},
{ImGuiKey_Keypad2, "Keypad2"},
{ImGuiKey_Keypad3, "Keypad3"},
{ImGuiKey_Keypad4, "Keypad4"},
{ImGuiKey_Keypad5, "Keypad5"},
{ImGuiKey_Keypad6, "Keypad6"},
{ImGuiKey_Keypad7, "Keypad7"},
{ImGuiKey_Keypad8, "Keypad8"},
{ImGuiKey_Keypad9, "Keypad9"},
{ImGuiKey_KeypadDecimal, "KeypadPeriod"},
{ImGuiKey_KeypadDivide, "KeypadDivide"},
{ImGuiKey_KeypadMultiply, "KeypadMultiply"},
{ImGuiKey_KeypadSubtract, "KeypadMinus"},
{ImGuiKey_KeypadAdd, "KeypadPlus"},
{ImGuiKey_KeypadEnter, "KeypadReturn"},
{ImGuiKey_KeypadEqual, "KeypadEqual"}};
s_imgui_key_map.clear();
for (const KeyMapping& km : mapping)
{
std::optional<u32> map(InputManager::ConvertHostKeyboardStringToCode(km.name));
if (!map.has_value() && km.alt_name)
map = InputManager::ConvertHostKeyboardStringToCode(km.alt_name);
if (map.has_value())
s_imgui_key_map[map.value()] = km.index;
}
}
bool ImGuiManager::LoadFontData()
{
if (s_standard_font_data.empty())
{
std::optional<std::vector<u8>> font_data = s_font_path.empty() ?
Host::ReadResourceFile("fonts/Roboto-Regular.ttf") :
FileSystem::ReadBinaryFile(s_font_path.c_str());
if (!font_data.has_value())
return false;
s_standard_font_data = std::move(font_data.value());
}
if (s_fixed_font_data.empty())
{
std::optional<std::vector<u8>> font_data = Host::ReadResourceFile("fonts/RobotoMono-Medium.ttf");
if (!font_data.has_value())
return false;
s_fixed_font_data = std::move(font_data.value());
}
if (s_icon_font_data.empty())
{
std::optional<std::vector<u8>> font_data = Host::ReadResourceFile("fonts/fa-solid-900.ttf");
if (!font_data.has_value())
return false;
s_icon_font_data = std::move(font_data.value());
}
return true;
}
ImFont* ImGuiManager::AddTextFont(float size)
{
static const ImWchar default_ranges[] = {
// Basic Latin + Latin Supplement + Central European diacritics
0x0020,
0x017F,
// Cyrillic + Cyrillic Supplement
0x0400,
0x052F,
// Cyrillic Extended-A
0x2DE0,
0x2DFF,
// Cyrillic Extended-B
0xA640,
0xA69F,
0,
};
ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_standard_font_data.data(),
static_cast<int>(s_standard_font_data.size()), size, &cfg,
s_font_range ? s_font_range : default_ranges);
}
ImFont* ImGuiManager::AddFixedFont(float size)
{
ImFontConfig cfg;
cfg.FontDataOwnedByAtlas = false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_fixed_font_data.data(),
static_cast<int>(s_fixed_font_data.size()), size, &cfg, nullptr);
}
bool ImGuiManager::AddIconFonts(float size)
{
static constexpr ImWchar range_fa[] = {
0xf002, 0xf002, 0xf005, 0xf005, 0xf007, 0xf007, 0xf00c, 0xf00e, 0xf011, 0xf011, 0xf013, 0xf013, 0xf017, 0xf017,
0xf019, 0xf019, 0xf01c, 0xf01c, 0xf021, 0xf021, 0xf023, 0xf023, 0xf025, 0xf025, 0xf027, 0xf028, 0xf02d, 0xf02e,
0xf030, 0xf030, 0xf03a, 0xf03a, 0xf03d, 0xf03d, 0xf049, 0xf04c, 0xf050, 0xf050, 0xf059, 0xf059, 0xf05e, 0xf05e,
0xf062, 0xf063, 0xf065, 0xf065, 0xf067, 0xf067, 0xf071, 0xf071, 0xf075, 0xf075, 0xf077, 0xf078, 0xf07b, 0xf07c,
0xf084, 0xf085, 0xf091, 0xf091, 0xf0a0, 0xf0a0, 0xf0ac, 0xf0ad, 0xf0c5, 0xf0c5, 0xf0c7, 0xf0c8, 0xf0cb, 0xf0cb,
0xf0d0, 0xf0d0, 0xf0dc, 0xf0dc, 0xf0e2, 0xf0e2, 0xf0eb, 0xf0eb, 0xf0f1, 0xf0f1, 0xf0f3, 0xf0f3, 0xf0fe, 0xf0fe,
0xf110, 0xf110, 0xf119, 0xf119, 0xf11b, 0xf11c, 0xf140, 0xf140, 0xf144, 0xf144, 0xf14a, 0xf14a, 0xf15b, 0xf15b,
0xf15d, 0xf15d, 0xf188, 0xf188, 0xf191, 0xf192, 0xf1dd, 0xf1de, 0xf1e6, 0xf1e6, 0xf1eb, 0xf1eb, 0xf1f8, 0xf1f8,
0xf1fc, 0xf1fc, 0xf242, 0xf242, 0xf245, 0xf245, 0xf26c, 0xf26c, 0xf279, 0xf279, 0xf2d0, 0xf2d0, 0xf2db, 0xf2db,
0xf2f2, 0xf2f2, 0xf2f5, 0xf2f5, 0xf3c1, 0xf3c1, 0xf410, 0xf410, 0xf466, 0xf466, 0xf500, 0xf500, 0xf51f, 0xf51f,
0xf545, 0xf545, 0xf547, 0xf548, 0xf552, 0xf552, 0xf57a, 0xf57a, 0xf5a2, 0xf5a2, 0xf5aa, 0xf5aa, 0xf5e7, 0xf5e7,
0xf65d, 0xf65e, 0xf6a9, 0xf6a9, 0xf7c2, 0xf7c2, 0xf807, 0xf807, 0xf815, 0xf815, 0xf818, 0xf818, 0xf84c, 0xf84c,
0xf8cc, 0xf8cc, 0x0, 0x0};
ImFontConfig cfg;
cfg.MergeMode = true;
cfg.PixelSnapH = true;
cfg.GlyphMinAdvanceX = size;
cfg.GlyphMaxAdvanceX = size;
cfg.FontDataOwnedByAtlas = false;
return (ImGui::GetIO().Fonts->AddFontFromMemoryTTF(s_icon_font_data.data(), static_cast<int>(s_icon_font_data.size()),
size * 0.75f, &cfg, range_fa) != nullptr);
}
bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts)
{
const float standard_font_size = std::ceil(15.0f * s_global_scale);
ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear();
s_standard_font = AddTextFont(standard_font_size);
if (!s_standard_font || !AddIconFonts(standard_font_size))
return false;
s_fixed_font = AddFixedFont(standard_font_size);
if (!s_fixed_font)
return false;
if (fullscreen_fonts)
{
const float medium_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE));
s_medium_font = AddTextFont(medium_font_size);
if (!s_medium_font || !AddIconFonts(medium_font_size))
return false;
const float large_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE));
s_large_font = AddTextFont(large_font_size);
if (!s_large_font || !AddIconFonts(large_font_size))
return false;
}
else
{
s_medium_font = nullptr;
s_large_font = nullptr;
}
ImGuiFullscreen::SetFonts(s_standard_font, s_medium_font, s_large_font);
return io.Fonts->Build();
}
bool ImGuiManager::AddFullscreenFontsIfMissing()
{
if (HasFullscreenFonts())
return true;
// can't do this in the middle of a frame
ImGui::EndFrame();
if (!AddImGuiFonts(true))
{
Log_ErrorPrint("Failed to lazily allocate fullscreen fonts.");
AddImGuiFonts(false);
}
g_host_display->UpdateImGuiFontTexture();
NewFrame();
return HasFullscreenFonts();
}
bool ImGuiManager::HasFullscreenFonts()
{
return (s_medium_font && s_large_font);
}
struct OSDMessage
{
std::string key;
std::string text;
std::chrono::steady_clock::time_point time;
float duration;
};
static std::deque<OSDMessage> s_osd_active_messages;
static std::deque<OSDMessage> s_osd_posted_messages;
static std::mutex s_osd_messages_lock;
void Host::AddOSDMessage(std::string message, float duration /*= 2.0f*/)
{
AddKeyedOSDMessage(std::string(), std::move(message), duration);
}
void Host::AddKeyedOSDMessage(std::string key, std::string message, float duration /* = 2.0f */)
{
if (!key.empty())
Log_InfoPrintf("OSD [%s]: %s", key.c_str(), message.c_str());
else
Log_InfoPrintf("OSD: %s", message.c_str());
OSDMessage msg;
msg.key = std::move(key);
msg.text = std::move(message);
msg.duration = duration;
msg.time = std::chrono::steady_clock::now();
std::unique_lock<std::mutex> lock(s_osd_messages_lock);
s_osd_posted_messages.push_back(std::move(msg));
}
void Host::AddFormattedOSDMessage(float duration, const char* format, ...)
{
std::va_list ap;
va_start(ap, format);
std::string ret = StringUtil::StdStringFromFormatV(format, ap);
va_end(ap);
return AddKeyedOSDMessage(std::string(), std::move(ret), duration);
}
void Host::AddIconOSDMessage(std::string key, const char* icon, std::string message, float duration /* = 2.0f */)
{
return AddKeyedOSDMessage(std::move(key), fmt::format("{} {}", icon, message), duration);
}
void Host::AddKeyedFormattedOSDMessage(std::string key, float duration, const char* format, ...)
{
std::va_list ap;
va_start(ap, format);
std::string ret = StringUtil::StdStringFromFormatV(format, ap);
va_end(ap);
return AddKeyedOSDMessage(std::move(key), std::move(ret), duration);
}
void Host::RemoveKeyedOSDMessage(std::string key)
{
OSDMessage msg;
msg.key = std::move(key);
msg.duration = 0.0f;
msg.time = std::chrono::steady_clock::now();
std::unique_lock<std::mutex> lock(s_osd_messages_lock);
s_osd_posted_messages.push_back(std::move(msg));
}
void Host::ClearOSDMessages()
{
{
std::unique_lock<std::mutex> lock(s_osd_messages_lock);
s_osd_posted_messages.clear();
}
s_osd_active_messages.clear();
}
void ImGuiManager::AcquirePendingOSDMessages()
{
std::atomic_thread_fence(std::memory_order_consume);
if (s_osd_posted_messages.empty())
return;
std::unique_lock lock(s_osd_messages_lock);
for (;;)
{
if (s_osd_posted_messages.empty())
break;
if (g_settings.display_show_osd_messages)
{
OSDMessage& new_msg = s_osd_posted_messages.front();
std::deque<OSDMessage>::iterator iter;
if (!new_msg.key.empty() && (iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(),
[&new_msg](const OSDMessage& other) {
return new_msg.key == other.key;
})) != s_osd_active_messages.end())
{
iter->text = std::move(new_msg.text);
iter->duration = new_msg.duration;
iter->time = new_msg.time;
}
else
{
s_osd_active_messages.push_back(std::move(new_msg));
}
}
s_osd_posted_messages.pop_front();
static constexpr size_t MAX_ACTIVE_OSD_MESSAGES = 512;
if (s_osd_active_messages.size() > MAX_ACTIVE_OSD_MESSAGES)
s_osd_active_messages.pop_front();
}
}
void ImGuiManager::DrawOSDMessages()
{
ImFont* const font = ImGui::GetFont();
const float scale = s_global_scale;
const float spacing = std::ceil(5.0f * scale);
const float margin = std::ceil(10.0f * scale);
const float padding = std::ceil(8.0f * scale);
const float rounding = std::ceil(5.0f * scale);
const float max_width = ImGui::GetIO().DisplaySize.x - (margin + padding) * 2.0f;
float position_x = margin;
float position_y = margin;
const auto now = std::chrono::steady_clock::now();
auto iter = s_osd_active_messages.begin();
while (iter != s_osd_active_messages.end())
{
const OSDMessage& msg = *iter;
const double time = std::chrono::duration<double>(now - msg.time).count();
const float time_remaining = static_cast<float>(msg.duration - time);
if (time_remaining <= 0.0f)
{
iter = s_osd_active_messages.erase(iter);
continue;
}
++iter;
const float opacity = std::min(time_remaining, 1.0f);
const u32 alpha = static_cast<u32>(opacity * 255.0f);
if (position_y >= ImGui::GetIO().DisplaySize.y)
break;
const ImVec2 pos(position_x, position_y);
const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, max_width, max_width, msg.text.c_str(),
msg.text.c_str() + msg.text.length()));
const ImVec2 size(text_size.x + padding * 2.0f, text_size.y + padding * 2.0f);
const ImVec4 text_rect(pos.x + padding, pos.y + padding, pos.x + size.x - padding, pos.y + size.y - padding);
ImDrawList* dl = ImGui::GetForegroundDrawList();
dl->AddRectFilled(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x21, 0x21, 0x21, alpha), rounding);
dl->AddRect(pos, ImVec2(pos.x + size.x, pos.y + size.y), IM_COL32(0x48, 0x48, 0x48, alpha), rounding);
dl->AddText(font, font->FontSize, ImVec2(text_rect.x, text_rect.y), IM_COL32(0xff, 0xff, 0xff, alpha),
msg.text.c_str(), msg.text.c_str() + msg.text.length(), max_width, &text_rect);
position_y += size.y + spacing;
}
}
void ImGuiManager::RenderOSDMessages()
{
AcquirePendingOSDMessages();
DrawOSDMessages();
}
float ImGuiManager::GetGlobalScale()
{
return s_global_scale;
}
float Host::GetOSDScale()
{
return s_global_scale;
}
ImFont* ImGuiManager::GetStandardFont()
{
return s_standard_font;
}
ImFont* ImGuiManager::GetFixedFont()
{
return s_fixed_font;
}
ImFont* ImGuiManager::GetMediumFont()
{
AddFullscreenFontsIfMissing();
return s_medium_font;
}
ImFont* ImGuiManager::GetLargeFont()
{
AddFullscreenFontsIfMissing();
return s_large_font;
}
bool ImGuiManager::WantsTextInput()
{
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
void ImGuiManager::AddTextInput(std::string str)
{
if (!ImGui::GetCurrentContext())
return;
if (!s_imgui_wants_keyboard.load(std::memory_order_acquire))
return;
ImGui::GetIO().AddInputCharactersUTF8(str.c_str());
}
void ImGuiManager::UpdateMousePosition(float x, float y)
{
if (!ImGui::GetCurrentContext())
return;
ImGui::GetIO().MousePos = ImVec2(x, y);
std::atomic_thread_fence(std::memory_order_release);
}
bool ImGuiManager::ProcessPointerButtonEvent(InputBindingKey key, float value)
{
if (!ImGui::GetCurrentContext() || key.data >= std::size(ImGui::GetIO().MouseDown))
return false;
// still update state anyway
ImGui::GetIO().AddMouseButtonEvent(key.data, value != 0.0f);
return s_imgui_wants_mouse.load(std::memory_order_acquire);
}
bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value)
{
if (!ImGui::GetCurrentContext() || value == 0.0f || key.data < static_cast<u32>(InputPointerAxis::WheelX))
return false;
// still update state anyway
const bool horizontal = (key.data == static_cast<u32>(InputPointerAxis::WheelX));
ImGui::GetIO().AddMouseWheelEvent(horizontal ? value : 0.0f, horizontal ? 0.0f : value);
return s_imgui_wants_mouse.load(std::memory_order_acquire);
}
bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value)
{
decltype(s_imgui_key_map)::iterator iter;
if (!ImGui::GetCurrentContext() || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end())
return false;
// still update state anyway
ImGui::GetIO().AddKeyEvent(iter->second, value != 0.0);
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value)
{
static constexpr ImGuiKey key_map[] = {
ImGuiKey_None, // Unknown,
ImGuiKey_GamepadDpadUp, // DPadUp
ImGuiKey_GamepadDpadRight, // DPadRight
ImGuiKey_GamepadDpadLeft, // DPadLeft
ImGuiKey_GamepadDpadDown, // DPadDown
ImGuiKey_None, // LeftStickUp
ImGuiKey_None, // LeftStickRight
ImGuiKey_None, // LeftStickDown
ImGuiKey_None, // LeftStickLeft
ImGuiKey_GamepadL3, // L3
ImGuiKey_None, // RightStickUp
ImGuiKey_None, // RightStickRight
ImGuiKey_None, // RightStickDown
ImGuiKey_None, // RightStickLeft
ImGuiKey_GamepadR3, // R3
ImGuiKey_GamepadFaceUp, // Triangle
ImGuiKey_GamepadFaceRight, // Circle
ImGuiKey_GamepadFaceDown, // Cross
ImGuiKey_GamepadFaceLeft, // Square
ImGuiKey_GamepadBack, // Select
ImGuiKey_GamepadStart, // Start
ImGuiKey_None, // System
ImGuiKey_GamepadL1, // L1
ImGuiKey_GamepadL2, // L2
ImGuiKey_GamepadR1, // R1
ImGuiKey_GamepadL2, // R2
};
if (!ImGui::GetCurrentContext() || !s_imgui_wants_keyboard.load(std::memory_order_acquire))
return false;
if (static_cast<u32>(key) >= std::size(key_map) || key_map[static_cast<u32>(key)] == ImGuiKey_None)
return false;
ImGui::GetIO().AddKeyAnalogEvent(key_map[static_cast<u32>(key)], (value > 0.0f), value);
return true;
}

85
src/util/imgui_manager.h Normal file
View File

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/types.h"
#include <string>
struct ImFont;
union InputBindingKey;
enum class GenericInputBinding : u8;
namespace ImGuiManager {
/// Sets the path to the font to use. Empty string means to use the default.
void SetFontPath(std::string path);
/// Sets the glyph range to use when loading fonts.
void SetFontRange(const u16* range);
/// Initializes ImGui, creates fonts, etc.
bool Initialize();
/// Frees all ImGui resources.
void Shutdown();
/// Updates internal state when the window is size.
void WindowResized();
/// Updates scaling of the on-screen elements.
void UpdateScale();
/// Call at the beginning of the frame to set up ImGui state.
void NewFrame();
/// Renders any on-screen display elements.
void RenderOSDMessages();
/// Returns the scale of all on-screen elements.
float GetGlobalScale();
/// Returns true if fullscreen fonts are present.
bool HasFullscreenFonts();
/// Allocates/adds fullscreen fonts if they're not loaded.
bool AddFullscreenFontsIfMissing();
/// Returns the standard font for external drawing.
ImFont* GetStandardFont();
/// Returns the fixed-width font for external drawing.
ImFont* GetFixedFont();
/// Returns the medium font for external drawing, scaled by ImGuiFullscreen.
/// This font is allocated on demand.
ImFont* GetMediumFont();
/// Returns the large font for external drawing, scaled by ImGuiFullscreen.
/// This font is allocated on demand.
ImFont* GetLargeFont();
/// Returns true if imgui wants to intercept text input.
bool WantsTextInput();
/// Called on the UI or CPU thread in response to a key press. String is UTF-8.
void AddTextInput(std::string str);
/// Called on the UI or CPU thread in response to mouse movement.
void UpdateMousePosition(float x, float y);
/// Called on the CPU thread in response to a mouse button press.
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
bool ProcessPointerButtonEvent(InputBindingKey key, float value);
/// Called on the CPU thread in response to a mouse wheel movement.
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
bool ProcessPointerAxisEvent(InputBindingKey key, float value);
/// Called on the CPU thread in response to a key press.
/// Returns true if ImGui intercepted the event, and regular handlers should not execute.
bool ProcessHostKeyEvent(InputBindingKey key, float value);
/// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation.
bool ProcessGenericInputEvent(GenericInputBinding key, float value);
} // namespace ImGuiManager

1916
src/util/input_manager.cpp Normal file

File diff suppressed because it is too large Load Diff

354
src/util/input_manager.h Normal file
View File

@ -0,0 +1,354 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include <functional>
#include <mutex>
#include <optional>
#include <string_view>
#include <utility>
#include <variant>
#include "common/settings_interface.h"
#include "common/types.h"
#include "common/window_info.h"
#include "core/input_types.h"
/// Class, or source of an input event.
enum class InputSourceType : u32
{
Keyboard,
Pointer,
Sensor,
#ifdef _WIN32
DInput,
XInput,
RawInput,
#endif
#ifdef WITH_SDL2
SDL,
#endif
#ifdef __ANDROID__
Android,
#endif
Count,
};
/// Subtype of a key for an input source.
enum class InputSubclass : u32
{
None = 0,
PointerButton = 0,
PointerAxis = 1,
ControllerButton = 0,
ControllerAxis = 1,
ControllerHat = 2,
ControllerMotor = 3,
ControllerHaptic = 4,
SensorAccelerometer = 0,
};
enum class InputModifier : u32
{
None = 0,
Negate, ///< Input * -1, gets the negative side of the axis
FullAxis, ///< (Input * 0.5) + 0.5, uses both the negative and positive side of the axis together
};
/// A composite type representing a full input key which is part of an event.
union InputBindingKey
{
struct
{
InputSourceType source_type : 4;
u32 source_index : 8; ///< controller number
InputSubclass source_subtype : 3; ///< if 1, binding is for an axis and not a button (used for controllers)
InputModifier modifier : 2;
u32 invert : 1; ///< if 1, value is inverted prior to being sent to the sink
u32 unused : 14;
u32 data;
};
u64 bits;
bool operator==(const InputBindingKey& k) const { return bits == k.bits; }
bool operator!=(const InputBindingKey& k) const { return bits != k.bits; }
/// Removes the direction bit from the key, which is used to look up the bindings for it.
/// This is because negative bindings should still fire when they reach zero again.
InputBindingKey MaskDirection() const
{
InputBindingKey r;
r.bits = bits;
r.modifier = InputModifier::None;
r.invert = 0;
return r;
}
};
static_assert(sizeof(InputBindingKey) == sizeof(u64), "Input binding key is 64 bits");
/// Hashability for InputBindingKey
struct InputBindingKeyHash
{
std::size_t operator()(const InputBindingKey& k) const { return std::hash<u64>{}(k.bits); }
};
/// Callback type for a binary event. Usually used for hotkeys.
using InputButtonEventHandler = std::function<void(s32 value)>;
/// Callback types for a normalized event. Usually used for pads.
using InputAxisEventHandler = std::function<void(float value)>;
/// ------------------------------------------------------------------------
/// Event Handler Type
/// ------------------------------------------------------------------------
/// This class acts as an adapter to convert from normalized values to
/// binary values when the callback is a binary/button handler. That way
/// you don't need to convert float->bool in your callbacks.
using InputEventHandler = std::variant<InputAxisEventHandler, InputButtonEventHandler>;
/// Input monitoring for external access.
struct InputInterceptHook
{
enum class CallbackResult
{
StopProcessingEvent,
ContinueProcessingEvent,
RemoveHookAndStopProcessingEvent,
RemoveHookAndContinueProcessingEvent,
};
using Callback = std::function<CallbackResult(InputBindingKey key, float value)>;
};
/// Hotkeys are actions (e.g. toggle frame limit) which can be bound to keys or chords.
/// The handler is called with an integer representing the key state, where 0 means that
/// one or more keys were released, 1 means all the keys were pressed, and -1 means that
/// the hotkey was cancelled due to a chord with more keys being activated.
struct HotkeyInfo
{
const char* name;
const char* category;
const char* display_name;
void (*handler)(s32 pressed);
};
#define DECLARE_HOTKEY_LIST(name) extern const HotkeyInfo name[]
#define BEGIN_HOTKEY_LIST(name) const HotkeyInfo name[] = {
#define DEFINE_HOTKEY(name, category, display_name, handler) {(name), (category), (display_name), (handler)},
#define END_HOTKEY_LIST() \
{ \
nullptr, nullptr, nullptr, nullptr \
} \
} \
;
DECLARE_HOTKEY_LIST(g_common_hotkeys);
DECLARE_HOTKEY_LIST(g_host_hotkeys);
/// Generic input bindings. These roughly match a DualShock 4 or XBox One controller.
/// They are used for automatic binding to PS2 controller types, and for big picture mode navigation.
enum class GenericInputBinding : u8;
using GenericInputBindingMapping = std::vector<std::pair<GenericInputBinding, std::string>>;
/// Host mouse relative axes are X, Y, wheel horizontal, wheel vertical.
enum class InputPointerAxis : u8
{
X,
Y,
WheelX,
WheelY,
Count
};
/// External input source class.
class InputSource;
namespace InputManager {
/// Minimum interval between vibration updates when the effect is continuous.
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
/// Maximum number of host mouse devices.
static constexpr u32 MAX_POINTER_DEVICES = 1;
/// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 4;
/// Returns a pointer to the external input source class, if present.
InputSource* GetInputSourceInterface(InputSourceType type);
/// Converts an input class to a string.
const char* InputSourceToString(InputSourceType clazz);
/// Returns the default state for an input source.
bool GetInputSourceDefaultEnabled(InputSourceType type);
/// Parses an input class string.
std::optional<InputSourceType> ParseInputSourceString(const std::string_view& str);
/// Parses a pointer device string, i.e. tells you which pointer is specified.
std::optional<u32> GetIndexFromPointerBinding(const std::string_view& str);
/// Returns the device name for a pointer index (e.g. Pointer-0).
std::string GetPointerDeviceName(u32 pointer_index);
/// Converts a key code from a human-readable string to an identifier.
std::optional<u32> ConvertHostKeyboardStringToCode(const std::string_view& str);
/// Converts a key code from an identifier to a human-readable string.
std::optional<std::string> ConvertHostKeyboardCodeToString(u32 code);
/// Creates a key for a host-specific key code.
InputBindingKey MakeHostKeyboardKey(u32 key_code);
/// Creates a key for a host-specific button.
InputBindingKey MakePointerButtonKey(u32 index, u32 button_index);
/// Creates a key for a host-specific mouse relative event
/// (axis 0 = horizontal, 1 = vertical, 2 = wheel horizontal, 3 = wheel vertical).
InputBindingKey MakePointerAxisKey(u32 index, InputPointerAxis axis);
/// Creates a key for a host-specific sensor.
InputBindingKey MakeSensorAxisKey(InputSubclass sensor, u32 axis);
/// Parses an input binding key string.
std::optional<InputBindingKey> ParseInputBindingKey(const std::string_view& binding);
/// Converts a input key to a string.
std::string ConvertInputBindingKeyToString(InputBindingInfo::Type binding_type, InputBindingKey key);
/// Converts a chord of binding keys to a string.
std::string ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, const InputBindingKey* keys,
size_t num_keys);
/// Returns a list of all hotkeys.
std::vector<const HotkeyInfo*> GetHotkeyList();
/// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name.
std::vector<std::pair<std::string, std::string>> EnumerateDevices();
/// Enumerates available vibration motors at the time of call.
std::vector<InputBindingKey> EnumerateMotors();
/// Retrieves bindings that match the generic bindings for the specified device.
GenericInputBindingMapping GetGenericBindingMapping(const std::string_view& device);
/// Returns true if the specified input source is enabled.
bool IsInputSourceEnabled(SettingsInterface& si, InputSourceType type);
/// Re-parses the config and registers all hotkey and pad bindings.
void ReloadBindings(SettingsInterface& si, SettingsInterface& binding_si);
/// Migrates any bindings from the pre-InputManager configuration.
bool MigrateBindings(SettingsInterface& si);
/// Re-parses the sources part of the config and initializes any backends.
void ReloadSources(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock);
/// Called when a device change is triggered by the system (DBT_DEVNODES_CHANGED on Windows).
/// Returns true if any device changes are detected.
bool ReloadDevices();
/// Shuts down any enabled input sources.
void CloseSources();
/// Polls input sources for events (e.g. external controllers).
void PollSources();
/// Returns true if any bindings exist for the specified key.
/// Can be safely called on another thread.
bool HasAnyBindingsForKey(InputBindingKey key);
/// Returns true if any bindings exist for the specified source + index.
/// Can be safely called on another thread.
bool HasAnyBindingsForSource(InputBindingKey key);
/// Parses a string binding into its components. Use with external AddBinding().
bool ParseBindingAndGetSource(const std::string_view& binding, InputBindingKey* key, InputSource** source);
/// Externally adds a fixed binding. Be sure to call *after* ReloadBindings() otherwise it will be lost.
void AddBinding(const std::string_view& binding, const InputEventHandler& handler);
/// Adds an external vibration binding.
void AddVibrationBinding(u32 pad_index, const InputBindingKey* motor_0_binding, InputSource* motor_0_source,
const InputBindingKey* motor_1_binding, InputSource* motor_1_source);
/// Updates internal state for any binds for this key, and fires callbacks as needed.
/// Returns true if anything was bound to this key, otherwise false.
bool InvokeEvents(InputBindingKey key, float value, GenericInputBinding generic_key = GenericInputBinding::Unknown);
/// Clears internal state for any binds with a matching source/index.
void ClearBindStateFromSource(InputBindingKey key);
/// Sets a hook which can be used to intercept events before they're processed by the normal bindings.
/// This is typically used when binding new controls to detect what gets pressed.
void SetHook(InputInterceptHook::Callback callback);
/// Removes any currently-active interception hook.
void RemoveHook();
/// Returns true if there is an interception hook present.
bool HasHook();
/// Internal method used by pads to dispatch vibration updates to input sources.
/// Intensity is normalized from 0 to 1.
void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensity, float small_motor_intensity);
/// Zeros all vibration intensities. Call when pausing.
/// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes.
void PauseVibration();
/// Updates absolute pointer position. Can call from UI thread, use when the host only reports absolute coordinates.
void UpdatePointerAbsolutePosition(u32 index, float x, float y);
/// Updates relative pointer position. Can call from the UI thread, use when host supports relative coordinate
/// reporting.
void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false);
/// Sets the state of the specified macro button.
void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns true if the raw input source is being used.
bool IsUsingRawInput();
/// Returns true if any bindings are present which require relative mouse movement.
bool HasPointerAxisBinds();
/// Restores default configuration.
void SetDefaultConfig(SettingsInterface& si);
/// Clears all bindings for a given port.
void ClearPortBindings(SettingsInterface& si, u32 port);
/// Copies pad configuration from one interface (ini) to another.
void CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si, bool copy_pad_config = true,
bool copy_pad_bindings = true, bool copy_hotkey_bindings = true);
/// Performs automatic controller mapping with the provided list of generic mappings.
bool MapController(SettingsInterface& si, u32 controller,
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping);
/// Returns a list of input profiles available.
std::vector<std::string> GetInputProfileNames();
/// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name);
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier);
} // namespace InputManager
namespace Host {
/// Return the current window handle. Needed for DInput.
std::optional<WindowInfo> GetTopLevelWindowInfo();
/// Called when a new input device is connected.
void OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name);
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(const std::string_view& identifier);
} // namespace Host

156
src/util/input_source.cpp Normal file
View File

@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "input_source.h"
#include "common/string_util.h"
InputSource::InputSource() = default;
InputSource::~InputSource() = default;
void InputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity)
{
if (large_key.bits != 0)
UpdateMotorState(large_key, large_intensity);
if (small_key.bits != 0)
UpdateMotorState(small_key, small_intensity);
}
InputBindingKey InputSource::MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = static_cast<u32>(axis_index);
return key;
}
InputBindingKey InputSource::MakeGenericControllerButtonKey(InputSourceType clazz, u32 controller_index,
s32 button_index)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::ControllerButton;
key.data = static_cast<u32>(button_index);
return key;
}
InputBindingKey InputSource::MakeGenericControllerHatKey(InputSourceType clazz, u32 controller_index, s32 hat_index,
u8 hat_direction, u32 num_directions)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::ControllerHat;
key.data = static_cast<u32>(hat_index) * num_directions + hat_direction;
return key;
}
InputBindingKey InputSource::MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::ControllerMotor;
key.data = static_cast<u32>(motor_index);
return key;
}
std::optional<InputBindingKey> InputSource::ParseGenericControllerKey(InputSourceType clazz,
const std::string_view& source,
const std::string_view& sub_binding)
{
// try to find the number, this function doesn't care about whether it's xinput or sdl or whatever
std::string_view::size_type pos = 0;
while (pos < source.size())
{
if (source[pos] >= '0' && source[pos] <= '9')
break;
pos++;
}
if (pos == source.size())
return std::nullopt;
const std::optional<s32> source_index = StringUtil::FromChars<s32>(source.substr(pos));
if (source_index.has_value() || source_index.value() < 0)
return std::nullopt;
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = source_index.value();
if (StringUtil::StartsWith(sub_binding, "+Axis") || StringUtil::StartsWith(sub_binding, "-Axis"))
{
const std::optional<s32> axis_number = StringUtil::FromChars<s32>(sub_binding.substr(5));
if (!axis_number.has_value() || axis_number.value() < 0)
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = static_cast<u32>(axis_number.value());
if (sub_binding[0] == '+')
key.modifier = InputModifier::None;
else if (sub_binding[0] == '-')
key.modifier = InputModifier::Negate;
else
return std::nullopt;
}
else if (StringUtil::StartsWith(sub_binding, "FullAxis"))
{
const std::optional<s32> axis_number = StringUtil::FromChars<s32>(sub_binding.substr(8));
if (!axis_number.has_value() || axis_number.value() < 0)
return std::nullopt;
key.source_subtype = InputSubclass::ControllerAxis;
key.data = static_cast<u32>(axis_number.value());
key.modifier = InputModifier::FullAxis;
}
else if (StringUtil::StartsWith(sub_binding, "Button"))
{
const std::optional<s32> button_number = StringUtil::FromChars<s32>(sub_binding.substr(6));
if (!button_number.has_value() || button_number.value() < 0)
return std::nullopt;
key.source_subtype = InputSubclass::ControllerButton;
key.data = static_cast<u32>(button_number.value());
}
else
{
return std::nullopt;
}
return key;
}
std::string InputSource::ConvertGenericControllerKeyToString(InputBindingKey key)
{
if (key.source_subtype == InputSubclass::ControllerAxis)
{
const char* modifier = "";
switch (key.modifier)
{
case InputModifier::None:
modifier = "+";
break;
case InputModifier::Negate:
modifier = "-";
break;
case InputModifier::FullAxis:
modifier = "Full";
break;
}
return StringUtil::StdStringFromFormat("%s-%u/%sAxis%u", InputManager::InputSourceToString(key.source_type),
key.source_index, modifier, key.data);
}
else if (key.source_subtype == InputSubclass::ControllerButton)
{
return StringUtil::StdStringFromFormat("%s%u/Button%u", InputManager::InputSourceToString(key.source_type),
key.source_index, key.data);
}
else
{
return {};
}
}

86
src/util/input_source.h Normal file
View File

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include <memory>
#include <mutex>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
#include "common/types.h"
#include "input_manager.h"
class SettingsInterface;
class InputSource
{
public:
InputSource();
virtual ~InputSource();
virtual bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) = 0;
virtual void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) = 0;
virtual bool ReloadDevices() = 0;
virtual void Shutdown() = 0;
virtual void PollEvents() = 0;
virtual std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
const std::string_view& binding) = 0;
virtual std::string ConvertKeyToString(InputBindingKey key) = 0;
/// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name.
virtual std::vector<std::pair<std::string, std::string>> EnumerateDevices() = 0;
/// Enumerates available vibration motors at the time of call.
virtual std::vector<InputBindingKey> EnumerateMotors() = 0;
/// Retrieves bindings that match the generic bindings for the specified device.
/// Returns false if it's not one of our devices.
virtual bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) = 0;
/// Informs the source of a new vibration motor state. Changes may not take effect until the next PollEvents() call.
virtual void UpdateMotorState(InputBindingKey key, float intensity) = 0;
/// Concurrently update both motors where possible, to avoid redundant packets.
virtual void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity);
/// Creates a key for a generic controller axis event.
static InputBindingKey MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index);
/// Creates a key for a generic controller button event.
static InputBindingKey MakeGenericControllerButtonKey(InputSourceType clazz, u32 controller_index, s32 button_index);
/// Creates a key for a generic controller hat event.
static InputBindingKey MakeGenericControllerHatKey(InputSourceType clazz, u32 controller_index, s32 hat_index,
u8 hat_direction, u32 num_directions);
/// Creates a key for a generic controller motor event.
static InputBindingKey MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index);
/// Parses a generic controller key string.
static std::optional<InputBindingKey> ParseGenericControllerKey(InputSourceType clazz, const std::string_view& source,
const std::string_view& sub_binding);
/// Converts a generic controller key to a string.
static std::string ConvertGenericControllerKeyToString(InputBindingKey key);
#ifdef _WIN32
static std::unique_ptr<InputSource> CreateDInputSource();
static std::unique_ptr<InputSource> CreateXInputSource();
static std::unique_ptr<InputSource> CreateWin32RawInputSource();
#endif
#ifdef WITH_SDL2
static std::unique_ptr<InputSource> CreateSDLSource();
#endif
#ifdef WITH_EVDEV
static std::unique_ptr<InputSource> CreateEvdevSource();
#endif
#ifdef __ANDROID__
static std::unique_ptr<InputSource> CreateAndroidSource();
#endif
};

View File

@ -0,0 +1,3 @@
fxc /T vs_4_0 /E main /O3 /Fh display_vs.hlsl.h /Vn "static s_display_vs_bytecode" display_vs.hlsl
fxc /T ps_4_0 /E main /O3 /Fh display_ps.hlsl.h /Vn "static s_display_ps_bytecode" display_ps.hlsl
fxc /T ps_4_0 /E main /O3 /D ALPHA=1 /Fh display_ps_alpha.hlsl.h /Vn "static s_display_ps_alpha_bytecode" display_ps.hlsl

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/gl/context.h"
#include "common/gl/loader.h"
#include "common/gl/program.h"
#include "common/gl/stream_buffer.h"
#include "common/gl/texture.h"
#include "common/timer.h"
#include "common/window_info.h"
#include "host_display.h"
#include "postprocessing_chain.h"
#include <memory>
class OpenGLHostDisplay final : public HostDisplay
{
public:
OpenGLHostDisplay();
~OpenGLHostDisplay();
RenderAPI GetRenderAPI() const override;
void* GetDevice() const override;
void* GetContext() const override;
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool 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) 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;
bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Format format, const void* data, u32 data_stride,
bool dynamic = false) override;
bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override;
void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override;
bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch) override;
bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void SetVSync(bool enabled) override;
bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
ALWAYS_INLINE GL::Context* GetGLContext() const { return m_gl_context.get(); }
ALWAYS_INLINE bool UsePBOForUploads() const { return m_use_pbo_for_pixels; }
ALWAYS_INLINE bool UseGLES3DrawPath() const { return m_use_gles2_draw_path; }
ALWAYS_INLINE std::vector<u8>& GetTextureRepackBuffer() { return m_texture_repack_buffer; }
GL::StreamBuffer* GetTextureStreamBuffer();
protected:
static constexpr u8 NUM_TIMESTAMP_QUERIES = 3;
const char* GetGLSLVersionString() const;
std::string GetGLSLVersionHeader() const;
bool CreateResources() override;
void DestroyResources() override;
bool CreateImGuiContext() override;
void DestroyImGuiContext() override;
bool UpdateImGuiFontTexture() override;
void SetSwapInterval();
void RenderDisplay();
void RenderImGui();
void RenderSoftwareCursor();
void RenderDisplay(s32 left, s32 bottom, s32 width, s32 height, GL::Texture* texture, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 bottom, s32 width, s32 height, GPUTexture* texture_handle);
struct PostProcessingStage
{
GL::Program program;
GL::Texture output_texture;
u32 uniforms_size;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(GLuint final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height,
GL::Texture* texture, s32 texture_view_x, s32 texture_view_y, s32 texture_view_width,
s32 texture_view_height, u32 target_width, u32 target_height);
void CreateTimestampQueries();
void DestroyTimestampQueries();
void PopTimestampQuery();
void KickTimestampQuery();
std::unique_ptr<GL::Context> m_gl_context;
GL::Program m_display_program;
GL::Program m_cursor_program;
GLuint m_display_vao = 0;
GLuint m_display_nearest_sampler = 0;
GLuint m_display_linear_sampler = 0;
GLuint m_display_border_sampler = 0;
GLuint m_uniform_buffer_alignment = 1;
std::unique_ptr<GL::StreamBuffer> m_texture_stream_buffer;
std::vector<u8> m_texture_repack_buffer;
u32 m_texture_stream_buffer_offset = 0;
FrontendCommon::PostProcessingChain m_post_processing_chain;
GL::Texture m_post_processing_input_texture;
std::unique_ptr<GL::StreamBuffer> m_post_processing_ubo;
std::vector<PostProcessingStage> m_post_processing_stages;
Common::Timer m_post_processing_timer;
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_use_gles2_draw_path = false;
bool m_use_pbo_for_pixels = false;
};

20
src/util/platform_misc.h Normal file
View File

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "common/window_info.h"
namespace FrontendCommon {
void SuspendScreensaver();
void ResumeScreensaver();
/// Abstracts platform-specific code for asynchronously playing a sound.
/// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound.
bool PlaySoundAsync(const char* path);
#ifdef __APPLE__
/// Add a handler to be run when macOS changes between dark and light themes
void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx));
/// Remove a handler previously added using AddThemeChangeHandler with the given context
void RemoveThemeChangeHandler(void* ctx);
#endif
} // namespace FrontendCommon

View File

@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "platform_misc.h"
#include "common/log.h"
#include "common/string.h"
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <Cocoa/Cocoa.h>
#include <QuartzCore/QuartzCore.h>
#include <cinttypes>
#include <vector>
Log_SetChannel(FrontendCommon);
#import <AppKit/AppKit.h>
static IOPMAssertionID s_prevent_idle_assertion = kIOPMNullAssertionID;
static bool SetScreensaverInhibitMacOS(bool inhibit)
{
if (inhibit)
{
const CFStringRef reason = CFSTR("System Running");
if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason,
&s_prevent_idle_assertion) != kIOReturnSuccess)
{
Log_ErrorPrintf("IOPMAssertionCreateWithName() failed");
return false;
}
return true;
}
else
{
IOPMAssertionRelease(s_prevent_idle_assertion);
s_prevent_idle_assertion = kIOPMNullAssertionID;
return true;
}
}
static bool s_screensaver_suspended;
void FrontendCommon::SuspendScreensaver()
{
if (s_screensaver_suspended)
if (!SetScreensaverInhibitMacOS(true))
{
Log_ErrorPrintf("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void FrontendCommon::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibitMacOS(false))
Log_ErrorPrint("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool FrontendCommon::PlaySoundAsync(const char* path)
{
NSString* nspath = [[NSString alloc] initWithUTF8String:path];
NSSound* sound = [[NSSound alloc] initWithContentsOfFile:nspath byReference:YES];
const bool result = [sound play];
[sound release];
[nspath release];
return result;
}
// From https://github.com/PCSX2/pcsx2/blob/1b673d9dd0829a48f5f0b6604c1de2108e981399/common/CocoaTools.mm
@interface PCSX2KVOHelper : NSObject
- (void)addCallback:(void*)ctx run:(void(*)(void*))callback;
- (void)removeCallback:(void*)ctx;
@end
@implementation PCSX2KVOHelper
{
std::vector<std::pair<void*, void(*)(void*)>> _callbacks;
}
- (void)addCallback:(void*)ctx run:(void(*)(void*))callback
{
_callbacks.push_back(std::make_pair(ctx, callback));
}
- (void)removeCallback:(void*)ctx
{
auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){
return ctx == entry.first;
});
_callbacks.erase(new_end, _callbacks.end());
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
for (const auto& callback : _callbacks)
callback.second(callback.first);
}
@end
static PCSX2KVOHelper* s_themeChangeHandler;
void FrontendCommon::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx))
{
assert([NSThread isMainThread]);
if (!s_themeChangeHandler)
{
s_themeChangeHandler = [[PCSX2KVOHelper alloc] init];
NSApplication* app = [NSApplication sharedApplication];
[app addObserver:s_themeChangeHandler
forKeyPath:@"effectiveAppearance"
options:0
context:nil];
}
[s_themeChangeHandler addCallback:ctx run:handler];
}
void FrontendCommon::RemoveThemeChangeHandler(void* ctx)
{
assert([NSThread isMainThread]);
[s_themeChangeHandler removeCallback:ctx];
}

View File

@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "common/log.h"
#include "common/scoped_guard.h"
#include "common/string.h"
#include "input_manager.h"
#include "platform_misc.h"
#include <cinttypes>
Log_SetChannel(FrontendCommon);
#include <spawn.h>
#include <unistd.h>
#if !defined(USE_DBUS) && defined(USE_X11)
#include <cstdio>
#include <sys/wait.h>
static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi)
{
TinyString command;
command.AppendString("xdg-screensaver");
TinyString operation;
operation.AppendString(inhibit ? "suspend" : "resume");
TinyString id;
id.Format("0x%" PRIx64, static_cast<u64>(reinterpret_cast<uintptr_t>(wi.window_handle)));
char* argv[4] = {command.GetWriteableCharArray(), operation.GetWriteableCharArray(), id.GetWriteableCharArray(),
nullptr};
pid_t pid;
int res = posix_spawnp(&pid, "xdg-screensaver", nullptr, nullptr, argv, environ);
if (res != 0)
{
Log_ErrorPrintf("posix_spawnp() failed: %d", res);
return false;
}
return true;
}
#elif defined(USE_DBUS)
#include <dbus/dbus.h>
static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason)
{
static dbus_uint32_t s_cookie;
const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit";
DBusError error;
DBusConnection* connection = nullptr;
static DBusConnection* s_comparison_connection;
DBusMessage* message = nullptr;
DBusMessage* response = nullptr;
DBusMessageIter message_itr;
ScopedGuard cleanup = [&]() {
if (dbus_error_is_set(&error))
{
Log_ErrorPrintf("SetScreensaverInhibitDBus error: %s", error.message);
dbus_error_free(&error);
}
if (message)
dbus_message_unref(message);
if (response)
dbus_message_unref(response);
};
dbus_error_init(&error);
// Calling dbus_bus_get() after the first time returns a pointer to the existing connection.
connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (!connection || (dbus_error_is_set(&error)))
return false;
if (s_comparison_connection != connection)
{
dbus_connection_set_exit_on_disconnect(connection, false);
s_cookie = 0;
s_comparison_connection = connection;
}
message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver", bus_method);
if (!message)
return false;
// Initialize an append iterator for the message, gets freed with the message.
dbus_message_iter_init_append(message, &message_itr);
if (inhibit_requested)
{
// Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie.
if (s_cookie)
return false;
// Append process/window name.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name))
return false;
// Append reason for inhibiting the screensaver.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason))
return false;
}
else
{
// Only Append the cookie.
if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie))
return false;
}
// Send message and get response.
response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error);
if (!response || dbus_error_is_set(&error))
return false;
s_cookie = 0;
if (inhibit_requested)
{
// Get the cookie from the response message.
if (!dbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) ||
dbus_error_is_set(&error))
return false;
}
return true;
}
#endif
static bool SetScreensaverInhibit(bool inhibit)
{
#ifdef USE_DBUS
return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running.");
#else
std::optional<WindowInfo> wi(Host::GetTopLevelWindowInfo());
if (!wi.has_value())
{
Log_ErrorPrintf("No top-level window.");
return false;
}
switch (wi->type)
{
#ifdef USE_X11
case WindowInfo::Type::X11:
return SetScreensaverInhibitX11(inhibit, wi.value());
#endif
default:
Log_ErrorPrintf("Unknown type: %u", static_cast<unsigned>(wi->type));
return false;
}
#endif
}
static bool s_screensaver_suspended;
void FrontendCommon::SuspendScreensaver()
{
if (s_screensaver_suspended)
return;
if (!SetScreensaverInhibit(true))
{
Log_ErrorPrintf("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void FrontendCommon::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibit(false))
Log_ErrorPrint("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool FrontendCommon::PlaySoundAsync(const char* path)
{
#ifdef __linux__
// This is... pretty awful. But I can't think of a better way without linking to e.g. gstreamer.
const char* cmdname = "aplay";
const char* argv[] = {cmdname, path, nullptr};
pid_t pid;
// Since we set SA_NOCLDWAIT in Qt, we don't need to wait here.
int res = posix_spawnp(&pid, cmdname, nullptr, nullptr, const_cast<char**>(argv), environ);
return (res == 0);
#else
return false;
#endif
}

View File

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "common/log.h"
#include "common/string.h"
#include "common/string_util.h"
#include "platform_misc.h"
#include <cinttypes>
Log_SetChannel(FrontendCommon);
#include "common/windows_headers.h"
#include <mmsystem.h>
static bool SetScreensaverInhibitWin32(bool inhibit)
{
if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL)
{
Log_ErrorPrintf("SetThreadExecutionState() failed: %d", GetLastError());
return false;
}
return true;
}
static bool s_screensaver_suspended;
void FrontendCommon::SuspendScreensaver()
{
if (s_screensaver_suspended)
return;
if (!SetScreensaverInhibitWin32(true))
{
Log_ErrorPrintf("Failed to suspend screensaver.");
return;
}
s_screensaver_suspended = true;
}
void FrontendCommon::ResumeScreensaver()
{
if (!s_screensaver_suspended)
return;
if (!SetScreensaverInhibitWin32(false))
Log_ErrorPrint("Failed to resume screensaver.");
s_screensaver_suspended = false;
}
bool FrontendCommon::PlaySoundAsync(const char* path)
{
const std::wstring wpath(StringUtil::UTF8StringToWideString(path));
return PlaySoundW(wpath.c_str(), NULL, SND_ASYNC | SND_NODEFAULT);
}

View File

@ -0,0 +1,185 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "postprocessing_chain.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string.h"
#include "common/path.h"
#include "core/host.h"
#include "core/settings.h"
#include "fmt/format.h"
#include <sstream>
Log_SetChannel(PostProcessingChain);
namespace FrontendCommon {
static bool TryLoadingShader(PostProcessingShader* shader, const std::string_view& shader_name)
{
std::string filename(Path::Combine(EmuFolders::Shaders, fmt::format("{}.glsl", shader_name)));
if (FileSystem::FileExists(filename.c_str()))
{
if (shader->LoadFromFile(std::string(shader_name), filename.c_str()))
return true;
}
std::optional<std::string> resource_str(Host::ReadResourceFileToString(fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name).c_str()));
if (resource_str.has_value() && shader->LoadFromString(std::string(shader_name), std::move(resource_str.value())))
return true;
Log_ErrorPrintf("Failed to load shader from '%s'", filename.c_str());
return false;
}
PostProcessingChain::PostProcessingChain() = default;
PostProcessingChain::~PostProcessingChain() = default;
void PostProcessingChain::AddShader(PostProcessingShader shader)
{
m_shaders.push_back(std::move(shader));
}
bool PostProcessingChain::AddStage(const std::string_view& name)
{
PostProcessingShader shader;
if (!TryLoadingShader(&shader, name))
return false;
m_shaders.push_back(std::move(shader));
return true;
}
std::string PostProcessingChain::GetConfigString() const
{
std::stringstream ss;
bool first = true;
for (const PostProcessingShader& shader : m_shaders)
{
if (!first)
ss << ':';
else
first = false;
ss << shader.GetName();
std::string config_string = shader.GetConfigString();
if (!config_string.empty())
ss << ';' << config_string;
}
return ss.str();
}
bool PostProcessingChain::CreateFromString(const std::string_view& chain_config)
{
std::vector<PostProcessingShader> shaders;
size_t last_sep = 0;
while (last_sep < chain_config.size())
{
size_t next_sep = chain_config.find(':', last_sep);
if (next_sep == std::string::npos)
next_sep = chain_config.size();
const std::string_view shader_config = chain_config.substr(last_sep, next_sep - last_sep);
size_t first_shader_sep = shader_config.find(';');
if (first_shader_sep == std::string::npos)
first_shader_sep = shader_config.size();
const std::string_view shader_name = shader_config.substr(0, first_shader_sep);
if (!shader_name.empty())
{
PostProcessingShader shader;
if (!TryLoadingShader(&shader, shader_name))
return false;
if (first_shader_sep < shader_config.size())
shader.SetConfigString(shader_config.substr(first_shader_sep + 1));
shaders.push_back(std::move(shader));
}
last_sep = next_sep + 1;
}
if (shaders.empty())
{
Log_ErrorPrintf("Postprocessing chain is empty!");
return false;
}
m_shaders = std::move(shaders);
Log_InfoPrintf("Loaded postprocessing chain of %zu shaders", m_shaders.size());
return true;
}
std::vector<std::string> PostProcessingChain::GetAvailableShaderNames()
{
std::vector<std::string> names;
FileSystem::FindResultsArray results;
FileSystem::FindFiles(Path::Combine(EmuFolders::Resources, "shaders").c_str(), "*.glsl",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results);
FileSystem::FindFiles(EmuFolders::Shaders.c_str(), "*.glsl",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS |
FILESYSTEM_FIND_KEEP_ARRAY,
&results);
for (FILESYSTEM_FIND_DATA& fd : results)
{
size_t pos = fd.FileName.rfind('.');
if (pos != std::string::npos && pos > 0)
fd.FileName.erase(pos);
// swap any backslashes for forward slashes so the config is cross-platform
for (size_t i = 0; i < fd.FileName.size(); i++)
{
if (fd.FileName[i] == '\\')
fd.FileName[i] = '/';
}
if (std::none_of(names.begin(), names.end(), [&fd](const std::string& other) { return fd.FileName == other; }))
{
names.push_back(std::move(fd.FileName));
}
}
return names;
}
void PostProcessingChain::RemoveStage(u32 index)
{
Assert(index < m_shaders.size());
m_shaders.erase(m_shaders.begin() + index);
}
void PostProcessingChain::MoveStageUp(u32 index)
{
Assert(index < m_shaders.size());
if (index == 0)
return;
PostProcessingShader shader = std::move(m_shaders[index]);
m_shaders.erase(m_shaders.begin() + index);
m_shaders.insert(m_shaders.begin() + (index - 1u), std::move(shader));
}
void PostProcessingChain::MoveStageDown(u32 index)
{
Assert(index < m_shaders.size());
if (index == (m_shaders.size() - 1u))
return;
PostProcessingShader shader = std::move(m_shaders[index]);
m_shaders.erase(m_shaders.begin() + index);
m_shaders.insert(m_shaders.begin() + (index + 1u), std::move(shader));
}
void PostProcessingChain::ClearStages()
{
m_shaders.clear();
}
} // namespace FrontendCommon

View File

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "postprocessing_shader.h"
#include <string_view>
#include <vector>
namespace FrontendCommon {
class PostProcessingChain
{
public:
PostProcessingChain();
~PostProcessingChain();
ALWAYS_INLINE bool IsEmpty() const { return m_shaders.empty(); }
ALWAYS_INLINE u32 GetStageCount() const { return static_cast<u32>(m_shaders.size()); }
ALWAYS_INLINE const PostProcessingShader& GetShaderStage(u32 i) const { return m_shaders[i]; }
ALWAYS_INLINE PostProcessingShader& GetShaderStage(u32 i) { return m_shaders[i]; }
void AddShader(PostProcessingShader shader);
bool AddStage(const std::string_view& name);
void RemoveStage(u32 index);
void MoveStageUp(u32 index);
void MoveStageDown(u32 index);
void ClearStages();
std::string GetConfigString() const;
bool CreateFromString(const std::string_view& chain_config);
static std::vector<std::string> GetAvailableShaderNames();
private:
std::vector<PostProcessingShader> m_shaders;
};
} // namespace FrontendCommon

View File

@ -0,0 +1,438 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "postprocessing_shader.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include "shadergen.h"
#include <cctype>
#include <cstring>
#include <sstream>
Log_SetChannel(PostProcessingShader);
namespace FrontendCommon {
void ParseKeyValue(const std::string_view& line, std::string_view* key, std::string_view* value)
{
size_t key_start = 0;
while (key_start < line.size() && std::isspace(line[key_start]))
key_start++;
size_t key_end = key_start;
while (key_end < line.size() && (!std::isspace(line[key_end]) && line[key_end] != '='))
key_end++;
if (key_start == key_end || key_end == line.size())
return;
size_t value_start = key_end;
while (value_start < line.size() && std::isspace(line[value_start]))
value_start++;
if (value_start == line.size() || line[value_start] != '=')
return;
value_start++;
while (value_start < line.size() && std::isspace(line[value_start]))
value_start++;
size_t value_end = line.size();
while (value_end > value_start && std::isspace(line[value_end - 1]))
value_end--;
if (value_start == value_end)
return;
*key = line.substr(key_start, key_end - key_start);
*value = line.substr(value_start, value_end - value_start);
}
template<typename T>
u32 ParseVector(const std::string_view& line, PostProcessingShader::Option::ValueVector* values)
{
u32 index = 0;
size_t start = 0;
while (index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS)
{
while (start < line.size() && std::isspace(line[start]))
start++;
if (start >= line.size())
break;
size_t end = line.find(',', start);
if (end == std::string_view::npos)
end = line.size();
const std::string_view component = line.substr(start, end - start);
T value = StringUtil::FromChars<T>(component).value_or(static_cast<T>(0));
if constexpr (std::is_same_v<T, float>)
(*values)[index++].float_value = value;
else if constexpr (std::is_same_v<T, s32>)
(*values)[index++].int_value = value;
start = end + 1;
}
const u32 size = index;
for (; index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; index++)
{
if constexpr (std::is_same_v<T, float>)
(*values)[index++].float_value = 0.0f;
else if constexpr (std::is_same_v<T, s32>)
(*values)[index++].int_value = 0;
}
return size;
}
PostProcessingShader::PostProcessingShader() = default;
PostProcessingShader::PostProcessingShader(std::string name, std::string code) : m_name(name), m_code(code)
{
LoadOptions();
}
PostProcessingShader::PostProcessingShader(const PostProcessingShader& copy)
: m_name(copy.m_name), m_code(copy.m_code), m_options(copy.m_options)
{
}
PostProcessingShader::PostProcessingShader(PostProcessingShader& move)
: m_name(std::move(move.m_name)), m_code(std::move(move.m_code)), m_options(std::move(move.m_options))
{
}
PostProcessingShader::~PostProcessingShader() = default;
bool PostProcessingShader::LoadFromFile(std::string name, const char* filename)
{
std::optional<std::string> code = FileSystem::ReadFileToString(filename);
if (!code.has_value() || code->empty())
return false;
return LoadFromString(std::move(name), code.value());
}
bool PostProcessingShader::LoadFromString(std::string name, std::string code)
{
m_name = std::move(name);
m_code = std::move(code);
m_options.clear();
LoadOptions();
return true;
}
bool PostProcessingShader::IsValid() const
{
return !m_name.empty() && !m_code.empty();
}
const PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name) const
{
for (const Option& option : m_options)
{
if (option.name == name)
return &option;
}
return nullptr;
}
FrontendCommon::PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name)
{
for (Option& option : m_options)
{
if (option.name == name)
return &option;
}
return nullptr;
}
std::string PostProcessingShader::GetConfigString() const
{
std::stringstream ss;
bool first = true;
for (const Option& option : m_options)
{
if (!first)
ss << ';';
else
first = false;
ss << option.name;
ss << '=';
for (u32 i = 0; i < option.vector_size; i++)
{
if (i > 0)
ss << ",";
switch (option.type)
{
case Option::Type::Bool:
ss << ((option.value[i].int_value != 0) ? "true" : "false");
break;
case Option::Type::Int:
ss << option.value[i].int_value;
break;
case Option::Type::Float:
ss << option.value[i].float_value;
break;
default:
break;
}
}
}
return ss.str();
}
void PostProcessingShader::SetConfigString(const std::string_view& str)
{
for (Option& option : m_options)
option.value = option.default_value;
size_t last_sep = 0;
while (last_sep < str.size())
{
size_t next_sep = str.find(';', last_sep);
if (next_sep == std::string_view::npos)
next_sep = str.size();
const std::string_view kv = str.substr(last_sep, next_sep - last_sep);
std::string_view key, value;
ParseKeyValue(kv, &key, &value);
if (!key.empty() && !value.empty())
{
Option* option = GetOptionByName(key);
if (option)
{
switch (option->type)
{
case Option::Type::Bool:
option->value[0].int_value = StringUtil::FromChars<bool>(value).value_or(false) ? 1 : 0;
break;
case Option::Type::Int:
ParseVector<s32>(value, &option->value);
break;
case Option::Type::Float:
ParseVector<float>(value, &option->value);
break;
default:
break;
}
}
}
last_sep = next_sep + 1;
}
}
bool PostProcessingShader::UsePushConstants() const
{
return GetUniformsSize() <= PUSH_CONSTANT_SIZE_THRESHOLD;
}
u32 PostProcessingShader::GetUniformsSize() const
{
// lazy packing. todo improve.
return sizeof(CommonUniforms) + (sizeof(Option::ValueVector) * static_cast<u32>(m_options.size()));
}
void PostProcessingShader::FillUniformBuffer(void* buffer, u32 texture_width, s32 texture_height, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height,
u32 window_width, u32 window_height, s32 original_width,
s32 original_height, float time) const
{
CommonUniforms* common = static_cast<CommonUniforms*>(buffer);
const float rcp_texture_width = 1.0f / static_cast<float>(texture_width);
const float rcp_texture_height = 1.0f / static_cast<float>(texture_height);
common->src_rect[0] = static_cast<float>(texture_view_x) * rcp_texture_width;
common->src_rect[1] = static_cast<float>(texture_view_y) * rcp_texture_height;
common->src_rect[2] = (static_cast<float>(texture_view_x + texture_view_width - 1)) * rcp_texture_width;
common->src_rect[3] = (static_cast<float>(texture_view_y + texture_view_height - 1)) * rcp_texture_height;
common->src_size[0] = (static_cast<float>(texture_view_width)) * rcp_texture_width;
common->src_size[1] = (static_cast<float>(texture_view_height)) * rcp_texture_height;
common->resolution[0] = static_cast<float>(texture_width);
common->resolution[1] = static_cast<float>(texture_height);
common->rcp_resolution[0] = rcp_texture_width;
common->rcp_resolution[1] = rcp_texture_height;
common->window_resolution[0] = static_cast<float>(window_width);
common->window_resolution[1] = static_cast<float>(window_height);
common->rcp_window_resolution[0] = 1.0f / static_cast<float>(window_width);
common->rcp_window_resolution[1] = 1.0f / static_cast<float>(window_height);
// pad the "original size" relative to the positioning on the screen
const float view_scale_x = static_cast<float>(original_width) / static_cast<float>(texture_view_width);
const float view_scale_y = static_cast<float>(original_height) / static_cast<float>(texture_view_height);
const s32 view_pad_x = texture_view_x + (texture_width - texture_view_width - texture_view_x);
const s32 view_pad_y = texture_view_y + (texture_height - texture_view_height - texture_view_y);
common->original_size[0] = static_cast<float>(original_width);
common->original_size[1] = static_cast<float>(original_height);
common->padded_original_size[0] = common->original_size[0] + static_cast<float>(view_pad_x) * view_scale_x;
common->padded_original_size[1] = common->original_size[1] + static_cast<float>(view_pad_y) * view_scale_y;
common->time = time;
u8* option_values = reinterpret_cast<u8*>(common + 1);
for (const Option& option : m_options)
{
std::memcpy(option_values, option.value.data(), sizeof(Option::ValueVector));
option_values += sizeof(Option::ValueVector);
}
}
FrontendCommon::PostProcessingShader& PostProcessingShader::operator=(const PostProcessingShader& copy)
{
m_name = copy.m_name;
m_code = copy.m_code;
m_options = copy.m_options;
return *this;
}
FrontendCommon::PostProcessingShader& PostProcessingShader::operator=(PostProcessingShader& move)
{
m_name = std::move(move.m_name);
m_code = std::move(move.m_code);
m_options = std::move(move.m_options);
return *this;
}
void PostProcessingShader::LoadOptions()
{
// Adapted from Dolphin's PostProcessingConfiguration::LoadOptions().
constexpr char config_start_delimiter[] = "[configuration]";
constexpr char config_end_delimiter[] = "[/configuration]";
size_t configuration_start = m_code.find(config_start_delimiter);
size_t configuration_end = m_code.find(config_end_delimiter);
if (configuration_start == std::string::npos || configuration_end == std::string::npos)
{
// Issue loading configuration or there isn't one.
return;
}
std::string configuration_string =
m_code.substr(configuration_start + std::strlen(config_start_delimiter),
configuration_end - configuration_start - std::strlen(config_start_delimiter));
std::istringstream in(configuration_string);
Option current_option = {};
while (!in.eof())
{
std::string line_str;
if (std::getline(in, line_str))
{
std::string_view line_view = line_str;
// Check for CRLF eol and convert it to LF
if (!line_view.empty() && line_view.at(line_view.size() - 1) == '\r')
line_view.remove_suffix(1);
if (line_view.empty())
continue;
if (line_view[0] == '[')
{
size_t endpos = line_view.find("]");
if (endpos != std::string::npos)
{
if (current_option.type != Option::Type::Invalid)
{
current_option.value = current_option.default_value;
if (current_option.ui_name.empty())
current_option.ui_name = current_option.name;
if (!current_option.name.empty() && current_option.vector_size > 0)
m_options.push_back(std::move(current_option));
current_option = {};
}
// New section!
std::string_view sub = line_view.substr(1, endpos - 1);
if (sub == "OptionBool")
current_option.type = Option::Type::Bool;
else if (sub == "OptionRangeFloat")
current_option.type = Option::Type::Float;
else if (sub == "OptionRangeInteger")
current_option.type = Option::Type::Int;
else
Log_ErrorPrintf("Invalid option type: '%s'", line_str.c_str());
continue;
}
}
if (current_option.type == Option::Type::Invalid)
continue;
std::string_view key, value;
ParseKeyValue(line_view, &key, &value);
if (!key.empty() && !value.empty())
{
if (key == "GUIName")
{
current_option.ui_name = value;
}
else if (key == "OptionName")
{
current_option.name = value;
}
else if (key == "DependentOption")
{
current_option.dependent_option = value;
}
else if (key == "MinValue" || key == "MaxValue" || key == "DefaultValue" || key == "StepAmount")
{
Option::ValueVector* dst_array;
if (key == "MinValue")
dst_array = &current_option.min_value;
else if (key == "MaxValue")
dst_array = &current_option.max_value;
else if (key == "DefaultValue")
dst_array = &current_option.default_value;
else // if (key == "StepAmount")
dst_array = &current_option.step_value;
u32 size = 0;
if (current_option.type == Option::Type::Bool)
(*dst_array)[size++].int_value = StringUtil::FromChars<bool>(value).value_or(false) ? 1 : 0;
else if (current_option.type == Option::Type::Float)
size = ParseVector<float>(value, dst_array);
else if (current_option.type == Option::Type::Int)
size = ParseVector<s32>(value, dst_array);
current_option.vector_size =
(current_option.vector_size == 0) ? size : std::min(current_option.vector_size, size);
}
else
{
Log_ErrorPrintf("Invalid option key: '%s'", line_str.c_str());
}
}
}
}
if (current_option.type != Option::Type::Invalid && !current_option.name.empty() && current_option.vector_size > 0)
{
current_option.value = current_option.default_value;
if (current_option.ui_name.empty())
current_option.ui_name = current_option.name;
m_options.push_back(std::move(current_option));
}
}
} // namespace FrontendCommon

View File

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/rectangle.h"
#include "core/types.h"
#include <array>
#include <string>
#include <string_view>
#include <vector>
namespace FrontendCommon {
class PostProcessingShader
{
public:
enum : u32
{
PUSH_CONSTANT_SIZE_THRESHOLD = 128
};
struct Option
{
enum : u32
{
MAX_VECTOR_COMPONENTS = 4
};
enum class Type
{
Invalid,
Bool,
Int,
Float
};
union Value
{
s32 int_value;
float float_value;
};
static_assert(sizeof(Value) == sizeof(u32));
using ValueVector = std::array<Value, MAX_VECTOR_COMPONENTS>;
static_assert(sizeof(ValueVector) == sizeof(u32) * MAX_VECTOR_COMPONENTS);
std::string name;
std::string ui_name;
std::string dependent_option;
Type type;
u32 vector_size;
ValueVector default_value;
ValueVector min_value;
ValueVector max_value;
ValueVector step_value;
ValueVector value;
};
PostProcessingShader();
PostProcessingShader(std::string name, std::string code);
PostProcessingShader(const PostProcessingShader& copy);
PostProcessingShader(PostProcessingShader& move);
~PostProcessingShader();
PostProcessingShader& operator=(const PostProcessingShader& copy);
PostProcessingShader& operator=(PostProcessingShader& move);
ALWAYS_INLINE const std::string& GetName() const { return m_name; }
ALWAYS_INLINE const std::string& GetCode() const { return m_code; }
ALWAYS_INLINE const std::vector<Option>& GetOptions() const { return m_options; }
ALWAYS_INLINE std::vector<Option>& GetOptions() { return m_options; }
ALWAYS_INLINE bool HasOptions() const { return !m_options.empty(); }
bool IsValid() const;
const Option* GetOptionByName(const std::string_view& name) const;
Option* GetOptionByName(const std::string_view& name);
std::string GetConfigString() const;
void SetConfigString(const std::string_view& str);
bool LoadFromFile(std::string name, const char* filename);
bool LoadFromString(std::string name, std::string code);
bool UsePushConstants() const;
u32 GetUniformsSize() const;
void FillUniformBuffer(void* buffer, u32 texture_width, s32 texture_height, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height, u32 window_width, u32 window_height,
s32 original_width, s32 original_height, float time) const;
private:
struct CommonUniforms
{
float src_rect[4];
float src_size[2];
float resolution[2];
float rcp_resolution[2];
float window_resolution[2];
float rcp_window_resolution[2];
float original_size[2];
float padded_original_size[2];
float time;
float padding;
};
void LoadOptions();
std::string m_name;
std::string m_code;
std::vector<Option> m_options;
};
} // namespace FrontendCommon

View File

@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "postprocessing_shadergen.h"
namespace FrontendCommon {
PostProcessingShaderGen::PostProcessingShaderGen(RenderAPI render_api, bool supports_dual_source_blend)
: ShaderGen(render_api, supports_dual_source_blend)
{
}
PostProcessingShaderGen::~PostProcessingShaderGen() = default;
std::string PostProcessingShaderGen::GeneratePostProcessingVertexShader(const PostProcessingShader& shader)
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0);
WriteUniformBuffer(ss, shader, shader.UsePushConstants());
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
ss << R"(
{
v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));
v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
#if API_OPENGL || API_OPENGL_ES || API_VULKAN
v_pos.y = -v_pos.y;
#endif
v_tex0 = src_rect.xy + (src_size * v_tex0);
}
)";
return ss.str();
}
std::string PostProcessingShaderGen::GeneratePostProcessingFragmentShader(const PostProcessingShader& shader)
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0);
WriteUniformBuffer(ss, shader, shader.UsePushConstants());
// Rename main, since we need to set up globals
if (!m_glsl)
{
// TODO: vecn -> floatn
ss << R"(
#define main real_main
static float2 v_tex0;
static float4 v_pos;
static float4 o_col0;
// Wrappers for sampling functions.
#define texture(sampler, coords) sampler.Sample(sampler##_ss, coords)
#define textureOffset(sampler, coords, offset) sampler.Sample(sampler##_ss, coords, offset)
#define gl_FragCoord v_pos
)";
}
else
{
if (m_use_glsl_interface_blocks)
{
if (IsVulkan())
ss << "layout(location = 0) ";
ss << "in VertexData {\n";
ss << " float2 v_tex0;\n";
ss << "};\n";
}
else
{
ss << "in float2 v_tex0;\n";
}
if (m_use_glsl_binding_layout)
{
ss << "layout(location = 0) out float4 o_col0;\n";
}
else
{
ss << "out float4 o_col0;\n";
}
}
ss << R"(
float4 Sample() { return texture(samp0, v_tex0); }
float4 SampleLocation(float2 location) { return texture(samp0, location); }
#define SampleOffset(offset) textureOffset(samp0, v_tex0, offset)
float2 GetFragCoord()
{
return gl_FragCoord.xy;
}
float2 GetWindowResolution()
{
return window_resolution;
}
float2 GetResolution()
{
return resolution;
}
float2 GetInvResolution()
{
return rcp_resolution;
}
float2 GetCoordinates()
{
return v_tex0;
}
float2 GetOriginalSize()
{
return original_size;
}
float2 GetPaddedOriginalSize()
{
return padded_original_size;
}
float GetTime()
{
return time;
}
void SetOutput(float4 color)
{
o_col0 = color;
}
#define GetOption(x) (x)
#define OptionEnabled(x) ((x) != 0)
)";
ss << shader.GetCode();
if (!m_glsl)
{
ss << R"(
#undef main
void main(in float2 v_tex0_ : TEXCOORD0, in float4 v_pos_ : SV_Position, out float4 o_col0_ : SV_Target)
{
v_pos = v_pos_;
v_tex0 = v_tex0_;
real_main();
o_col0_ = o_col0;
}
)";
}
return ss.str();
}
void PostProcessingShaderGen::WriteUniformBuffer(std::stringstream& ss, const PostProcessingShader& shader,
bool use_push_constants)
{
u32 pad_counter = 0;
WriteUniformBufferDeclaration(ss, use_push_constants);
ss << "{\n";
ss << " float4 src_rect;\n";
ss << " float2 src_size;\n";
ss << " float2 resolution;\n";
ss << " float2 rcp_resolution;\n";
ss << " float2 window_resolution;\n";
ss << " float2 rcp_window_resolution;\n";
ss << " float2 original_size;\n";
ss << " float2 padded_original_size;\n";
ss << " float time;\n";
ss << " float ubo_pad" << (pad_counter++) << ";\n";
ss << "\n";
static constexpr std::array<const char*, PostProcessingShader::Option::MAX_VECTOR_COMPONENTS + 1> vector_size_suffix =
{{"", "", "2", "3", "4"}};
for (const PostProcessingShader::Option& option : shader.GetOptions())
{
switch (option.type)
{
case PostProcessingShader::Option::Type::Bool:
ss << " int " << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " int ubo_pad" << (pad_counter++) << ";\n";
break;
case PostProcessingShader::Option::Type::Int:
{
ss << " int" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " int ubo_pad" << (pad_counter++) << ";\n";
}
break;
case PostProcessingShader::Option::Type::Float:
default:
{
ss << " float" << vector_size_suffix[option.vector_size] << " " << option.name << ";\n";
for (u32 i = option.vector_size; i < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; i++)
ss << " float ubo_pad" << (pad_counter++) << ";\n";
}
break;
}
}
ss << "};\n\n";
}
} // namespace FrontendCommon

View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "util/shadergen.h"
#include "postprocessing_shader.h"
#include <sstream>
namespace FrontendCommon {
class PostProcessingShaderGen : public ShaderGen
{
public:
PostProcessingShaderGen(RenderAPI render_api, bool supports_dual_source_blend);
~PostProcessingShaderGen();
std::string GeneratePostProcessingVertexShader(const PostProcessingShader& shader);
std::string GeneratePostProcessingFragmentShader(const PostProcessingShader& shader);
private:
void WriteUniformBuffer(std::stringstream& ss, const PostProcessingShader& shader, bool use_push_constants);
};
} // namespace FrontendCommon

View File

@ -0,0 +1,949 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "sdl_input_source.h"
#include "common/assert.h"
#include "common/bitutils.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/host.h"
#include "core/host_settings.h"
#include "input_manager.h"
#include <cmath>
#ifdef __APPLE__
#include <dispatch/dispatch.h>
#endif
Log_SetChannel(SDLInputSource);
static constexpr const char* s_sdl_axis_names[] = {
"LeftX", // SDL_CONTROLLER_AXIS_LEFTX
"LeftY", // SDL_CONTROLLER_AXIS_LEFTY
"RightX", // SDL_CONTROLLER_AXIS_RIGHTX
"RightY", // SDL_CONTROLLER_AXIS_RIGHTY
"LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT
"RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT
};
static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = {
{GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX
{GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY
{GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX
{GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // SDL_CONTROLLER_AXIS_RIGHTY
{GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT
{GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT
};
static constexpr const char* s_sdl_button_names[] = {
"A", // SDL_CONTROLLER_BUTTON_A
"B", // SDL_CONTROLLER_BUTTON_B
"X", // SDL_CONTROLLER_BUTTON_X
"Y", // SDL_CONTROLLER_BUTTON_Y
"Back", // SDL_CONTROLLER_BUTTON_BACK
"Guide", // SDL_CONTROLLER_BUTTON_GUIDE
"Start", // SDL_CONTROLLER_BUTTON_START
"LeftStick", // SDL_CONTROLLER_BUTTON_LEFTSTICK
"RightStick", // SDL_CONTROLLER_BUTTON_RIGHTSTICK
"LeftShoulder", // SDL_CONTROLLER_BUTTON_LEFTSHOULDER
"RightShoulder", // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
"DPadUp", // SDL_CONTROLLER_BUTTON_DPAD_UP
"DPadDown", // SDL_CONTROLLER_BUTTON_DPAD_DOWN
"DPadLeft", // SDL_CONTROLLER_BUTTON_DPAD_LEFT
"DPadRight", // SDL_CONTROLLER_BUTTON_DPAD_RIGHT
"Misc1", // SDL_CONTROLLER_BUTTON_MISC1
"Paddle1", // SDL_CONTROLLER_BUTTON_PADDLE1
"Paddle2", // SDL_CONTROLLER_BUTTON_PADDLE2
"Paddle3", // SDL_CONTROLLER_BUTTON_PADDLE3
"Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4
"Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD
};
static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[] = {
GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A
GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B
GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X
GenericInputBinding::Triangle, // SDL_CONTROLLER_BUTTON_Y
GenericInputBinding::Select, // SDL_CONTROLLER_BUTTON_BACK
GenericInputBinding::System, // SDL_CONTROLLER_BUTTON_GUIDE
GenericInputBinding::Start, // SDL_CONTROLLER_BUTTON_START
GenericInputBinding::L3, // SDL_CONTROLLER_BUTTON_LEFTSTICK
GenericInputBinding::R3, // SDL_CONTROLLER_BUTTON_RIGHTSTICK
GenericInputBinding::L1, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER
GenericInputBinding::R1, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
GenericInputBinding::DPadUp, // SDL_CONTROLLER_BUTTON_DPAD_UP
GenericInputBinding::DPadDown, // SDL_CONTROLLER_BUTTON_DPAD_DOWN
GenericInputBinding::DPadLeft, // SDL_CONTROLLER_BUTTON_DPAD_LEFT
GenericInputBinding::DPadRight, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_MISC1
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE1
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE2
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE3
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE4
GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD
};
static constexpr const char* s_sdl_hat_direction_names[] = {
// clang-format off
"North",
"East",
"South",
"West",
// clang-format on
};
static constexpr const char* s_sdl_default_led_colors[] = {
"0000ff", // SDL-0
"ff0000", // SDL-1
"00ff00", // SDL-2
"ffff00", // SDL-3
};
static void SetControllerRGBLED(SDL_GameController* gc, u32 color)
{
SDL_GameControllerSetLED(gc, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
}
SDLInputSource::SDLInputSource() = default;
SDLInputSource::~SDLInputSource()
{
Assert(m_controllers.empty());
}
bool SDLInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
std::optional<std::vector<u8>> controller_db_data = Host::ReadResourceFile("gamecontrollerdb.txt");
if (controller_db_data.has_value())
{
SDL_RWops* ops = SDL_RWFromConstMem(controller_db_data->data(), static_cast<int>(controller_db_data->size()));
if (SDL_GameControllerAddMappingsFromRW(ops, true) < 0)
Log_ErrorPrintf("SDL_GameControllerAddMappingsFromRW() failed: %s", SDL_GetError());
}
else
{
Log_ErrorPrintf("Controller database resource is missing.");
}
LoadSettings(si);
settings_lock.unlock();
SetHints();
bool result = InitializeSubsystem();
settings_lock.lock();
return result;
}
void SDLInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
const bool old_controller_enhanced_mode = m_controller_enhanced_mode;
LoadSettings(si);
if (m_controller_enhanced_mode != old_controller_enhanced_mode)
{
settings_lock.unlock();
ShutdownSubsystem();
SetHints();
InitializeSubsystem();
settings_lock.lock();
}
}
bool SDLInputSource::ReloadDevices()
{
// We'll get a GC added/removed event here.
PollEvents();
return false;
}
void SDLInputSource::Shutdown()
{
ShutdownSubsystem();
}
void SDLInputSource::LoadSettings(SettingsInterface& si)
{
m_controller_enhanced_mode = si.GetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
m_sdl_hints = si.GetKeyValueList("SDLHints");
for (u32 i = 0; i < MAX_LED_COLORS; i++)
{
const u32 color = GetRGBForPlayerId(si, i);
if (m_led_colors[i] == color)
continue;
m_led_colors[i] = color;
const auto it = GetControllerDataForPlayerId(i);
if (it == m_controllers.end() || !it->game_controller || !SDL_GameControllerHasLED(it->game_controller))
continue;
SetControllerRGBLED(it->game_controller, color);
}
}
u32 SDLInputSource::GetRGBForPlayerId(SettingsInterface& si, u32 player_id)
{
return ParseRGBForPlayerId(
si.GetStringValue("SDLExtra", fmt::format("Player{}LED", player_id).c_str(), s_sdl_default_led_colors[player_id]),
player_id);
}
u32 SDLInputSource::ParseRGBForPlayerId(const std::string_view& str, u32 player_id)
{
if (player_id >= MAX_LED_COLORS)
return 0;
const u32 default_color = StringUtil::FromChars<u32>(s_sdl_default_led_colors[player_id], 16).value_or(0);
const u32 color = StringUtil::FromChars<u32>(str, 16).value_or(default_color);
return color;
}
void SDLInputSource::SetHints()
{
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, m_controller_enhanced_mode ? "1" : "0");
// Enable Wii U Pro Controller support
// New as of SDL 2.26, so use string
SDL_SetHint("SDL_JOYSTICK_HIDAPI_WII", "1");
#ifndef _WIN32
// Gets us pressure sensitive button support on Linux
// Apparently doesn't work on Windows, so leave it off there
// New as of SDL 2.26, so use string
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS3", "1");
#endif
for (const std::pair<std::string, std::string>& hint : m_sdl_hints)
SDL_SetHint(hint.first.c_str(), hint.second.c_str());
}
bool SDLInputSource::InitializeSubsystem()
{
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
{
Log_ErrorPrint("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
return false;
}
// we should open the controllers as the connected events come in, so no need to do any more here
m_sdl_subsystem_initialized = true;
return true;
}
void SDLInputSource::ShutdownSubsystem()
{
while (!m_controllers.empty())
CloseDevice(m_controllers.begin()->joystick_id);
if (m_sdl_subsystem_initialized)
{
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);
m_sdl_subsystem_initialized = false;
}
}
void SDLInputSource::PollEvents()
{
for (;;)
{
SDL_Event ev;
if (SDL_PollEvent(&ev))
ProcessSDLEvent(&ev);
else
break;
}
}
std::vector<std::pair<std::string, std::string>> SDLInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
for (const ControllerData& cd : m_controllers)
{
std::string id(StringUtil::StdStringFromFormat("SDL-%d", cd.player_id));
const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick);
if (name)
ret.emplace_back(std::move(id), name);
else
ret.emplace_back(std::move(id), "Unknown Device");
}
return ret;
}
std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_view& device,
const std::string_view& binding)
{
if (!StringUtil::StartsWith(device, "SDL-") || binding.empty())
return std::nullopt;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::SDL;
key.source_index = static_cast<u32>(player_id.value());
if (StringUtil::EndsWith(binding, "Motor"))
{
key.source_subtype = InputSubclass::ControllerMotor;
if (binding == "LargeMotor")
{
key.data = 0;
return key;
}
else if (binding == "SmallMotor")
{
key.data = 1;
return key;
}
else
{
return std::nullopt;
}
}
else if (StringUtil::EndsWith(binding, "Haptic"))
{
key.source_subtype = InputSubclass::ControllerHaptic;
key.data = 0;
return key;
}
else if (binding[0] == '+' || binding[0] == '-')
{
// likely an axis
const std::string_view axis_name(binding.substr(1));
if (StringUtil::StartsWith(axis_name, "Axis"))
{
std::string_view end;
if (auto value = StringUtil::FromChars<u32>(axis_name.substr(4), 10, &end))
{
key.source_subtype = InputSubclass::ControllerAxis;
key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names));
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
key.invert = (end == "~");
return key;
}
}
for (u32 i = 0; i < std::size(s_sdl_axis_names); i++)
{
if (axis_name == s_sdl_axis_names[i])
{
// found an axis!
key.source_subtype = InputSubclass::ControllerAxis;
key.data = i;
key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
return key;
}
}
}
else if (StringUtil::StartsWith(binding, "FullAxis"))
{
std::string_view end;
if (auto value = StringUtil::FromChars<u32>(binding.substr(8), 10, &end))
{
key.source_subtype = InputSubclass::ControllerAxis;
key.data = *value + static_cast<u32>(std::size(s_sdl_axis_names));
key.modifier = InputModifier::FullAxis;
key.invert = (end == "~");
return key;
}
}
else if (StringUtil::StartsWith(binding, "Hat"))
{
std::string_view hat_dir;
if (auto value = StringUtil::FromChars<u32>(binding.substr(3), 10, &hat_dir); value.has_value() && !hat_dir.empty())
{
for (u8 dir = 0; dir < static_cast<u8>(std::size(s_sdl_hat_direction_names)); dir++)
{
if (hat_dir == s_sdl_hat_direction_names[dir])
{
key.source_subtype = InputSubclass::ControllerHat;
key.data = value.value() * static_cast<u32>(std::size(s_sdl_hat_direction_names)) + dir;
return key;
}
}
}
}
else
{
// must be a button
if (StringUtil::StartsWith(binding, "Button"))
{
if (auto value = StringUtil::FromChars<u32>(binding.substr(6)))
{
key.source_subtype = InputSubclass::ControllerButton;
key.data = *value + static_cast<u32>(std::size(s_sdl_button_names));
return key;
}
}
for (u32 i = 0; i < std::size(s_sdl_button_names); i++)
{
if (binding == s_sdl_button_names[i])
{
key.source_subtype = InputSubclass::ControllerButton;
key.data = i;
return key;
}
}
}
// unknown axis/button
return std::nullopt;
}
std::string SDLInputSource::ConvertKeyToString(InputBindingKey key)
{
std::string ret;
if (key.source_type == InputSourceType::SDL)
{
if (key.source_subtype == InputSubclass::ControllerAxis)
{
const char* modifier =
(key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
if (key.data < std::size(s_sdl_axis_names))
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%s%s", key.source_index, modifier, s_sdl_axis_names[key.data]);
}
else
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%sAxis%u%s", key.source_index, modifier,
key.data - static_cast<u32>(std::size(s_sdl_axis_names)),
key.invert ? "~" : "");
}
}
else if (key.source_subtype == InputSubclass::ControllerButton)
{
if (key.data < std::size(s_sdl_button_names))
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%s", key.source_index, s_sdl_button_names[key.data]);
}
else
{
ret = StringUtil::StdStringFromFormat("SDL-%u/Button%u", key.source_index,
key.data - static_cast<u32>(std::size(s_sdl_button_names)));
}
}
else if (key.source_subtype == InputSubclass::ControllerHat)
{
const u32 hat_index = key.data / static_cast<u32>(std::size(s_sdl_hat_direction_names));
const u32 hat_direction = key.data % static_cast<u32>(std::size(s_sdl_hat_direction_names));
ret = StringUtil::StdStringFromFormat("SDL-%u/Hat%u%s", key.source_index, hat_index,
s_sdl_hat_direction_names[hat_direction]);
}
else if (key.source_subtype == InputSubclass::ControllerMotor)
{
ret = StringUtil::StdStringFromFormat("SDL-%u/%sMotor", key.source_index, key.data ? "Large" : "Small");
}
else if (key.source_subtype == InputSubclass::ControllerHaptic)
{
ret = StringUtil::StdStringFromFormat("SDL-%u/Haptic", key.source_index);
}
}
return ret;
}
bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
{
switch (event->type)
{
case SDL_CONTROLLERDEVICEADDED:
{
Log_InfoPrintf("(SDLInputSource) Controller %d inserted", event->cdevice.which);
OpenDevice(event->cdevice.which, true);
return true;
}
case SDL_CONTROLLERDEVICEREMOVED:
{
Log_InfoPrintf("(SDLInputSource) Controller %d removed", event->cdevice.which);
CloseDevice(event->cdevice.which);
return true;
}
case SDL_JOYDEVICEADDED:
{
// Let game controller handle.. well.. game controllers.
if (SDL_IsGameController(event->jdevice.which))
return false;
Log_InfoPrintf("(SDLInputSource) Joystick %d inserted", event->jdevice.which);
OpenDevice(event->cdevice.which, false);
return true;
}
break;
case SDL_JOYDEVICEREMOVED:
{
if (auto it = GetControllerDataForJoystickId(event->cdevice.which);
it != m_controllers.end() && it->game_controller)
return false;
Log_InfoPrintf("(SDLInputSource) Joystick %d removed", event->jdevice.which);
CloseDevice(event->cdevice.which);
return true;
}
case SDL_CONTROLLERAXISMOTION:
return HandleControllerAxisEvent(&event->caxis);
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
return HandleControllerButtonEvent(&event->cbutton);
case SDL_JOYAXISMOTION:
return HandleJoystickAxisEvent(&event->jaxis);
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
return HandleJoystickButtonEvent(&event->jbutton);
case SDL_JOYHATMOTION:
return HandleJoystickHatEvent(&event->jhat);
default:
return false;
}
}
SDL_Joystick* SDLInputSource::GetJoystickForDevice(const std::string_view& device)
{
if (!StringUtil::StartsWith(device, "SDL-"))
return nullptr;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return nullptr;
auto it = GetControllerDataForPlayerId(player_id.value());
if (it == m_controllers.end())
return nullptr;
return it->joystick;
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(int id)
{
return std::find_if(m_controllers.begin(), m_controllers.end(),
[id](const ControllerData& cd) { return cd.joystick_id == id; });
}
SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForPlayerId(int id)
{
return std::find_if(m_controllers.begin(), m_controllers.end(),
[id](const ControllerData& cd) { return cd.player_id == id; });
}
int SDLInputSource::GetFreePlayerId() const
{
for (int player_id = 0;; player_id++)
{
size_t i;
for (i = 0; i < m_controllers.size(); i++)
{
if (m_controllers[i].player_id == player_id)
break;
}
if (i == m_controllers.size())
return player_id;
}
return 0;
}
bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
{
SDL_GameController* gcontroller;
SDL_Joystick* joystick;
if (is_gamecontroller)
{
gcontroller = SDL_GameControllerOpen(index);
joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr;
}
else
{
gcontroller = nullptr;
joystick = SDL_JoystickOpen(index);
}
if (!gcontroller && !joystick)
{
Log_ErrorPrintf("(SDLInputSource) Failed to open controller %d", index);
if (gcontroller)
SDL_GameControllerClose(gcontroller);
return false;
}
const int joystick_id = SDL_JoystickInstanceID(joystick);
int player_id = gcontroller ? SDL_GameControllerGetPlayerIndex(gcontroller) : SDL_JoystickGetPlayerIndex(joystick);
if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end())
{
const int free_player_id = GetFreePlayerId();
Log_WarningPrintf("(SDLInputSource) Controller %d (joystick %d) returned player ID %d, which is invalid or in "
"use. Using ID %d instead.",
index, joystick_id, player_id, free_player_id);
player_id = free_player_id;
}
const char* name = gcontroller ? SDL_GameControllerName(gcontroller) : SDL_JoystickName(joystick);
if (!name)
name = "Unknown Device";
Log_VerbosePrintf("(SDLInputSource) Opened %s %d (instance id %d, player id %d): %s",
is_gamecontroller ? "game controller" : "joystick", index, joystick_id, player_id, name);
ControllerData cd = {};
cd.player_id = player_id;
cd.joystick_id = joystick_id;
cd.haptic_left_right_effect = -1;
cd.game_controller = gcontroller;
cd.joystick = joystick;
if (gcontroller)
{
const int num_axes = SDL_JoystickNumAxes(joystick);
const int num_buttons = SDL_JoystickNumButtons(joystick);
cd.joy_axis_used_in_gc.resize(num_axes, false);
cd.joy_button_used_in_gc.resize(num_buttons, false);
auto mark_bind = [&](SDL_GameControllerButtonBind bind) {
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes)
cd.joy_axis_used_in_gc[bind.value.axis] = true;
if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons)
cd.joy_button_used_in_gc[bind.value.button] = true;
};
for (size_t i = 0; i < std::size(s_sdl_axis_names); i++)
mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast<SDL_GameControllerAxis>(i)));
for (size_t i = 0; i < std::size(s_sdl_button_names); i++)
mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i)));
}
else
{
// GC doesn't have the concept of hats, so we only need to do this for joysticks.
const int num_hats = SDL_JoystickNumHats(joystick);
if (num_hats > 0)
cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0));
}
cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0);
if (cd.use_game_controller_rumble)
{
Log_VerbosePrintf("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", name);
}
else
{
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
if (haptic)
{
SDL_HapticEffect ef = {};
ef.leftright.type = SDL_HAPTIC_LEFTRIGHT;
ef.leftright.length = 1000;
int ef_id = SDL_HapticNewEffect(haptic, &ef);
if (ef_id >= 0)
{
cd.haptic = haptic;
cd.haptic_left_right_effect = ef_id;
}
else
{
Log_ErrorPrintf("(SDLInputSource) Failed to create haptic left/right effect: %s", SDL_GetError());
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0)
{
cd.haptic = haptic;
}
else
{
Log_ErrorPrintf("(SDLInputSource) No haptic rumble supported: %s", SDL_GetError());
SDL_HapticClose(haptic);
}
}
}
if (cd.haptic)
Log_VerbosePrintf("(SDLInputSource) Rumble is supported on '%s' via haptic", name);
}
if (!cd.haptic && !cd.use_game_controller_rumble)
Log_VerbosePrintf("(SDLInputSource) Rumble is not supported on '%s'", name);
if (player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS && gcontroller &&
SDL_GameControllerHasLED(gcontroller))
{
SetControllerRGBLED(gcontroller, m_led_colors[player_id]);
}
m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name);
return true;
}
bool SDLInputSource::CloseDevice(int joystick_index)
{
auto it = GetControllerDataForJoystickId(joystick_index);
if (it == m_controllers.end())
return false;
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", it->player_id));
if (it->haptic)
SDL_HapticClose(it->haptic);
if (it->game_controller)
SDL_GameControllerClose(it->game_controller);
else
SDL_JoystickClose(it->joystick);
m_controllers.erase(it);
return true;
}
static float NormalizeS16(s16 value)
{
return static_cast<float>(value) / (value < 0 ? 32768.0f : 32767.0f);
}
bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis));
InputManager::InvokeEvents(key, NormalizeS16(ev->value));
return true;
}
bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, ev->button));
const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ?
s_sdl_generic_binding_button_mapping[ev->button] :
GenericInputBinding::Unknown;
InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key);
return true;
}
bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
if (ev->axis < it->joy_axis_used_in_gc.size() && it->joy_axis_used_in_gc[ev->axis])
return false; // Will get handled by GC event
const u32 axis = ev->axis + static_cast<u32>(std::size(s_sdl_axis_names)); // Ensure we don't conflict with GC axes
const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, axis));
InputManager::InvokeEvents(key, NormalizeS16(ev->value));
return true;
}
bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end())
return false;
if (ev->button < it->joy_button_used_in_gc.size() && it->joy_button_used_in_gc[ev->button])
return false; // Will get handled by GC event
const u32 button =
ev->button + static_cast<u32>(std::size(s_sdl_button_names)); // Ensure we don't conflict with GC buttons
const InputBindingKey key(MakeGenericControllerButtonKey(InputSourceType::SDL, it->player_id, button));
InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f);
return true;
}
bool SDLInputSource::HandleJoystickHatEvent(const SDL_JoyHatEvent* ev)
{
auto it = GetControllerDataForJoystickId(ev->which);
if (it == m_controllers.end() || ev->hat >= it->last_hat_state.size())
return false;
const unsigned long last_direction = it->last_hat_state[ev->hat];
it->last_hat_state[ev->hat] = ev->value;
unsigned long changed_direction = last_direction ^ ev->value;
while (changed_direction != 0)
{
const u32 pos = CountTrailingZeros(changed_direction);
const unsigned long mask = (1u << pos);
changed_direction &= ~mask;
const InputBindingKey key(MakeGenericControllerHatKey(InputSourceType::SDL, it->player_id, ev->hat,
static_cast<u8>(pos),
static_cast<u32>(std::size(s_sdl_hat_direction_names))));
InputManager::InvokeEvents(key, (last_direction & mask) ? 0.0f : 1.0f);
}
return true;
}
std::vector<InputBindingKey> SDLInputSource::EnumerateMotors()
{
std::vector<InputBindingKey> ret;
InputBindingKey key = {};
key.source_type = InputSourceType::SDL;
for (ControllerData& cd : m_controllers)
{
key.source_index = cd.player_id;
if (cd.use_game_controller_rumble || cd.haptic_left_right_effect)
{
// two motors
key.source_subtype = InputSubclass::ControllerMotor;
key.data = 0;
ret.push_back(key);
key.data = 1;
ret.push_back(key);
}
else if (cd.haptic)
{
// haptic effect
key.source_subtype = InputSubclass::ControllerHaptic;
key.data = 0;
ret.push_back(key);
}
}
return ret;
}
bool SDLInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping)
{
if (!StringUtil::StartsWith(device, "SDL-"))
return false;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(4));
if (!player_id.has_value() || player_id.value() < 0)
return false;
ControllerDataVector::iterator it = GetControllerDataForPlayerId(player_id.value());
if (it == m_controllers.end())
return false;
if (it->game_controller)
{
// assume all buttons are present.
const s32 pid = player_id.value();
for (u32 i = 0; i < std::size(s_sdl_generic_binding_axis_mapping); i++)
{
const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0];
const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1];
if (negative != GenericInputBinding::Unknown)
mapping->emplace_back(negative, StringUtil::StdStringFromFormat("SDL-%d/-%s", pid, s_sdl_axis_names[i]));
if (positive != GenericInputBinding::Unknown)
mapping->emplace_back(positive, StringUtil::StdStringFromFormat("SDL-%d/+%s", pid, s_sdl_axis_names[i]));
}
for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++)
{
const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i];
if (binding != GenericInputBinding::Unknown)
mapping->emplace_back(binding, StringUtil::StdStringFromFormat("SDL-%d/%s", pid, s_sdl_button_names[i]));
}
if (it->use_game_controller_rumble || it->haptic_left_right_effect)
{
mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("SDL-%d/SmallMotor", pid));
mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("SDL-%d/LargeMotor", pid));
}
else
{
mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("SDL-%d/Haptic", pid));
mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("SDL-%d/Haptic", pid));
}
return true;
}
else
{
// joysticks have arbitrary axis numbers, so automapping isn't going to work here.
return false;
}
}
void SDLInputSource::UpdateMotorState(InputBindingKey key, float intensity)
{
if (key.source_subtype != InputSubclass::ControllerMotor && key.source_subtype != InputSubclass::ControllerHaptic)
return;
auto it = GetControllerDataForPlayerId(key.source_index);
if (it == m_controllers.end())
return;
it->rumble_intensity[key.data] = static_cast<u16>(intensity * 65535.0f);
SendRumbleUpdate(&(*it));
}
void SDLInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity)
{
if (large_key.source_index != small_key.source_index || large_key.source_subtype != InputSubclass::ControllerMotor ||
small_key.source_subtype != InputSubclass::ControllerMotor)
{
// bonkers config where they're mapped to different controllers... who would do such a thing?
UpdateMotorState(large_key, large_intensity);
UpdateMotorState(small_key, small_intensity);
return;
}
auto it = GetControllerDataForPlayerId(large_key.source_index);
if (it == m_controllers.end())
return;
it->rumble_intensity[large_key.data] = static_cast<u16>(large_intensity * 65535.0f);
it->rumble_intensity[small_key.data] = static_cast<u16>(small_intensity * 65535.0f);
SendRumbleUpdate(&(*it));
}
void SDLInputSource::SendRumbleUpdate(ControllerData* cd)
{
// we'll update before this duration is elapsed
static constexpr u32 DURATION = 65535; // SDL_MAX_RUMBLE_DURATION_MS
if (cd->use_game_controller_rumble)
{
SDL_GameControllerRumble(cd->game_controller, cd->rumble_intensity[0], cd->rumble_intensity[1], DURATION);
return;
}
if (cd->haptic_left_right_effect >= 0)
{
if ((static_cast<u32>(cd->rumble_intensity[0]) + static_cast<u32>(cd->rumble_intensity[1])) > 0)
{
SDL_HapticEffect ef;
ef.type = SDL_HAPTIC_LEFTRIGHT;
ef.leftright.large_magnitude = cd->rumble_intensity[0];
ef.leftright.small_magnitude = cd->rumble_intensity[1];
ef.leftright.length = DURATION;
SDL_HapticUpdateEffect(cd->haptic, cd->haptic_left_right_effect, &ef);
SDL_HapticRunEffect(cd->haptic, cd->haptic_left_right_effect, SDL_HAPTIC_INFINITY);
}
else
{
SDL_HapticStopEffect(cd->haptic, cd->haptic_left_right_effect);
}
}
else
{
const float strength =
static_cast<float>(std::max(cd->rumble_intensity[0], cd->rumble_intensity[1])) * (1.0f / 65535.0f);
if (strength > 0.0f)
SDL_HapticRumblePlay(cd->haptic, strength, DURATION);
else
SDL_HapticRumbleStop(cd->haptic);
}
}
std::unique_ptr<InputSource> InputSource::CreateSDLSource()
{
return std::make_unique<SDLInputSource>();
}

View File

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "SDL.h"
#include "input_source.h"
#include <array>
#include <functional>
#include <mutex>
#include <vector>
class SettingsInterface;
class SDLInputSource final : public InputSource
{
public:
static constexpr u32 MAX_LED_COLORS = 4;
SDLInputSource();
~SDLInputSource();
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
const std::string_view& binding) override;
std::string ConvertKeyToString(InputBindingKey key) override;
bool ProcessSDLEvent(const SDL_Event* event);
SDL_Joystick* GetJoystickForDevice(const std::string_view& device);
static u32 GetRGBForPlayerId(SettingsInterface& si, u32 player_id);
static u32 ParseRGBForPlayerId(const std::string_view& str, u32 player_id);
private:
struct ControllerData
{
SDL_Haptic* haptic;
SDL_GameController* game_controller;
SDL_Joystick* joystick;
u16 rumble_intensity[2];
int haptic_left_right_effect;
int joystick_id;
int player_id;
bool use_game_controller_rumble;
// Used to disable Joystick controls that are used in GameController inputs so we don't get double events
std::vector<bool> joy_button_used_in_gc;
std::vector<bool> joy_axis_used_in_gc;
// Track last hat state so we can send "unpressed" events.
std::vector<u8> last_hat_state;
};
using ControllerDataVector = std::vector<ControllerData>;
bool InitializeSubsystem();
void ShutdownSubsystem();
void LoadSettings(SettingsInterface& si);
void SetHints();
ControllerDataVector::iterator GetControllerDataForJoystickId(int id);
ControllerDataVector::iterator GetControllerDataForPlayerId(int id);
int GetFreePlayerId() const;
bool OpenDevice(int index, bool is_gamecontroller);
bool CloseDevice(int joystick_index);
bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev);
bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev);
bool HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev);
bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev);
bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev);
void SendRumbleUpdate(ControllerData* cd);
ControllerDataVector m_controllers;
bool m_sdl_subsystem_initialized = false;
bool m_controller_enhanced_mode = false;
std::array<u32, MAX_LED_COLORS> m_led_colors{};
std::vector<std::pair<std::string, std::string>> m_sdl_hints;
};

679
src/util/shadergen.cpp Normal file
View File

@ -0,0 +1,679 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "shadergen.h"
#include "common/assert.h"
#include "common/log.h"
#include <cstdio>
#include <cstring>
#ifdef WITH_OPENGL
#include "common/gl/loader.h"
#endif
Log_SetChannel(ShaderGen);
ShaderGen::ShaderGen(RenderAPI render_api, bool supports_dual_source_blend)
: m_render_api(render_api), m_glsl(render_api != RenderAPI::D3D11 && render_api != RenderAPI::D3D12),
m_supports_dual_source_blend(supports_dual_source_blend), m_use_glsl_interface_blocks(false)
{
#if defined(WITH_OPENGL) || defined(WITH_VULKAN)
if (m_glsl)
{
#ifdef WITH_OPENGL
if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES)
SetGLSLVersionString();
m_use_glsl_interface_blocks = (IsVulkan() || GLAD_GL_ES_VERSION_3_2 || GLAD_GL_VERSION_3_2);
m_use_glsl_binding_layout = (IsVulkan() || UseGLSLBindingLayout());
if (m_render_api == RenderAPI::OpenGL)
{
// SSAA with interface blocks is broken on AMD's OpenGL driver.
const char* gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
if (std::strcmp(gl_vendor, "ATI Technologies Inc.") == 0)
m_use_glsl_interface_blocks = false;
}
#else
m_use_glsl_interface_blocks = true;
m_use_glsl_binding_layout = true;
#endif
}
#endif
}
ShaderGen::~ShaderGen() = default;
bool ShaderGen::UseGLSLBindingLayout()
{
#ifdef WITH_OPENGL
return (GLAD_GL_ES_VERSION_3_1 || GLAD_GL_VERSION_4_3 ||
(GLAD_GL_ARB_explicit_attrib_location && GLAD_GL_ARB_explicit_uniform_location &&
GLAD_GL_ARB_shading_language_420pack));
#else
return true;
#endif
}
void ShaderGen::DefineMacro(std::stringstream& ss, const char* name, bool enabled)
{
ss << "#define " << name << " " << BoolToUInt32(enabled) << "\n";
}
#ifdef WITH_OPENGL
void ShaderGen::SetGLSLVersionString()
{
const char* glsl_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
const bool glsl_es = (m_render_api == RenderAPI::OpenGLES);
Assert(glsl_version != nullptr);
// Skip any strings in front of the version code.
const char* glsl_version_start = glsl_version;
while (*glsl_version_start != '\0' && (*glsl_version_start < '0' || *glsl_version_start > '9'))
glsl_version_start++;
int major_version = 0, minor_version = 0;
if (std::sscanf(glsl_version_start, "%d.%d", &major_version, &minor_version) == 2)
{
// Cap at GLSL 4.3, we're not using anything newer for now.
if (!glsl_es && (major_version > 4 || (major_version == 4 && minor_version > 30)))
{
major_version = 4;
minor_version = 30;
}
else if (glsl_es && (major_version > 3 || (major_version == 3 && minor_version > 20)))
{
major_version = 3;
minor_version = 20;
}
}
else
{
Log_ErrorPrintf("Invalid GLSL version string: '%s' ('%s')", glsl_version, glsl_version_start);
if (glsl_es)
{
major_version = 3;
minor_version = 0;
}
m_glsl_version_string = glsl_es ? "300" : "130";
}
char buf[128];
std::snprintf(buf, sizeof(buf), "#version %d%02d%s", major_version, minor_version,
(glsl_es && major_version >= 3) ? " es" : "");
m_glsl_version_string = buf;
}
#endif
void ShaderGen::WriteHeader(std::stringstream& ss)
{
if (m_render_api == RenderAPI::OpenGL || m_render_api == RenderAPI::OpenGLES)
ss << m_glsl_version_string << "\n\n";
else if (m_render_api == RenderAPI::Vulkan)
ss << "#version 450 core\n\n";
#ifdef WITH_OPENGL
// Extension enabling for OpenGL.
if (m_render_api == RenderAPI::OpenGLES)
{
// Enable EXT_blend_func_extended for dual-source blend on OpenGL ES.
if (GLAD_GL_EXT_blend_func_extended)
ss << "#extension GL_EXT_blend_func_extended : require\n";
if (GLAD_GL_ARB_blend_func_extended)
ss << "#extension GL_ARB_blend_func_extended : require\n";
// Test for V3D driver - we have to fudge coordinates slightly.
if (std::strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)), "Broadcom") &&
std::strstr(reinterpret_cast<const char*>(glGetString(GL_RENDERER)), "V3D"))
{
ss << "#define DRIVER_V3D 1\n";
}
else if (std::strstr(reinterpret_cast<const char*>(glGetString(GL_RENDERER)), "PowerVR"))
{
ss << "#define DRIVER_POWERVR 1\n";
}
}
else if (m_render_api == RenderAPI::OpenGL)
{
// Need extensions for binding layout if GL<4.3.
if (m_use_glsl_binding_layout && !GLAD_GL_VERSION_4_3)
{
ss << "#extension GL_ARB_explicit_attrib_location : require\n";
ss << "#extension GL_ARB_explicit_uniform_location : require\n";
ss << "#extension GL_ARB_shading_language_420pack : require\n";
}
if (!GLAD_GL_VERSION_3_2)
ss << "#extension GL_ARB_uniform_buffer_object : require\n";
// Enable SSBOs if it's not required by the version.
if (!GLAD_GL_VERSION_4_3 && !GLAD_GL_ES_VERSION_3_1 && GLAD_GL_ARB_shader_storage_buffer_object)
ss << "#extension GL_ARB_shader_storage_buffer_object : require\n";
}
#endif
DefineMacro(ss, "API_OPENGL", m_render_api == RenderAPI::OpenGL);
DefineMacro(ss, "API_OPENGL_ES", m_render_api == RenderAPI::OpenGLES);
DefineMacro(ss, "API_D3D11", m_render_api == RenderAPI::D3D11);
DefineMacro(ss, "API_D3D12", m_render_api == RenderAPI::D3D12);
DefineMacro(ss, "API_VULKAN", m_render_api == RenderAPI::Vulkan);
#ifdef WITH_OPENGL
if (m_render_api == RenderAPI::OpenGLES)
{
ss << "precision highp float;\n";
ss << "precision highp int;\n";
ss << "precision highp sampler2D;\n";
if (GLAD_GL_ES_VERSION_3_1)
ss << "precision highp sampler2DMS;\n";
if (GLAD_GL_ES_VERSION_3_2)
ss << "precision highp usamplerBuffer;\n";
ss << "\n";
}
#endif
if (m_glsl)
{
ss << "#define GLSL 1\n";
ss << "#define float2 vec2\n";
ss << "#define float3 vec3\n";
ss << "#define float4 vec4\n";
ss << "#define int2 ivec2\n";
ss << "#define int3 ivec3\n";
ss << "#define int4 ivec4\n";
ss << "#define uint2 uvec2\n";
ss << "#define uint3 uvec3\n";
ss << "#define uint4 uvec4\n";
ss << "#define float2x2 mat2\n";
ss << "#define float3x3 mat3\n";
ss << "#define float4x4 mat4\n";
ss << "#define mul(x, y) ((x) * (y))\n";
ss << "#define nointerpolation flat\n";
ss << "#define frac fract\n";
ss << "#define lerp mix\n";
ss << "#define CONSTANT const\n";
ss << "#define GLOBAL\n";
ss << "#define FOR_UNROLL for\n";
ss << "#define FOR_LOOP for\n";
ss << "#define IF_BRANCH if\n";
ss << "#define IF_FLATTEN if\n";
ss << "#define VECTOR_EQ(a, b) ((a) == (b))\n";
ss << "#define VECTOR_NEQ(a, b) ((a) != (b))\n";
ss << "#define VECTOR_COMP_EQ(a, b) equal((a), (b))\n";
ss << "#define VECTOR_COMP_NEQ(a, b) notEqual((a), (b))\n";
ss << "#define SAMPLE_TEXTURE(name, coords) texture(name, coords)\n";
ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) textureOffset(name, coords, offset)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) textureLod(name, coords, level)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) textureLod(name, coords, level, offset)\n";
ss << "#define LOAD_TEXTURE(name, coords, mip) texelFetch(name, coords, mip)\n";
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) texelFetch(name, coords, int(sample))\n";
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) texelFetchOffset(name, coords, mip, offset)\n";
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
ss << "#define BEGIN_ARRAY(type, size) type[size](\n";
ss << "#define END_ARRAY )\n";
ss << "float saturate(float value) { return clamp(value, 0.0, 1.0); }\n";
ss << "float2 saturate(float2 value) { return clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }\n";
ss << "float3 saturate(float3 value) { return clamp(value, float3(0.0, 0.0, 0.0), float3(1.0, 1.0, 1.0)); }\n";
ss << "float4 saturate(float4 value) { return clamp(value, float4(0.0, 0.0, 0.0, 0.0), float4(1.0, 1.0, 1.0, "
"1.0)); }\n";
}
else
{
ss << "#define HLSL 1\n";
ss << "#define roundEven round\n";
ss << "#define mix lerp\n";
ss << "#define fract frac\n";
ss << "#define vec2 float2\n";
ss << "#define vec3 float3\n";
ss << "#define vec4 float4\n";
ss << "#define ivec2 int2\n";
ss << "#define ivec3 int3\n";
ss << "#define ivec4 int4\n";
ss << "#define uivec2 uint2\n";
ss << "#define uivec3 uint3\n";
ss << "#define uivec4 uint4\n";
ss << "#define mat2 float2x2\n";
ss << "#define mat3 float3x3\n";
ss << "#define mat4 float4x4\n";
ss << "#define CONSTANT static const\n";
ss << "#define GLOBAL static\n";
ss << "#define FOR_UNROLL [unroll] for\n";
ss << "#define FOR_LOOP [loop] for\n";
ss << "#define IF_BRANCH [branch] if\n";
ss << "#define IF_FLATTEN [flatten] if\n";
ss << "#define VECTOR_EQ(a, b) (all((a) == (b)))\n";
ss << "#define VECTOR_NEQ(a, b) (any((a) != (b)))\n";
ss << "#define VECTOR_COMP_EQ(a, b) ((a) == (b))\n";
ss << "#define VECTOR_COMP_NEQ(a, b) ((a) != (b))\n";
ss << "#define SAMPLE_TEXTURE(name, coords) name.Sample(name##_ss, coords)\n";
ss << "#define SAMPLE_TEXTURE_OFFSET(name, coords, offset) name.Sample(name##_ss, coords, offset)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL(name, coords, level) name.SampleLevel(name##_ss, coords, level)\n";
ss << "#define SAMPLE_TEXTURE_LEVEL_OFFSET(name, coords, level, offset) name.SampleLevel(name##_ss, coords, level, "
"offset)\n";
ss << "#define LOAD_TEXTURE(name, coords, mip) name.Load(int3(coords, mip))\n";
ss << "#define LOAD_TEXTURE_MS(name, coords, sample) name.Load(coords, sample)\n";
ss << "#define LOAD_TEXTURE_OFFSET(name, coords, mip, offset) name.Load(int3(coords, mip), offset)\n";
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
ss << "#define BEGIN_ARRAY(type, size) {\n";
ss << "#define END_ARRAY }\n";
}
ss << "\n";
}
void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan)
{
if (IsVulkan())
{
if (push_constant_on_vulkan)
ss << "layout(push_constant) uniform PushConstants\n";
else
ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n";
}
else if (m_glsl)
{
if (m_use_glsl_binding_layout)
ss << "layout(std140, binding = 1) uniform UBOBlock\n";
else
ss << "layout(std140) uniform UBOBlock\n";
}
else
{
ss << "cbuffer UBOBlock : register(b0)\n";
}
}
void ShaderGen::DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
bool push_constant_on_vulkan)
{
WriteUniformBufferDeclaration(ss, push_constant_on_vulkan);
ss << "{\n";
for (const char* member : members)
ss << member << ";\n";
ss << "};\n\n";
}
void ShaderGen::DeclareTexture(std::stringstream& ss, const char* name, u32 index, bool multisampled /* = false */)
{
if (m_glsl)
{
if (IsVulkan())
ss << "layout(set = 0, binding = " << (index + 1u) << ") ";
else if (m_use_glsl_binding_layout)
ss << "layout(binding = " << index << ") ";
ss << "uniform " << (multisampled ? "sampler2DMS " : "sampler2D ") << name << ";\n";
}
else
{
ss << (multisampled ? "Texture2DMS<float4> " : "Texture2D ") << name << " : register(t" << index << ");\n";
ss << "SamplerState " << name << "_ss : register(s" << index << ");\n";
}
}
void ShaderGen::DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned)
{
if (m_glsl)
{
if (IsVulkan())
ss << "layout(set = 0, binding = " << index << ") ";
else if (m_use_glsl_binding_layout)
ss << "layout(binding = " << index << ") ";
ss << "uniform " << (is_int ? (is_unsigned ? "u" : "i") : "") << "samplerBuffer " << name << ";\n";
}
else
{
ss << "Buffer<" << (is_int ? (is_unsigned ? "uint4" : "int4") : "float4") << "> " << name << " : register(t"
<< index << ");\n";
}
}
const char* ShaderGen::GetInterpolationQualifier(bool interface_block, bool centroid_interpolation,
bool sample_interpolation, bool is_out) const
{
#ifdef WITH_OPENGL
const bool shading_language_420pack = GLAD_GL_ARB_shading_language_420pack;
#else
const bool shading_language_420pack = false;
#endif
if (m_glsl && interface_block && (!IsVulkan() && !shading_language_420pack))
{
return (sample_interpolation ? (is_out ? "sample out " : "sample in ") :
(centroid_interpolation ? (is_out ? "centroid out " : "centroid in ") : ""));
}
else
{
return (sample_interpolation ? "sample " : (centroid_interpolation ? "centroid " : ""));
}
}
void ShaderGen::DeclareVertexEntryPoint(
std::stringstream& ss, const std::initializer_list<const char*>& attributes, u32 num_color_outputs,
u32 num_texcoord_outputs, const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
bool declare_vertex_id /* = false */, const char* output_block_suffix /* = "" */, bool msaa /* = false */,
bool ssaa /* = false */, bool noperspective_color /* = false */)
{
if (m_glsl)
{
if (m_use_glsl_binding_layout)
{
u32 attribute_counter = 0;
for (const char* attribute : attributes)
{
ss << "layout(location = " << attribute_counter << ") in " << attribute << ";\n";
attribute_counter++;
}
}
else
{
for (const char* attribute : attributes)
ss << "in " << attribute << ";\n";
}
if (m_use_glsl_interface_blocks)
{
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, true);
if (IsVulkan())
ss << "layout(location = 0) ";
ss << "out VertexData" << output_block_suffix << " {\n";
for (u32 i = 0; i < num_color_outputs; i++)
ss << " " << (noperspective_color ? "noperspective " : "") << qualifier << "float4 v_col" << i << ";\n";
for (u32 i = 0; i < num_texcoord_outputs; i++)
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
for (const auto& [qualifiers, name] : additional_outputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << " " << qualifier_to_use << " " << name << ";\n";
}
ss << "};\n";
}
else
{
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, true);
for (u32 i = 0; i < num_color_outputs; i++)
ss << qualifier << (noperspective_color ? "noperspective " : "") << "out float4 v_col" << i << ";\n";
for (u32 i = 0; i < num_texcoord_outputs; i++)
ss << qualifier << "out float2 v_tex" << i << ";\n";
for (const auto& [qualifiers, name] : additional_outputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << qualifier_to_use << " out " << name << ";\n";
}
}
ss << "#define v_pos gl_Position\n\n";
if (declare_vertex_id)
{
if (IsVulkan())
ss << "#define v_id uint(gl_VertexIndex)\n";
else
ss << "#define v_id uint(gl_VertexID)\n";
}
ss << "\n";
ss << "void main()\n";
}
else
{
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, true);
ss << "void main(\n";
if (declare_vertex_id)
ss << " in uint v_id : SV_VertexID,\n";
u32 attribute_counter = 0;
for (const char* attribute : attributes)
{
ss << " in " << attribute << " : ATTR" << attribute_counter << ",\n";
attribute_counter++;
}
for (u32 i = 0; i < num_color_outputs; i++)
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "out float4 v_col" << i << " : COLOR"
<< i << ",\n";
for (u32 i = 0; i < num_texcoord_outputs; i++)
ss << " " << qualifier << "out float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
u32 additional_counter = num_texcoord_outputs;
for (const auto& [qualifiers, name] : additional_outputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << " " << qualifier_to_use << " out " << name << " : TEXCOORD" << additional_counter << ",\n";
additional_counter++;
}
ss << " out float4 v_pos : SV_Position)\n";
}
}
void ShaderGen::DeclareFragmentEntryPoint(
std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
bool declare_fragcoord /* = false */, u32 num_color_outputs /* = 1 */, bool depth_output /* = false */,
bool msaa /* = false */, bool ssaa /* = false */, bool declare_sample_id /* = false */,
bool noperspective_color /* = false */)
{
if (m_glsl)
{
if (m_use_glsl_interface_blocks)
{
const char* qualifier = GetInterpolationQualifier(true, msaa, ssaa, false);
if (IsVulkan())
ss << "layout(location = 0) ";
ss << "in VertexData {\n";
for (u32 i = 0; i < num_color_inputs; i++)
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "float4 v_col" << i << ";\n";
for (u32 i = 0; i < num_texcoord_inputs; i++)
ss << " " << qualifier << "float2 v_tex" << i << ";\n";
for (const auto& [qualifiers, name] : additional_inputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << " " << qualifier_to_use << " " << name << ";\n";
}
ss << "};\n";
}
else
{
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
for (u32 i = 0; i < num_color_inputs; i++)
ss << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << ";\n";
for (u32 i = 0; i < num_texcoord_inputs; i++)
ss << qualifier << "in float2 v_tex" << i << ";\n";
for (const auto& [qualifiers, name] : additional_inputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << qualifier_to_use << " in " << name << ";\n";
}
}
if (declare_fragcoord)
ss << "#define v_pos gl_FragCoord\n";
if (declare_sample_id)
ss << "#define f_sample_index uint(gl_SampleID)\n";
if (depth_output)
ss << "#define o_depth gl_FragDepth\n";
if (m_use_glsl_binding_layout)
{
if (m_supports_dual_source_blend)
{
for (u32 i = 0; i < num_color_outputs; i++)
ss << "layout(location = 0, index = " << i << ") out float4 o_col" << i << ";\n";
}
else
{
Assert(num_color_outputs <= 1);
for (u32 i = 0; i < num_color_outputs; i++)
ss << "layout(location = " << i << ") out float4 o_col" << i << ";\n";
}
}
else
{
for (u32 i = 0; i < num_color_outputs; i++)
ss << "out float4 o_col" << i << ";\n";
}
ss << "\n";
ss << "void main()\n";
}
else
{
const char* qualifier = GetInterpolationQualifier(false, msaa, ssaa, false);
ss << "void main(\n";
for (u32 i = 0; i < num_color_inputs; i++)
ss << " " << qualifier << (noperspective_color ? "noperspective " : "") << "in float4 v_col" << i << " : COLOR"
<< i << ",\n";
for (u32 i = 0; i < num_texcoord_inputs; i++)
ss << " " << qualifier << "in float2 v_tex" << i << " : TEXCOORD" << i << ",\n";
u32 additional_counter = num_texcoord_inputs;
for (const auto& [qualifiers, name] : additional_inputs)
{
const char* qualifier_to_use = (std::strlen(qualifiers) > 0) ? qualifiers : qualifier;
ss << " " << qualifier_to_use << " in " << name << " : TEXCOORD" << additional_counter << ",\n";
additional_counter++;
}
if (declare_fragcoord)
ss << " in float4 v_pos : SV_Position,\n";
if (declare_sample_id)
ss << " in uint f_sample_index : SV_SampleIndex,\n";
if (depth_output)
{
ss << " out float o_depth : SV_Depth";
if (num_color_outputs > 0)
ss << ",\n";
else
ss << ")\n";
}
for (u32 i = 0; i < num_color_outputs; i++)
{
ss << " out float4 o_col" << i << " : SV_Target" << i;
if (i == (num_color_outputs - 1))
ss << ")\n";
else
ss << ",\n";
}
}
}
std::string ShaderGen::GenerateScreenQuadVertexShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
ss << R"(
{
v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));
v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
#if API_OPENGL || API_OPENGL_ES || API_VULKAN
v_pos.y = -v_pos.y;
#endif
}
)";
return ss.str();
}
std::string ShaderGen::GenerateUVQuadVertexShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"float2 u_uv_min", "float2 u_uv_max"}, true);
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
ss << R"(
{
v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));
v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
v_tex0 = u_uv_min + (u_uv_max - u_uv_min) * v_tex0;
#if API_OPENGL || API_OPENGL_ES || API_VULKAN
v_pos.y = -v_pos.y;
#endif
}
)";
return ss.str();
}
std::string ShaderGen::GenerateFillFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"float4 u_fill_color"}, true);
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1, true);
ss << R"(
{
o_col0 = u_fill_color;
o_depth = u_fill_color.a;
}
)";
return ss.str();
}
std::string ShaderGen::GenerateCopyFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareUniformBuffer(ss, {"float4 u_src_rect"}, true);
DeclareTexture(ss, "samp0", 0);
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
ss << R"(
{
float2 coords = u_src_rect.xy + v_tex0 * u_src_rect.zw;
o_col0 = SAMPLE_TEXTURE(samp0, coords);
}
)";
return ss.str();
}
std::string ShaderGen::GenerateSampleFragmentShader()
{
std::stringstream ss;
WriteHeader(ss);
DeclareTexture(ss, "samp0", 0);
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, 1);
ss << R"(
{
o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
}
)";
return ss.str();
}

60
src/util/shadergen.h Normal file
View File

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "host_display.h"
#include <sstream>
#include <string>
class ShaderGen
{
public:
ShaderGen(RenderAPI render_api, bool supports_dual_source_blend);
~ShaderGen();
static bool UseGLSLBindingLayout();
std::string GenerateScreenQuadVertexShader();
std::string GenerateUVQuadVertexShader();
std::string GenerateFillFragmentShader();
std::string GenerateCopyFragmentShader();
std::string GenerateSampleFragmentShader();
protected:
ALWAYS_INLINE bool IsVulkan() const { return (m_render_api == RenderAPI::Vulkan); }
const char* GetInterpolationQualifier(bool interface_block, bool centroid_interpolation, bool sample_interpolation,
bool is_out) const;
#ifdef WITH_OPENGL
void SetGLSLVersionString();
#endif
void DefineMacro(std::stringstream& ss, const char* name, bool enabled);
void WriteHeader(std::stringstream& ss);
void WriteUniformBufferDeclaration(std::stringstream& ss, bool push_constant_on_vulkan);
void DeclareUniformBuffer(std::stringstream& ss, const std::initializer_list<const char*>& members,
bool push_constant_on_vulkan);
void DeclareTexture(std::stringstream& ss, const char* name, u32 index, bool multisampled = false);
void DeclareTextureBuffer(std::stringstream& ss, const char* name, u32 index, bool is_int, bool is_unsigned);
void DeclareVertexEntryPoint(std::stringstream& ss, const std::initializer_list<const char*>& attributes,
u32 num_color_outputs, u32 num_texcoord_outputs,
const std::initializer_list<std::pair<const char*, const char*>>& additional_outputs,
bool declare_vertex_id = false, const char* output_block_suffix = "", bool msaa = false,
bool ssaa = false, bool noperspective_color = false);
void DeclareFragmentEntryPoint(std::stringstream& ss, u32 num_color_inputs, u32 num_texcoord_inputs,
const std::initializer_list<std::pair<const char*, const char*>>& additional_inputs,
bool declare_fragcoord = false, u32 num_color_outputs = 1, bool depth_output = false,
bool msaa = false, bool ssaa = false, bool declare_sample_id = false,
bool noperspective_color = false);
RenderAPI m_render_api;
bool m_glsl;
bool m_supports_dual_source_blend;
bool m_use_glsl_interface_blocks;
bool m_use_glsl_binding_layout;
std::string m_glsl_version_string;
};

View File

@ -5,8 +5,17 @@
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);SOUNDTOUCH_FLOAT_SAMPLES;SOUNDTOUCH_ALLOW_SSE;ST_NO_EXCEPTION_HANDLING=1</PreprocessorDefinitions>
<PreprocessorDefinitions>WITH_CUBEB=1;WITH_SDL2=1;WITH_DINPUT=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'=='ARM64'">%(PreprocessorDefinitions);SOUNDTOUCH_USE_NEON</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\soundtouch\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)dep\soundtouch\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\stb\include</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>d3dcompiler.lib;d3d11.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Import Project="..\..\dep\msvc\vsprops\SDL2Compile.props" />
</Project>

View File

@ -5,18 +5,50 @@
<ClInclude Include="audio_stream.h" />
<ClInclude Include="cd_image.h" />
<ClInclude Include="cd_image_hasher.h" />
<ClInclude Include="cubeb_audio_stream.h" />
<ClInclude Include="cue_parser.h" />
<ClInclude Include="d3d11_host_display.h" />
<ClInclude Include="d3d12_host_display.h" />
<ClInclude Include="dinput_source.h" />
<ClInclude Include="host_display.h" />
<ClInclude Include="imgui_fullscreen.h" />
<ClInclude Include="imgui_impl_dx11.h" />
<ClInclude Include="imgui_impl_dx12.h" />
<ClInclude Include="imgui_impl_opengl3.h">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="imgui_impl_vulkan.h">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="imgui_manager.h" />
<ClInclude Include="ini_settings_interface.h" />
<ClInclude Include="input_manager.h" />
<ClInclude Include="input_source.h" />
<ClInclude Include="iso_reader.h" />
<ClInclude Include="jit_code_buffer.h" />
<ClInclude Include="opengl_host_display.h">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="pbp_types.h" />
<ClInclude Include="memory_arena.h" />
<ClInclude Include="page_fault_handler.h" />
<ClInclude Include="cd_subchannel_replacement.h" />
<ClInclude Include="platform_misc.h" />
<ClInclude Include="postprocessing_chain.h" />
<ClInclude Include="postprocessing_shader.h" />
<ClInclude Include="postprocessing_shadergen.h" />
<ClInclude Include="sdl_input_source.h" />
<ClInclude Include="shadergen.h" />
<ClInclude Include="shiftjis.h" />
<ClInclude Include="state_wrapper.h" />
<ClInclude Include="cd_xa.h" />
<ClInclude Include="vulkan_host_display.h">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="wav_writer.h" />
<ClInclude Include="win32_raw_input_source.h" />
<ClInclude Include="xaudio2_audio_stream.h" />
<ClInclude Include="xinput_source.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="audio_stream.cpp" />
@ -31,20 +63,58 @@
<ClCompile Include="cd_image_mds.cpp" />
<ClCompile Include="cd_image_memory.cpp" />
<ClCompile Include="cd_image_pbp.cpp" />
<ClCompile Include="cubeb_audio_stream.cpp" />
<ClCompile Include="cue_parser.cpp" />
<ClCompile Include="cd_image_ppf.cpp" />
<ClCompile Include="d3d11_host_display.cpp" />
<ClCompile Include="d3d12_host_display.cpp" />
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="host_display.cpp" />
<ClCompile Include="imgui_fullscreen.cpp" />
<ClCompile Include="imgui_impl_dx11.cpp" />
<ClCompile Include="imgui_impl_dx12.cpp" />
<ClCompile Include="imgui_impl_opengl3.cpp">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="imgui_impl_vulkan.cpp">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="imgui_manager.cpp" />
<ClCompile Include="ini_settings_interface.cpp" />
<ClCompile Include="input_manager.cpp" />
<ClCompile Include="input_source.cpp" />
<ClCompile Include="iso_reader.cpp" />
<ClCompile Include="jit_code_buffer.cpp" />
<ClCompile Include="cd_subchannel_replacement.cpp" />
<ClCompile Include="opengl_host_display.cpp">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="platform_misc_win32.cpp" />
<ClCompile Include="postprocessing_chain.cpp" />
<ClCompile Include="postprocessing_shader.cpp" />
<ClCompile Include="postprocessing_shadergen.cpp" />
<ClCompile Include="sdl_input_source.cpp" />
<ClCompile Include="shadergen.cpp" />
<ClCompile Include="shiftjis.cpp" />
<ClCompile Include="memory_arena.cpp" />
<ClCompile Include="page_fault_handler.cpp" />
<ClCompile Include="state_wrapper.cpp" />
<ClCompile Include="cd_xa.cpp" />
<ClCompile Include="vulkan_host_display.cpp">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="wav_writer.cpp" />
<ClCompile Include="win32_raw_input_source.cpp" />
<ClCompile Include="xaudio2_audio_stream.cpp" />
<ClCompile Include="xinput_source.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\dep\cubeb\cubeb.vcxproj">
<Project>{72f9423c-91ee-4487-aac6-555ed6f61aa1}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
</ProjectReference>
<ProjectReference Include="..\..\dep\libchdr\libchdr.vcxproj">
<Project>{425d6c99-d1c8-43c2-b8ac-4d7b1d941017}</Project>
</ProjectReference>

View File

@ -16,6 +16,30 @@
<ClInclude Include="pbp_types.h" />
<ClInclude Include="cue_parser.h" />
<ClInclude Include="ini_settings_interface.h" />
<ClInclude Include="host_display.h" />
<ClInclude Include="shadergen.h" />
<ClInclude Include="postprocessing_shadergen.h" />
<ClInclude Include="vulkan_host_display.h" />
<ClInclude Include="d3d11_host_display.h" />
<ClInclude Include="d3d12_host_display.h" />
<ClInclude Include="imgui_fullscreen.h" />
<ClInclude Include="imgui_impl_dx11.h" />
<ClInclude Include="imgui_impl_dx12.h" />
<ClInclude Include="imgui_impl_opengl3.h" />
<ClInclude Include="imgui_impl_vulkan.h" />
<ClInclude Include="imgui_manager.h" />
<ClInclude Include="opengl_host_display.h" />
<ClInclude Include="postprocessing_chain.h" />
<ClInclude Include="postprocessing_shader.h" />
<ClInclude Include="input_source.h" />
<ClInclude Include="platform_misc.h" />
<ClInclude Include="sdl_input_source.h" />
<ClInclude Include="win32_raw_input_source.h" />
<ClInclude Include="xaudio2_audio_stream.h" />
<ClInclude Include="xinput_source.h" />
<ClInclude Include="dinput_source.h" />
<ClInclude Include="input_manager.h" />
<ClInclude Include="cubeb_audio_stream.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="jit_code_buffer.cpp" />
@ -42,5 +66,29 @@
<ClCompile Include="cd_image_ppf.cpp" />
<ClCompile Include="cd_image_device.cpp" />
<ClCompile Include="ini_settings_interface.cpp" />
<ClCompile Include="host_display.cpp" />
<ClCompile Include="shadergen.cpp" />
<ClCompile Include="vulkan_host_display.cpp" />
<ClCompile Include="d3d11_host_display.cpp" />
<ClCompile Include="d3d12_host_display.cpp" />
<ClCompile Include="imgui_fullscreen.cpp" />
<ClCompile Include="imgui_impl_dx11.cpp" />
<ClCompile Include="imgui_impl_dx12.cpp" />
<ClCompile Include="imgui_impl_opengl3.cpp" />
<ClCompile Include="imgui_impl_vulkan.cpp" />
<ClCompile Include="imgui_manager.cpp" />
<ClCompile Include="opengl_host_display.cpp" />
<ClCompile Include="postprocessing_chain.cpp" />
<ClCompile Include="postprocessing_shader.cpp" />
<ClCompile Include="postprocessing_shadergen.cpp" />
<ClCompile Include="platform_misc_win32.cpp" />
<ClCompile Include="sdl_input_source.cpp" />
<ClCompile Include="win32_raw_input_source.cpp" />
<ClCompile Include="xaudio2_audio_stream.cpp" />
<ClCompile Include="xinput_source.cpp" />
<ClCompile Include="dinput_source.cpp" />
<ClCompile Include="input_manager.cpp" />
<ClCompile Include="input_source.cpp" />
<ClCompile Include="cubeb_audio_stream.cpp" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/timer.h"
#include "common/vulkan/loader.h"
#include "common/vulkan/stream_buffer.h"
#include "common/vulkan/swap_chain.h"
#include "common/window_info.h"
#include "host_display.h"
#include "postprocessing_chain.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;
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi, bool 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) 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;
bool SetPostProcessingChain(const std::string_view& config) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Format format, const void* data, u32 data_stride,
bool dynamic = false) override;
bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override;
void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override;
bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch) override;
bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data,
u32 out_data_stride) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void SetVSync(bool enabled) override;
bool Render(bool skip_present) override;
bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, std::vector<u32>* out_pixels,
u32* out_stride, GPUTexture::Format* out_format) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
static AdapterAndModeList StaticGetAdapterAndModeList(const WindowInfo* wi);
protected:
struct PushConstants
{
float src_rect_left;
float src_rect_top;
float src_rect_width;
float src_rect_height;
};
struct PostProcessingStage
{
PostProcessingStage() = default;
PostProcessingStage(PostProcessingStage&& move);
~PostProcessingStage();
VkPipeline pipeline = VK_NULL_HANDLE;
VkFramebuffer output_framebuffer = VK_NULL_HANDLE;
Vulkan::Texture output_texture;
u32 uniforms_size = 0;
};
bool CheckPostProcessingRenderTargets(u32 target_width, u32 target_height);
void ApplyPostProcessingChain(VkFramebuffer target_fb, s32 final_left, s32 final_top, s32 final_width,
s32 final_height, Vulkan::Texture* texture, s32 texture_view_x, s32 texture_view_y,
s32 texture_view_width, s32 texture_view_height, u32 target_width, u32 target_height);
VkRenderPass GetRenderPassForDisplay() const;
bool CheckStagingBufferSize(u32 required_size);
void DestroyStagingBuffer();
bool CreateResources() override;
void DestroyResources() override;
bool CreateImGuiContext() override;
void DestroyImGuiContext() override;
bool UpdateImGuiFontTexture() override;
void BeginSwapChainRenderPass(VkFramebuffer framebuffer, u32 width, u32 height);
void RenderDisplay();
void RenderImGui();
void RenderSoftwareCursor();
void RenderDisplay(s32 left, s32 top, s32 width, s32 height, Vulkan::Texture* texture, s32 texture_view_x,
s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter);
void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture_handle);
std::unique_ptr<Vulkan::SwapChain> m_swap_chain;
VkDescriptorSetLayout m_descriptor_set_layout = VK_NULL_HANDLE;
VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE;
VkPipeline m_cursor_pipeline = VK_NULL_HANDLE;
VkPipeline m_display_pipeline = VK_NULL_HANDLE;
VkSampler m_point_sampler = VK_NULL_HANDLE;
VkSampler m_linear_sampler = VK_NULL_HANDLE;
VkSampler m_border_sampler = VK_NULL_HANDLE;
VmaAllocation m_readback_staging_allocation = VK_NULL_HANDLE;
VkBuffer m_readback_staging_buffer = VK_NULL_HANDLE;
u8* m_readback_staging_buffer_map = nullptr;
u32 m_readback_staging_buffer_size = 0;
bool m_is_adreno = false;
VkDescriptorSetLayout m_post_process_descriptor_set_layout = VK_NULL_HANDLE;
VkDescriptorSetLayout m_post_process_ubo_descriptor_set_layout = VK_NULL_HANDLE;
VkPipelineLayout m_post_process_pipeline_layout = VK_NULL_HANDLE;
VkPipelineLayout m_post_process_ubo_pipeline_layout = VK_NULL_HANDLE;
FrontendCommon::PostProcessingChain m_post_processing_chain;
Vulkan::Texture m_post_processing_input_texture;
VkFramebuffer m_post_processing_input_framebuffer = VK_NULL_HANDLE;
Vulkan::StreamBuffer m_post_processing_ubo;
std::vector<PostProcessingStage> m_post_processing_stages;
Common::Timer m_post_processing_timer;
};

View File

@ -0,0 +1,272 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "win32_raw_input_source.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/host.h"
#include "core/system.h"
#include "input_manager.h"
#include <cmath>
#include <hidusage.h>
#include <malloc.h>
Log_SetChannel(Win32RawInputSource);
static const wchar_t* WINDOW_CLASS_NAME = L"Win32RawInputSource";
static bool s_window_class_registered = false;
static constexpr const u32 ALL_BUTTON_MASKS = RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_1_UP | RI_MOUSE_BUTTON_2_DOWN |
RI_MOUSE_BUTTON_2_UP | RI_MOUSE_BUTTON_3_DOWN | RI_MOUSE_BUTTON_3_UP |
RI_MOUSE_BUTTON_4_DOWN | RI_MOUSE_BUTTON_4_UP | RI_MOUSE_BUTTON_5_DOWN |
RI_MOUSE_BUTTON_5_UP;
Win32RawInputSource::Win32RawInputSource() = default;
Win32RawInputSource::~Win32RawInputSource() = default;
bool Win32RawInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
if (!RegisterDummyClass())
{
Log_ErrorPrintf("(Win32RawInputSource) Failed to register dummy window class");
return false;
}
if (!CreateDummyWindow())
{
Log_ErrorPrintf("(Win32RawInputSource) Failed to create dummy window");
return false;
}
if (!OpenDevices())
{
Log_ErrorPrintf("(Win32RawInputSource) Failed to open devices");
return false;
}
return true;
}
void Win32RawInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) {}
bool Win32RawInputSource::ReloadDevices()
{
return false;
}
void Win32RawInputSource::Shutdown()
{
CloseDevices();
DestroyDummyWindow();
}
void Win32RawInputSource::PollEvents()
{
// noop, handled by message pump
}
std::vector<std::pair<std::string, std::string>> Win32RawInputSource::EnumerateDevices()
{
return {};
}
void Win32RawInputSource::UpdateMotorState(InputBindingKey key, float intensity) {}
void Win32RawInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity)
{
}
std::optional<InputBindingKey> Win32RawInputSource::ParseKeyString(const std::string_view& device,
const std::string_view& binding)
{
return std::nullopt;
}
std::string Win32RawInputSource::ConvertKeyToString(InputBindingKey key)
{
return {};
}
std::vector<InputBindingKey> Win32RawInputSource::EnumerateMotors()
{
return {};
}
bool Win32RawInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping)
{
return {};
}
bool Win32RawInputSource::RegisterDummyClass()
{
if (s_window_class_registered)
return true;
WNDCLASSW wc = {};
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpfnWndProc = DummyWindowProc;
wc.lpszClassName = WINDOW_CLASS_NAME;
return (RegisterClassW(&wc) != 0);
}
bool Win32RawInputSource::CreateDummyWindow()
{
m_dummy_window = CreateWindowExW(0, WINDOW_CLASS_NAME, WINDOW_CLASS_NAME, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, HWND_MESSAGE, NULL, GetModuleHandleW(nullptr), NULL);
if (!m_dummy_window)
return false;
SetWindowLongPtrW(m_dummy_window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
return true;
}
void Win32RawInputSource::DestroyDummyWindow()
{
if (!m_dummy_window)
return;
DestroyWindow(m_dummy_window);
m_dummy_window = {};
}
LRESULT CALLBACK Win32RawInputSource::DummyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg != WM_INPUT)
return DefWindowProcW(hwnd, msg, wParam, lParam);
UINT size = 0;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
PRAWINPUT data = static_cast<PRAWINPUT>(_alloca(size));
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, data, &size, sizeof(RAWINPUTHEADER));
// we shouldn't get any WM_INPUT messages prior to SetWindowLongPtr(), so this'll be fine
Win32RawInputSource* ris = reinterpret_cast<Win32RawInputSource*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
if (ris->ProcessRawInputEvent(data))
return 0;
// forward through to normal message processing
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
bool Win32RawInputSource::OpenDevices()
{
UINT num_devices = 0;
if (GetRawInputDeviceList(nullptr, &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1) ||
num_devices == 0)
return false;
std::vector<RAWINPUTDEVICELIST> devices(num_devices);
if (GetRawInputDeviceList(devices.data(), &num_devices, sizeof(RAWINPUTDEVICELIST)) == static_cast<UINT>(-1))
return false;
devices.resize(num_devices);
for (const RAWINPUTDEVICELIST& rid : devices)
{
#if 0
if (rid.dwType == RIM_TYPEKEYBOARD)
m_num_keyboards++;
#endif
if (rid.dwType == RIM_TYPEMOUSE)
m_mice.push_back({rid.hDevice});
}
Log_DevPrintf("(Win32RawInputSource) Found %u keyboards and %zu mice", m_num_keyboards, m_mice.size());
// Grab all keyboard/mouse input.
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
}
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, 0, m_dummy_window};
if (!RegisterRawInputDevices(&rrid, 1, sizeof(rrid)))
return false;
}
return true;
}
void Win32RawInputSource::CloseDevices()
{
if (m_num_keyboards > 0)
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
m_num_keyboards = 0;
}
if (!m_mice.empty())
{
const RAWINPUTDEVICE rrid = {HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_REMOVE, m_dummy_window};
RegisterRawInputDevices(&rrid, 1, sizeof(rrid));
m_mice.clear();
}
}
bool Win32RawInputSource::ProcessRawInputEvent(const RAWINPUT* event)
{
if (event->header.dwType == RIM_TYPEMOUSE)
{
const u32 mouse_index = 0;
for (MouseState& state : m_mice)
{
if (state.device != event->header.hDevice)
continue;
const RAWMOUSE& rm = event->data.mouse;
s32 dx = rm.lLastX;
s32 dy = rm.lLastY;
// handle absolute positioned devices
if ((rm.usFlags & MOUSE_MOVE_ABSOLUTE) == MOUSE_MOVE_ABSOLUTE)
{
dx -= std::exchange(dx, state.last_x);
dy -= std::exchange(dy, state.last_y);
}
unsigned long button_mask =
(rm.usButtonFlags & (rm.usButtonFlags ^ std::exchange(state.button_state, rm.usButtonFlags))) &
ALL_BUTTON_MASKS;
// when the VM isn't running, allow events to run as normal (so we don't break the UI)
if (System::GetState() != System::State::Running)
return false;
while (button_mask != 0)
{
unsigned long bit_index;
_BitScanForward(&bit_index, button_mask);
// these are ordered down..up for each button
const u32 button_number = bit_index >> 1;
const bool button_pressed = (bit_index & 1u) != 0;
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(mouse_index, button_number),
static_cast<float>(button_pressed), GenericInputBinding::Unknown);
button_mask &= ~(1u << bit_index);
}
if (dx != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::X, static_cast<float>(dx), true);
if (dy != 0)
InputManager::UpdatePointerRelativeDelta(mouse_index, InputPointerAxis::Y, static_cast<float>(dy), true);
return true;
}
}
return false;
}
std::unique_ptr<InputSource> InputSource::CreateWin32RawInputSource()
{
return std::make_unique<Win32RawInputSource>();
}

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/windows_headers.h"
#include "input_source.h"
#include <array>
#include <functional>
#include <mutex>
#include <vector>
class SettingsInterface;
class Win32RawInputSource final : public InputSource
{
public:
Win32RawInputSource();
~Win32RawInputSource();
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
const std::string_view& binding) override;
std::string ConvertKeyToString(InputBindingKey key) override;
private:
struct MouseState
{
HANDLE device;
u32 button_state;
s32 last_x;
s32 last_y;
};
static bool RegisterDummyClass();
static LRESULT CALLBACK DummyWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool CreateDummyWindow();
void DestroyDummyWindow();
bool OpenDevices();
void CloseDevices();
bool ProcessRawInputEvent(const RAWINPUT* event);
HWND m_dummy_window = {};
u32 m_num_keyboards = 0;
u32 m_num_mice = 0;
std::vector<MouseState> m_mice;
};

View File

@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "xaudio2_audio_stream.h"
#include "common/assert.h"
#include "common/log.h"
#include <VersionHelpers.h>
#include <xaudio2.h>
Log_SetChannel(XAudio2AudioStream);
XAudio2AudioStream::XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch)
: AudioStream(sample_rate, channels, buffer_ms, stretch)
{
}
XAudio2AudioStream::~XAudio2AudioStream()
{
if (IsOpen())
CloseDevice();
if (m_xaudio2_library)
FreeLibrary(m_xaudio2_library);
if (m_com_initialized_by_us)
CoUninitialize();
}
std::unique_ptr<AudioStream> AudioStream::CreateXAudio2Stream(u32 sample_rate, u32 channels, u32 buffer_ms,
u32 latency_ms, AudioStretchMode stretch)
{
std::unique_ptr<XAudio2AudioStream> stream(
std::make_unique<XAudio2AudioStream>(sample_rate, channels, buffer_ms, stretch));
if (!stream->OpenDevice(latency_ms))
stream.reset();
return stream;
}
bool XAudio2AudioStream::OpenDevice(u32 latency_ms)
{
DebugAssert(!IsOpen());
m_xaudio2_library = LoadLibraryW(XAUDIO2_DLL_W);
if (!m_xaudio2_library)
{
Log_ErrorPrintf("Failed to load '%s', make sure you're using Windows 10", XAUDIO2_DLL_A);
return false;
}
using PFNXAUDIO2CREATE =
HRESULT(STDAPICALLTYPE*)(IXAudio2 * *ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor);
PFNXAUDIO2CREATE xaudio2_create =
reinterpret_cast<PFNXAUDIO2CREATE>(GetProcAddress(m_xaudio2_library, "XAudio2Create"));
if (!xaudio2_create)
return false;
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
m_com_initialized_by_us = SUCCEEDED(hr);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE)
{
Log_ErrorPrintf("Failed to initialize COM");
return false;
}
hr = xaudio2_create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
Log_ErrorPrintf("XAudio2Create() failed: %08X", hr);
return false;
}
hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_channels, m_sample_rate, 0, nullptr);
if (FAILED(hr))
{
Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr);
return false;
}
WAVEFORMATEX wf = {};
wf.cbSize = sizeof(wf);
wf.nAvgBytesPerSec = m_sample_rate * m_channels * sizeof(s16);
wf.nBlockAlign = static_cast<WORD>(sizeof(s16) * m_channels);
wf.nChannels = static_cast<WORD>(m_channels);
wf.nSamplesPerSec = m_sample_rate;
wf.wBitsPerSample = sizeof(s16) * 8;
wf.wFormatTag = WAVE_FORMAT_PCM;
hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this);
if (FAILED(hr))
{
Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr);
return false;
}
hr = m_source_voice->SetFrequencyRatio(1.0f);
if (FAILED(hr))
{
Log_ErrorPrintf("SetFrequencyRatio() failed: %08X", hr);
return false;
}
m_enqueue_buffer_size = std::max<u32>(INTERNAL_BUFFER_SIZE, GetBufferSizeForMS(m_sample_rate, latency_ms));
Log_DevPrintf("Allocating %u buffers of %u frames", NUM_BUFFERS, m_enqueue_buffer_size);
for (u32 i = 0; i < NUM_BUFFERS; i++)
m_enqueue_buffers[i] = std::make_unique<SampleType[]>(m_enqueue_buffer_size * m_channels);
BaseInitialize();
m_volume = 100;
m_paused = false;
hr = m_source_voice->Start(0, 0);
if (FAILED(hr))
{
Log_ErrorPrintf("Start() failed: %08X", hr);
return false;
}
EnqueueBuffer();
return true;
}
void XAudio2AudioStream::SetPaused(bool paused)
{
if (m_paused == paused)
return;
if (paused)
{
HRESULT hr = m_source_voice->Stop(0, 0);
if (FAILED(hr))
Log_ErrorPrintf("Stop() failed: %08X", hr);
}
else
{
HRESULT hr = m_source_voice->Start(0, 0);
if (FAILED(hr))
Log_ErrorPrintf("Start() failed: %08X", hr);
}
m_paused = paused;
if (!m_buffer_enqueued)
EnqueueBuffer();
}
void XAudio2AudioStream::CloseDevice()
{
HRESULT hr;
if (!m_paused)
{
hr = m_source_voice->Stop(0, 0);
if (FAILED(hr))
Log_ErrorPrintf("Stop() failed: %08X", hr);
}
m_source_voice = nullptr;
m_mastering_voice = nullptr;
m_xaudio.Reset();
m_enqueue_buffers = {};
m_current_buffer = 0;
m_paused = true;
}
void XAudio2AudioStream::EnqueueBuffer()
{
SampleType* samples = m_enqueue_buffers[m_current_buffer].get();
ReadFrames(samples, m_enqueue_buffer_size);
const XAUDIO2_BUFFER buf = {
static_cast<UINT32>(0), // flags
static_cast<UINT32>(sizeof(s16) * m_channels * m_enqueue_buffer_size), // bytes
reinterpret_cast<const BYTE*>(samples) // data
};
HRESULT hr = m_source_voice->SubmitSourceBuffer(&buf, nullptr);
if (FAILED(hr))
Log_ErrorPrintf("SubmitSourceBuffer() failed: %08X", hr);
m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS;
}
void XAudio2AudioStream::SetOutputVolume(u32 volume)
{
HRESULT hr = m_mastering_voice->SetVolume(static_cast<float>(m_volume) / 100.0f);
if (FAILED(hr))
{
Log_ErrorPrintf("SetVolume() failed: %08X", hr);
return;
}
m_volume = volume;
}
void __stdcall XAudio2AudioStream::OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
void __stdcall XAudio2AudioStream::OnVoiceProcessingPassEnd(void) {}
void __stdcall XAudio2AudioStream::OnStreamEnd(void) {}
void __stdcall XAudio2AudioStream::OnBufferStart(void* pBufferContext) {}
void __stdcall XAudio2AudioStream::OnBufferEnd(void* pBufferContext)
{
EnqueueBuffer();
}
void __stdcall XAudio2AudioStream::OnLoopEnd(void* pBufferContext) {}
void __stdcall XAudio2AudioStream::OnVoiceError(void* pBufferContext, HRESULT Error) {}

View File

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/windows_headers.h"
#include "util/audio_stream.h"
#include <array>
#include <cstdint>
#include <memory>
#include <wrl/client.h>
// We need to use the Windows 10 headers otherwise this won't compile.
#undef _WIN32_WINNT
#define _WIN32_WINNT _WIN32_WINNT_WIN10
#include <xaudio2.h>
class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback
{
public:
XAudio2AudioStream(u32 sample_rate, u32 channels, u32 buffer_ms, AudioStretchMode stretch);
~XAudio2AudioStream();
void SetPaused(bool paused) override;
void SetOutputVolume(u32 volume) override;
bool OpenDevice(u32 latency_ms);
void CloseDevice();
void EnqueueBuffer();
private:
enum : u32
{
NUM_BUFFERS = 2,
INTERNAL_BUFFER_SIZE = 512,
};
ALWAYS_INLINE bool IsOpen() const { return static_cast<bool>(m_xaudio); }
// Inherited via IXAudio2VoiceCallback
void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override;
void __stdcall OnVoiceProcessingPassEnd(void) override;
void __stdcall OnStreamEnd(void) override;
void __stdcall OnBufferStart(void* pBufferContext) override;
void __stdcall OnBufferEnd(void* pBufferContext) override;
void __stdcall OnLoopEnd(void* pBufferContext) override;
void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override;
Microsoft::WRL::ComPtr<IXAudio2> m_xaudio;
IXAudio2MasteringVoice* m_mastering_voice = nullptr;
IXAudio2SourceVoice* m_source_voice = nullptr;
std::array<std::unique_ptr<SampleType[]>, NUM_BUFFERS> m_enqueue_buffers;
u32 m_enqueue_buffer_size = 0;
u32 m_current_buffer = 0;
bool m_buffer_enqueued = false;
HMODULE m_xaudio2_library = {};
bool m_com_initialized_by_us = false;
};

487
src/util/xinput_source.cpp Normal file
View File

@ -0,0 +1,487 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "xinput_source.h"
#include "common/assert.h"
#include "common/log.h"
#include "common/string_util.h"
#include "core/host.h"
#include "input_manager.h"
#include <cmath>
Log_SetChannel(XInputSource);
const char* XInputSource::s_axis_names[XInputSource::NUM_AXES] = {
"LeftX", // AXIS_LEFTX
"LeftY", // AXIS_LEFTY
"RightX", // AXIS_RIGHTX
"RightY", // AXIS_RIGHTY
"LeftTrigger", // AXIS_TRIGGERLEFT
"RightTrigger", // AXIS_TRIGGERRIGHT
};
static const GenericInputBinding s_xinput_generic_binding_axis_mapping[][2] = {
{GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // AXIS_LEFTX
{GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // AXIS_LEFTY
{GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // AXIS_RIGHTX
{GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // AXIS_RIGHTY
{GenericInputBinding::Unknown, GenericInputBinding::L2}, // AXIS_TRIGGERLEFT
{GenericInputBinding::Unknown, GenericInputBinding::R2}, // AXIS_TRIGGERRIGHT
};
const char* XInputSource::s_button_names[XInputSource::NUM_BUTTONS] = {
"DPadUp", // XINPUT_GAMEPAD_DPAD_UP
"DPadDown", // XINPUT_GAMEPAD_DPAD_DOWN
"DPadLeft", // XINPUT_GAMEPAD_DPAD_LEFT
"DPadRight", // XINPUT_GAMEPAD_DPAD_RIGHT
"Start", // XINPUT_GAMEPAD_START
"Back", // XINPUT_GAMEPAD_BACK
"LeftStick", // XINPUT_GAMEPAD_LEFT_THUMB
"RightStick", // XINPUT_GAMEPAD_RIGHT_THUMB
"LeftShoulder", // XINPUT_GAMEPAD_LEFT_SHOULDER
"RightShoulder", // XINPUT_GAMEPAD_RIGHT_SHOULDER
"A", // XINPUT_GAMEPAD_A
"B", // XINPUT_GAMEPAD_B
"X", // XINPUT_GAMEPAD_X
"Y", // XINPUT_GAMEPAD_Y
"Guide", // XINPUT_GAMEPAD_GUIDE
};
const u16 XInputSource::s_button_masks[XInputSource::NUM_BUTTONS] = {
XINPUT_GAMEPAD_DPAD_UP,
XINPUT_GAMEPAD_DPAD_DOWN,
XINPUT_GAMEPAD_DPAD_LEFT,
XINPUT_GAMEPAD_DPAD_RIGHT,
XINPUT_GAMEPAD_START,
XINPUT_GAMEPAD_BACK,
XINPUT_GAMEPAD_LEFT_THUMB,
XINPUT_GAMEPAD_RIGHT_THUMB,
XINPUT_GAMEPAD_LEFT_SHOULDER,
XINPUT_GAMEPAD_RIGHT_SHOULDER,
XINPUT_GAMEPAD_A,
XINPUT_GAMEPAD_B,
XINPUT_GAMEPAD_X,
XINPUT_GAMEPAD_Y,
0x400, // XINPUT_GAMEPAD_GUIDE
};
static const GenericInputBinding s_xinput_generic_binding_button_mapping[] = {
GenericInputBinding::DPadUp, // XINPUT_GAMEPAD_DPAD_UP
GenericInputBinding::DPadDown, // XINPUT_GAMEPAD_DPAD_DOWN
GenericInputBinding::DPadLeft, // XINPUT_GAMEPAD_DPAD_LEFT
GenericInputBinding::DPadRight, // XINPUT_GAMEPAD_DPAD_RIGHT
GenericInputBinding::Start, // XINPUT_GAMEPAD_START
GenericInputBinding::Select, // XINPUT_GAMEPAD_BACK
GenericInputBinding::L3, // XINPUT_GAMEPAD_LEFT_THUMB
GenericInputBinding::R3, // XINPUT_GAMEPAD_RIGHT_THUMB
GenericInputBinding::L1, // XINPUT_GAMEPAD_LEFT_SHOULDER
GenericInputBinding::R1, // XINPUT_GAMEPAD_RIGHT_SHOULDER
GenericInputBinding::Cross, // XINPUT_GAMEPAD_A
GenericInputBinding::Circle, // XINPUT_GAMEPAD_B
GenericInputBinding::Square, // XINPUT_GAMEPAD_X
GenericInputBinding::Triangle, // XINPUT_GAMEPAD_Y
GenericInputBinding::System, // XINPUT_GAMEPAD_GUIDE
};
XInputSource::XInputSource() = default;
XInputSource::~XInputSource() = default;
bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
{
// xinput1_3.dll is flawed and obsolete, but it's also commonly used by wrappers.
// For this reason, try to load it *only* from the application directory, and not system32.
m_xinput_module = LoadLibraryExW(L"xinput1_3", nullptr, LOAD_LIBRARY_SEARCH_APPLICATION_DIR);
if (!m_xinput_module)
{
m_xinput_module = LoadLibraryW(L"xinput1_4");
}
if (!m_xinput_module)
{
m_xinput_module = LoadLibraryW(L"xinput9_1_0");
}
if (!m_xinput_module)
{
Log_ErrorPrintf("Failed to load XInput module.");
return false;
}
// Try the hidden version of XInputGetState(), which lets us query the guide button.
m_xinput_get_state =
reinterpret_cast<decltype(m_xinput_get_state)>(GetProcAddress(m_xinput_module, reinterpret_cast<LPCSTR>(100)));
if (!m_xinput_get_state)
reinterpret_cast<decltype(m_xinput_get_state)>(GetProcAddress(m_xinput_module, "XInputGetState"));
m_xinput_set_state =
reinterpret_cast<decltype(m_xinput_set_state)>(GetProcAddress(m_xinput_module, "XInputSetState"));
m_xinput_get_capabilities =
reinterpret_cast<decltype(m_xinput_get_capabilities)>(GetProcAddress(m_xinput_module, "XInputGetCapabilities"));
if (!m_xinput_get_state || !m_xinput_set_state || !m_xinput_get_capabilities)
{
Log_ErrorPrintf("Failed to get XInput function pointers.");
return false;
}
ReloadDevices();
return true;
}
void XInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) {}
bool XInputSource::ReloadDevices()
{
bool changed = false;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
XINPUT_STATE new_state;
DWORD result = m_xinput_get_state(i, &new_state);
if (result == ERROR_SUCCESS)
{
if (m_controllers[i].connected)
continue;
HandleControllerConnection(i);
changed = true;
}
else if (result == ERROR_DEVICE_NOT_CONNECTED)
{
if (!m_controllers[i].connected)
continue;
HandleControllerDisconnection(i);
changed = true;
}
}
return changed;
}
void XInputSource::Shutdown()
{
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (m_controllers[i].connected)
HandleControllerDisconnection(i);
}
if (m_xinput_module)
{
FreeLibrary(m_xinput_module);
m_xinput_module = nullptr;
}
m_xinput_get_state = nullptr;
m_xinput_set_state = nullptr;
m_xinput_get_capabilities = nullptr;
}
void XInputSource::PollEvents()
{
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
const bool was_connected = m_controllers[i].connected;
if (!was_connected)
continue;
XINPUT_STATE new_state;
DWORD result = m_xinput_get_state(i, &new_state);
if (result == ERROR_SUCCESS)
{
if (!was_connected)
HandleControllerConnection(i);
CheckForStateChanges(i, new_state);
}
else
{
if (result != ERROR_DEVICE_NOT_CONNECTED)
Log_WarningPrintf("XInputGetState(%u) failed: 0x%08X / 0x%08X", i, result, GetLastError());
if (was_connected)
HandleControllerDisconnection(i);
}
}
}
std::vector<std::pair<std::string, std::string>> XInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (!m_controllers[i].connected)
continue;
ret.emplace_back(StringUtil::StdStringFromFormat("XInput-%u", i),
StringUtil::StdStringFromFormat("XInput Controller %u", i));
}
return ret;
}
std::optional<InputBindingKey> XInputSource::ParseKeyString(const std::string_view& device,
const std::string_view& binding)
{
if (!StringUtil::StartsWith(device, "XInput-") || binding.empty())
return std::nullopt;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7));
if (!player_id.has_value() || player_id.value() < 0)
return std::nullopt;
InputBindingKey key = {};
key.source_type = InputSourceType::XInput;
key.source_index = static_cast<u32>(player_id.value());
if (StringUtil::EndsWith(binding, "Motor"))
{
key.source_subtype = InputSubclass::ControllerMotor;
if (binding == "LargeMotor")
{
key.data = 0;
return key;
}
else if (binding == "SmallMotor")
{
key.data = 1;
return key;
}
else
{
return std::nullopt;
}
}
else if (binding[0] == '+' || binding[0] == '-')
{
// likely an axis
const std::string_view axis_name(binding.substr(1));
for (u32 i = 0; i < std::size(s_axis_names); i++)
{
if (axis_name == s_axis_names[i])
{
// found an axis!
key.source_subtype = InputSubclass::ControllerAxis;
key.data = i;
key.modifier = binding[0] == '-' ? InputModifier::Negate : InputModifier::None;
return key;
}
}
}
else
{
// must be a button
for (u32 i = 0; i < std::size(s_button_names); i++)
{
if (binding == s_button_names[i])
{
key.source_subtype = InputSubclass::ControllerButton;
key.data = i;
return key;
}
}
}
// unknown axis/button
return std::nullopt;
}
std::string XInputSource::ConvertKeyToString(InputBindingKey key)
{
std::string ret;
if (key.source_type == InputSourceType::XInput)
{
if (key.source_subtype == InputSubclass::ControllerAxis && key.data < std::size(s_axis_names))
{
const char modifier = key.modifier == InputModifier::Negate ? '-' : '+';
ret = StringUtil::StdStringFromFormat("XInput-%u/%c%s", key.source_index, modifier, s_axis_names[key.data]);
}
else if (key.source_subtype == InputSubclass::ControllerButton && key.data < std::size(s_button_names))
{
ret = StringUtil::StdStringFromFormat("XInput-%u/%s", key.source_index, s_button_names[key.data]);
}
else if (key.source_subtype == InputSubclass::ControllerMotor)
{
ret = StringUtil::StdStringFromFormat("XInput-%u/%sMotor", key.source_index, key.data ? "Large" : "Small");
}
}
return ret;
}
std::vector<InputBindingKey> XInputSource::EnumerateMotors()
{
std::vector<InputBindingKey> ret;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
const ControllerData& cd = m_controllers[i];
if (!cd.connected)
continue;
if (cd.has_large_motor)
ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 0));
if (cd.has_small_motor)
ret.push_back(MakeGenericControllerMotorKey(InputSourceType::XInput, i, 1));
}
return ret;
}
bool XInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping)
{
if (!StringUtil::StartsWith(device, "XInput-"))
return false;
const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7));
if (!player_id.has_value() || player_id.value() < 0)
return false;
if (player_id.value() < 0 || player_id.value() >= static_cast<s32>(XUSER_MAX_COUNT))
return false;
// assume all buttons are present.
const s32 pid = player_id.value();
for (u32 i = 0; i < std::size(s_xinput_generic_binding_axis_mapping); i++)
{
const GenericInputBinding negative = s_xinput_generic_binding_axis_mapping[i][0];
const GenericInputBinding positive = s_xinput_generic_binding_axis_mapping[i][1];
if (negative != GenericInputBinding::Unknown)
mapping->emplace_back(negative, StringUtil::StdStringFromFormat("XInput-%d/-%s", pid, s_axis_names[i]));
if (positive != GenericInputBinding::Unknown)
mapping->emplace_back(positive, StringUtil::StdStringFromFormat("XInput-%d/+%s", pid, s_axis_names[i]));
}
for (u32 i = 0; i < std::size(s_xinput_generic_binding_button_mapping); i++)
{
const GenericInputBinding binding = s_xinput_generic_binding_button_mapping[i];
if (binding != GenericInputBinding::Unknown)
mapping->emplace_back(binding, StringUtil::StdStringFromFormat("XInput-%d/%s", pid, s_button_names[i]));
}
if (m_controllers[pid].has_small_motor)
mapping->emplace_back(GenericInputBinding::SmallMotor,
StringUtil::StdStringFromFormat("XInput-%d/SmallMotor", pid));
if (m_controllers[pid].has_large_motor)
mapping->emplace_back(GenericInputBinding::LargeMotor,
StringUtil::StdStringFromFormat("XInput-%d/LargeMotor", pid));
return true;
}
void XInputSource::HandleControllerConnection(u32 index)
{
Log_InfoPrintf("XInput controller %u connected.", index);
XINPUT_CAPABILITIES caps = {};
if (m_xinput_get_capabilities(index, 0, &caps) != ERROR_SUCCESS)
Log_WarningPrintf("Failed to get XInput capabilities for controller %u", index);
ControllerData& cd = m_controllers[index];
cd.connected = true;
cd.has_large_motor = caps.Vibration.wLeftMotorSpeed != 0;
cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0;
cd.last_state = {};
InputManager::OnInputDeviceConnected(StringUtil::StdStringFromFormat("XInput-%u", index),
StringUtil::StdStringFromFormat("XInput Controller %u", index));
}
void XInputSource::HandleControllerDisconnection(u32 index)
{
Log_InfoPrintf("XInput controller %u disconnected.", index);
InputManager::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("XInput-%u", index));
m_controllers[index] = {};
}
void XInputSource::CheckForStateChanges(u32 index, const XINPUT_STATE& new_state)
{
ControllerData& cd = m_controllers[index];
if (new_state.dwPacketNumber == cd.last_state.dwPacketNumber)
return;
XINPUT_GAMEPAD& ogp = cd.last_state.Gamepad;
const XINPUT_GAMEPAD& ngp = new_state.Gamepad;
#define CHECK_AXIS(field, axis, min_value, max_value) \
if (ogp.field != ngp.field) \
{ \
InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::XInput, index, axis), \
static_cast<float>(ngp.field) / ((ngp.field < 0) ? min_value : max_value), \
GenericInputBinding::Unknown); \
}
// Y axes is inverted in XInput when compared to SDL.
CHECK_AXIS(sThumbLX, AXIS_LEFTX, 32768, 32767);
CHECK_AXIS(sThumbLY, AXIS_LEFTY, -32768, -32767);
CHECK_AXIS(sThumbRX, AXIS_RIGHTX, 32768, 32767);
CHECK_AXIS(sThumbRY, AXIS_RIGHTY, -32768, -32767);
CHECK_AXIS(bLeftTrigger, AXIS_LEFTTRIGGER, 0, 255);
CHECK_AXIS(bRightTrigger, AXIS_RIGHTTRIGGER, 0, 255);
#undef CHECK_AXIS
const u16 old_button_bits = ogp.wButtons;
const u16 new_button_bits = ngp.wButtons;
if (old_button_bits != new_button_bits)
{
for (u32 button = 0; button < NUM_BUTTONS; button++)
{
const u16 button_mask = s_button_masks[button];
if ((old_button_bits & button_mask) != (new_button_bits & button_mask))
{
const GenericInputBinding generic_key = (button < std::size(s_xinput_generic_binding_button_mapping)) ?
s_xinput_generic_binding_button_mapping[button] :
GenericInputBinding::Unknown;
const float value = ((new_button_bits & button_mask) != 0) ? 1.0f : 0.0f;
InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::XInput, index, button), value,
generic_key);
}
}
}
cd.last_state = new_state;
}
void XInputSource::UpdateMotorState(InputBindingKey key, float intensity)
{
if (key.source_subtype != InputSubclass::ControllerMotor || key.source_index >= NUM_CONTROLLERS)
return;
ControllerData& cd = m_controllers[key.source_index];
if (!cd.connected)
return;
const u16 i_intensity = static_cast<u16>(intensity * 65535.0f);
if (key.data != 0)
cd.last_vibration.wRightMotorSpeed = i_intensity;
else
cd.last_vibration.wLeftMotorSpeed = i_intensity;
m_xinput_set_state(key.source_index, &cd.last_vibration);
}
void XInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity)
{
if (large_key.source_index != small_key.source_index || large_key.source_subtype != InputSubclass::ControllerMotor ||
small_key.source_subtype != InputSubclass::ControllerMotor)
{
// bonkers config where they're mapped to different controllers... who would do such a thing?
UpdateMotorState(large_key, large_intensity);
UpdateMotorState(small_key, small_intensity);
return;
}
ControllerData& cd = m_controllers[large_key.source_index];
if (!cd.connected)
return;
cd.last_vibration.wLeftMotorSpeed = static_cast<u16>(large_intensity * 65535.0f);
cd.last_vibration.wRightMotorSpeed = static_cast<u16>(small_intensity * 65535.0f);
m_xinput_set_state(large_key.source_index, &cd.last_vibration);
}
std::unique_ptr<InputSource> InputSource::CreateXInputSource()
{
return std::make_unique<XInputSource>();
}

81
src/util/xinput_source.h Normal file
View File

@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "common/windows_headers.h"
#include "input_source.h"
#include <Xinput.h>
#include <array>
#include <functional>
#include <mutex>
#include <vector>
class SettingsInterface;
class XInputSource final : public InputSource
{
public:
XInputSource();
~XInputSource();
bool Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
void UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock) override;
bool ReloadDevices() override;
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
float small_intensity) override;
std::optional<InputBindingKey> ParseKeyString(const std::string_view& device,
const std::string_view& binding) override;
std::string ConvertKeyToString(InputBindingKey key) override;
private:
enum : u32
{
NUM_CONTROLLERS = XUSER_MAX_COUNT, // 4
NUM_BUTTONS = 15,
};
enum : u32
{
AXIS_LEFTX,
AXIS_LEFTY,
AXIS_RIGHTX,
AXIS_RIGHTY,
AXIS_LEFTTRIGGER,
AXIS_RIGHTTRIGGER,
NUM_AXES,
};
struct ControllerData
{
XINPUT_STATE last_state;
XINPUT_VIBRATION last_vibration = {};
bool connected = false;
bool has_large_motor = false;
bool has_small_motor = false;
};
using ControllerDataArray = std::array<ControllerData, NUM_CONTROLLERS>;
void CheckForStateChanges(u32 index, const XINPUT_STATE& new_state);
void HandleControllerConnection(u32 index);
void HandleControllerDisconnection(u32 index);
ControllerDataArray m_controllers;
HMODULE m_xinput_module{};
DWORD(WINAPI* m_xinput_get_state)(DWORD, XINPUT_STATE*) = nullptr;
DWORD(WINAPI* m_xinput_set_state)(DWORD, XINPUT_VIBRATION*) = nullptr;
DWORD(WINAPI* m_xinput_get_capabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*) = nullptr;
static const char* s_axis_names[NUM_AXES];
static const char* s_button_names[NUM_BUTTONS];
static const u16 s_button_masks[NUM_BUTTONS];
};