Move frontend-common to util/core
This commit is contained in:
@ -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()
|
||||
|
||||
@ -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();
|
||||
|
||||
288
src/util/cubeb_audio_stream.cpp
Normal file
288
src/util/cubeb_audio_stream.cpp
Normal 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, ¶ms, &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, ¶ms, 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;
|
||||
}
|
||||
37
src/util/cubeb_audio_stream.h
Normal file
37
src/util/cubeb_audio_stream.h
Normal 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
|
||||
};
|
||||
1194
src/util/d3d11_host_display.cpp
Normal file
1194
src/util/d3d11_host_display.cpp
Normal file
File diff suppressed because it is too large
Load Diff
159
src/util/d3d11_host_display.h
Normal file
159
src/util/d3d11_host_display.h
Normal 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;
|
||||
};
|
||||
1062
src/util/d3d12_host_display.cpp
Normal file
1062
src/util/d3d12_host_display.cpp
Normal file
File diff suppressed because it is too large
Load Diff
143
src/util/d3d12_host_display.h
Normal file
143
src/util/d3d12_host_display.h
Normal 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
485
src/util/dinput_source.cpp
Normal 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
86
src/util/dinput_source.h
Normal 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
12
src/util/display_ps.hlsl
Normal 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
142
src/util/display_ps.hlsl.h
Normal 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
|
||||
};
|
||||
131
src/util/display_ps_alpha.hlsl.h
Normal file
131
src/util/display_ps_alpha.hlsl.h
Normal 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
13
src/util/display_vs.hlsl
Normal 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
197
src/util/display_vs.hlsl.h
Normal 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
702
src/util/host_display.cpp
Normal 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
277
src/util/host_display.h
Normal 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
|
||||
2569
src/util/imgui_fullscreen.cpp
Normal file
2569
src/util/imgui_fullscreen.cpp
Normal file
File diff suppressed because it is too large
Load Diff
294
src/util/imgui_fullscreen.h
Normal file
294
src/util/imgui_fullscreen.h
Normal 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
|
||||
499
src/util/imgui_impl_dx11.cpp
Normal file
499
src/util/imgui_impl_dx11.cpp
Normal 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);
|
||||
}
|
||||
17
src/util/imgui_impl_dx11.h
Normal file
17
src/util/imgui_impl_dx11.h
Normal 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();
|
||||
545
src/util/imgui_impl_dx12.cpp
Normal file
545
src/util/imgui_impl_dx12.cpp
Normal 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();
|
||||
}
|
||||
14
src/util/imgui_impl_dx12.h
Normal file
14
src/util/imgui_impl_dx12.h
Normal 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();
|
||||
569
src/util/imgui_impl_opengl3.cpp
Normal file
569
src/util/imgui_impl_opengl3.cpp
Normal 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
|
||||
16
src/util/imgui_impl_opengl3.h
Normal file
16
src/util/imgui_impl_opengl3.h
Normal 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();
|
||||
685
src/util/imgui_impl_vulkan.cpp
Normal file
685
src/util/imgui_impl_vulkan.cpp
Normal 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);
|
||||
}
|
||||
12
src/util/imgui_impl_vulkan.h
Normal file
12
src/util/imgui_impl_vulkan.h
Normal 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
850
src/util/imgui_manager.cpp
Normal 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
85
src/util/imgui_manager.h
Normal 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
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
354
src/util/input_manager.h
Normal 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
156
src/util/input_source.cpp
Normal 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
86
src/util/input_source.h
Normal 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
|
||||
};
|
||||
3
src/util/make_d3d_display_shader_bytecode.bat
Normal file
3
src/util/make_d3d_display_shader_bytecode.bat
Normal 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
|
||||
1142
src/util/opengl_host_display.cpp
Normal file
1142
src/util/opengl_host_display.cpp
Normal file
File diff suppressed because it is too large
Load Diff
140
src/util/opengl_host_display.h
Normal file
140
src/util/opengl_host_display.h
Normal 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
20
src/util/platform_misc.h
Normal 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
|
||||
132
src/util/platform_misc_mac.mm
Normal file
132
src/util/platform_misc_mac.mm
Normal 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];
|
||||
}
|
||||
188
src/util/platform_misc_unix.cpp
Normal file
188
src/util/platform_misc_unix.cpp
Normal 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
|
||||
}
|
||||
56
src/util/platform_misc_win32.cpp
Normal file
56
src/util/platform_misc_win32.cpp
Normal 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);
|
||||
}
|
||||
185
src/util/postprocessing_chain.cpp
Normal file
185
src/util/postprocessing_chain.cpp
Normal 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
|
||||
39
src/util/postprocessing_chain.h
Normal file
39
src/util/postprocessing_chain.h
Normal 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
|
||||
438
src/util/postprocessing_shader.cpp
Normal file
438
src/util/postprocessing_shader.cpp
Normal 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 = ¤t_option.min_value;
|
||||
else if (key == "MaxValue")
|
||||
dst_array = ¤t_option.max_value;
|
||||
else if (key == "DefaultValue")
|
||||
dst_array = ¤t_option.default_value;
|
||||
else // if (key == "StepAmount")
|
||||
dst_array = ¤t_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
|
||||
113
src/util/postprocessing_shader.h
Normal file
113
src/util/postprocessing_shader.h
Normal 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
|
||||
204
src/util/postprocessing_shadergen.cpp
Normal file
204
src/util/postprocessing_shadergen.cpp
Normal 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
|
||||
24
src/util/postprocessing_shadergen.h
Normal file
24
src/util/postprocessing_shadergen.h
Normal 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
|
||||
949
src/util/sdl_input_source.cpp
Normal file
949
src/util/sdl_input_source.cpp
Normal 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>();
|
||||
}
|
||||
92
src/util/sdl_input_source.h
Normal file
92
src/util/sdl_input_source.h
Normal 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
679
src/util/shadergen.cpp
Normal 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
60
src/util/shadergen.h
Normal 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;
|
||||
};
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
1236
src/util/vulkan_host_display.cpp
Normal file
1236
src/util/vulkan_host_display.cpp
Normal file
File diff suppressed because it is too large
Load Diff
144
src/util/vulkan_host_display.h
Normal file
144
src/util/vulkan_host_display.h
Normal 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;
|
||||
};
|
||||
272
src/util/win32_raw_input_source.cpp
Normal file
272
src/util/win32_raw_input_source.cpp
Normal 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>();
|
||||
}
|
||||
61
src/util/win32_raw_input_source.h
Normal file
61
src/util/win32_raw_input_source.h
Normal 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;
|
||||
};
|
||||
207
src/util/xaudio2_audio_stream.cpp
Normal file
207
src/util/xaudio2_audio_stream.cpp
Normal 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) {}
|
||||
59
src/util/xaudio2_audio_stream.h
Normal file
59
src/util/xaudio2_audio_stream.h
Normal 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
487
src/util/xinput_source.cpp
Normal 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
81
src/util/xinput_source.h
Normal 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];
|
||||
};
|
||||
Reference in New Issue
Block a user