move sunshine to src
- this will allow for common cpp workflow files within org
This commit is contained in:
164
src/platform/windows/PolicyConfig.h
Normal file
164
src/platform/windows/PolicyConfig.h
Normal file
@@ -0,0 +1,164 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// PolicyConfig.h
|
||||
// Undocumented COM-interface IPolicyConfig.
|
||||
// Use for set default audio render endpoint
|
||||
// @author EreTIk
|
||||
// http://eretik.omegahg.com/
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#undef DEFINE_GUID
|
||||
#ifdef __cplusplus
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
#else
|
||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
||||
#endif
|
||||
|
||||
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
|
||||
DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
|
||||
|
||||
#endif
|
||||
|
||||
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
|
||||
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
|
||||
// ----------------------------------------------------------------------------
|
||||
// class CPolicyConfigClient
|
||||
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
|
||||
//
|
||||
// interface IPolicyConfig
|
||||
// {f8679f50-850a-41cf-9c72-430f290290c8}
|
||||
//
|
||||
// Query interface:
|
||||
// CComPtr<IPolicyConfig> PolicyConfig;
|
||||
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
|
||||
//
|
||||
// @compatible: Windows 7 and Later
|
||||
// ----------------------------------------------------------------------------
|
||||
interface IPolicyConfig : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
|
||||
PCWSTR);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
PINT64,
|
||||
PINT64);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
PCWSTR wszDeviceId,
|
||||
ERole eRole);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT);
|
||||
};
|
||||
|
||||
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;
|
||||
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient;
|
||||
// ----------------------------------------------------------------------------
|
||||
// class CPolicyConfigVistaClient
|
||||
// {294935CE-F637-4E7C-A41B-AB255460B862}
|
||||
//
|
||||
// interface IPolicyConfigVista
|
||||
// {568b9108-44bf-40b4-9006-86afe5b5a620}
|
||||
//
|
||||
// Query interface:
|
||||
// CComPtr<IPolicyConfigVista> PolicyConfig;
|
||||
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
|
||||
//
|
||||
// @compatible: Windows Vista and Later
|
||||
// ----------------------------------------------------------------------------
|
||||
interface IPolicyConfigVista : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
PINT64,
|
||||
PINT64); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
const PROPERTYKEY &,
|
||||
PROPVARIANT *);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
PCWSTR wszDeviceId,
|
||||
ERole eRole);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT); // not available on Windows 7, use method from IPolicyConfig
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
988
src/platform/windows/audio.cpp
Normal file
988
src/platform/windows/audio.cpp
Normal file
@@ -0,0 +1,988 @@
|
||||
//
|
||||
// Created by loki on 1/12/20.
|
||||
//
|
||||
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <roapi.h>
|
||||
|
||||
#include <codecvt>
|
||||
|
||||
#include <synchapi.h>
|
||||
|
||||
#define INITGUID
|
||||
#include <propkeydef.h>
|
||||
#undef INITGUID
|
||||
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
// Must be the last included file
|
||||
// clang-format off
|
||||
#include "PolicyConfig.h"
|
||||
// clang-format on
|
||||
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
||||
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
|
||||
|
||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
|
||||
|
||||
using namespace std::literals;
|
||||
namespace platf::audio {
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
|
||||
template<class T>
|
||||
void Release(T *p) {
|
||||
p->Release();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void co_task_free(T *p) {
|
||||
CoTaskMemFree((LPVOID)p);
|
||||
}
|
||||
|
||||
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
|
||||
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
|
||||
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
|
||||
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
|
||||
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
|
||||
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
|
||||
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
|
||||
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
|
||||
using policy_t = util::safe_ptr<IPolicyConfig, Release<IPolicyConfig>>;
|
||||
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
|
||||
|
||||
class co_init_t : public deinit_t {
|
||||
public:
|
||||
co_init_t() {
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY);
|
||||
}
|
||||
|
||||
~co_init_t() override {
|
||||
CoUninitialize();
|
||||
}
|
||||
};
|
||||
|
||||
class prop_var_t {
|
||||
public:
|
||||
prop_var_t() {
|
||||
PropVariantInit(&prop);
|
||||
}
|
||||
|
||||
~prop_var_t() {
|
||||
PropVariantClear(&prop);
|
||||
}
|
||||
|
||||
PROPVARIANT prop;
|
||||
};
|
||||
|
||||
class audio_pipe_t {
|
||||
public:
|
||||
static constexpr auto stereo = 2;
|
||||
static constexpr auto channels51 = 6;
|
||||
static constexpr auto channels71 = 8;
|
||||
|
||||
using samples_t = std::vector<std::int16_t>;
|
||||
using buf_t = util::buffer_t<std::int16_t>;
|
||||
|
||||
virtual void to_stereo(samples_t &out, const buf_t &in) = 0;
|
||||
virtual void to_51(samples_t &out, const buf_t &in) = 0;
|
||||
virtual void to_71(samples_t &out, const buf_t &in) = 0;
|
||||
};
|
||||
|
||||
class mono_t : public audio_pipe_t {
|
||||
public:
|
||||
void to_stereo(samples_t &out, const buf_t &in) override {
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end;) {
|
||||
*sample_out_p++ = *sample_in_pos * 7 / 10;
|
||||
*sample_out_p++ = *sample_in_pos++ * 7 / 10;
|
||||
}
|
||||
}
|
||||
|
||||
void to_51(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||
int left = *sample_in_pos++;
|
||||
|
||||
auto fl = (left * 7 / 10);
|
||||
|
||||
sample_out_p[FRONT_LEFT] = fl;
|
||||
sample_out_p[FRONT_RIGHT] = fl;
|
||||
sample_out_p[FRONT_CENTER] = fl * 6;
|
||||
sample_out_p[LOW_FREQUENCY] = fl / 10;
|
||||
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||
sample_out_p[BACK_RIGHT] = left * 4 / 10;
|
||||
}
|
||||
}
|
||||
|
||||
void to_71(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||
int left = *sample_in_pos++;
|
||||
|
||||
auto fl = (left * 7 / 10);
|
||||
|
||||
sample_out_p[FRONT_LEFT] = fl;
|
||||
sample_out_p[FRONT_RIGHT] = fl;
|
||||
sample_out_p[FRONT_CENTER] = fl * 6;
|
||||
sample_out_p[LOW_FREQUENCY] = fl / 10;
|
||||
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||
sample_out_p[BACK_RIGHT] = left * 4 / 10;
|
||||
sample_out_p[SIDE_LEFT] = left * 5 / 10;
|
||||
sample_out_p[SIDE_RIGHT] = left * 5 / 10;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class stereo_t : public audio_pipe_t {
|
||||
public:
|
||||
void to_stereo(samples_t &out, const buf_t &in) override {
|
||||
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||
}
|
||||
|
||||
void to_51(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||
int left = sample_in_pos[speaker::FRONT_LEFT];
|
||||
int right = sample_in_pos[speaker::FRONT_RIGHT];
|
||||
|
||||
sample_in_pos += 2;
|
||||
|
||||
auto fl = (left * 7 / 10);
|
||||
auto fr = (right * 7 / 10);
|
||||
|
||||
auto mix = (fl + fr) / 2;
|
||||
|
||||
sample_out_p[FRONT_LEFT] = fl;
|
||||
sample_out_p[FRONT_RIGHT] = fr;
|
||||
sample_out_p[FRONT_CENTER] = mix;
|
||||
sample_out_p[LOW_FREQUENCY] = mix / 2;
|
||||
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||
sample_out_p[BACK_RIGHT] = right * 4 / 10;
|
||||
}
|
||||
}
|
||||
|
||||
void to_71(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||
int left = sample_in_pos[speaker::FRONT_LEFT];
|
||||
int right = sample_in_pos[speaker::FRONT_RIGHT];
|
||||
|
||||
sample_in_pos += 2;
|
||||
|
||||
auto fl = (left * 7 / 10);
|
||||
auto fr = (right * 7 / 10);
|
||||
|
||||
auto mix = (fl + fr) / 2;
|
||||
|
||||
sample_out_p[FRONT_LEFT] = fl;
|
||||
sample_out_p[FRONT_RIGHT] = fr;
|
||||
sample_out_p[FRONT_CENTER] = mix;
|
||||
sample_out_p[LOW_FREQUENCY] = mix / 2;
|
||||
sample_out_p[BACK_LEFT] = left * 4 / 10;
|
||||
sample_out_p[BACK_RIGHT] = right * 4 / 10;
|
||||
sample_out_p[SIDE_LEFT] = left * 5 / 10;
|
||||
sample_out_p[SIDE_RIGHT] = right * 5 / 10;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class surr51_t : public audio_pipe_t {
|
||||
public:
|
||||
void to_stereo(samples_t &out, const buf_t &in) {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) {
|
||||
int left {}, right {};
|
||||
|
||||
left += sample_in_pos[FRONT_LEFT];
|
||||
left += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||
left += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||
left += sample_in_pos[BACK_LEFT] * 7 / 10;
|
||||
left += sample_in_pos[BACK_RIGHT] * 3 / 10;
|
||||
|
||||
right += sample_in_pos[FRONT_RIGHT];
|
||||
right += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||
right += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||
right += sample_in_pos[BACK_LEFT] * 3 / 10;
|
||||
right += sample_in_pos[BACK_RIGHT] * 7 / 10;
|
||||
|
||||
sample_out_p[0] = left;
|
||||
sample_out_p[1] = right;
|
||||
|
||||
sample_in_pos += channels51;
|
||||
}
|
||||
}
|
||||
|
||||
void to_51(samples_t &out, const buf_t &in) override {
|
||||
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||
}
|
||||
|
||||
void to_71(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels71) {
|
||||
int fl = sample_in_pos[FRONT_LEFT];
|
||||
int fr = sample_in_pos[FRONT_RIGHT];
|
||||
int bl = sample_in_pos[BACK_LEFT];
|
||||
int br = sample_in_pos[BACK_RIGHT];
|
||||
|
||||
auto mix_l = (fl + bl) / 2;
|
||||
auto mix_r = (bl + br) / 2;
|
||||
|
||||
sample_out_p[FRONT_LEFT] = fl;
|
||||
sample_out_p[FRONT_RIGHT] = fr;
|
||||
sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER];
|
||||
sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY];
|
||||
sample_out_p[BACK_LEFT] = bl;
|
||||
sample_out_p[BACK_RIGHT] = br;
|
||||
sample_out_p[SIDE_LEFT] = mix_l;
|
||||
sample_out_p[SIDE_RIGHT] = mix_r;
|
||||
|
||||
sample_in_pos += channels51;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class surr71_t : public audio_pipe_t {
|
||||
public:
|
||||
void to_stereo(samples_t &out, const buf_t &in) {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += stereo) {
|
||||
int left {}, right {};
|
||||
|
||||
left += sample_in_pos[FRONT_LEFT];
|
||||
left += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||
left += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||
left += sample_in_pos[BACK_LEFT] * 7 / 10;
|
||||
left += sample_in_pos[BACK_RIGHT] * 3 / 10;
|
||||
left += sample_in_pos[SIDE_LEFT];
|
||||
|
||||
right += sample_in_pos[FRONT_RIGHT];
|
||||
right += sample_in_pos[FRONT_CENTER] * 9 / 10;
|
||||
right += sample_in_pos[LOW_FREQUENCY] * 3 / 10;
|
||||
right += sample_in_pos[BACK_LEFT] * 3 / 10;
|
||||
right += sample_in_pos[BACK_RIGHT] * 7 / 10;
|
||||
right += sample_in_pos[SIDE_RIGHT];
|
||||
|
||||
sample_out_p[0] = left;
|
||||
sample_out_p[1] = right;
|
||||
|
||||
sample_in_pos += channels71;
|
||||
}
|
||||
}
|
||||
|
||||
void to_51(samples_t &out, const buf_t &in) override {
|
||||
using namespace speaker;
|
||||
|
||||
auto sample_in_pos = std::begin(in);
|
||||
auto sample_end = std::begin(out) + out.size();
|
||||
|
||||
for(auto sample_out_p = std::begin(out); sample_out_p != sample_end; sample_out_p += channels51) {
|
||||
auto sl = (int)sample_out_p[SIDE_LEFT] * 3 / 10;
|
||||
auto sr = (int)sample_out_p[SIDE_RIGHT] * 3 / 10;
|
||||
|
||||
sample_out_p[FRONT_LEFT] = sample_in_pos[FRONT_LEFT] + sl;
|
||||
sample_out_p[FRONT_RIGHT] = sample_in_pos[FRONT_RIGHT] + sr;
|
||||
sample_out_p[FRONT_CENTER] = sample_in_pos[FRONT_CENTER];
|
||||
sample_out_p[LOW_FREQUENCY] = sample_in_pos[LOW_FREQUENCY];
|
||||
sample_out_p[BACK_LEFT] = sample_in_pos[BACK_LEFT] + sl;
|
||||
sample_out_p[BACK_RIGHT] = sample_in_pos[BACK_RIGHT] + sr;
|
||||
|
||||
sample_in_pos += channels71;
|
||||
}
|
||||
}
|
||||
|
||||
void to_71(samples_t &out, const buf_t &in) override {
|
||||
std::copy_n(std::begin(in), out.size(), std::begin(out));
|
||||
}
|
||||
};
|
||||
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
struct format_t {
|
||||
enum type_e : int {
|
||||
none,
|
||||
mono,
|
||||
stereo,
|
||||
surr51,
|
||||
surr71,
|
||||
} type;
|
||||
|
||||
std::string_view name;
|
||||
int channels;
|
||||
int channel_mask;
|
||||
} formats[] {
|
||||
{
|
||||
format_t::mono,
|
||||
"Mono"sv,
|
||||
1,
|
||||
SPEAKER_FRONT_CENTER,
|
||||
},
|
||||
{
|
||||
format_t::stereo,
|
||||
"Stereo"sv,
|
||||
2,
|
||||
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
|
||||
},
|
||||
{
|
||||
format_t::surr51,
|
||||
"Surround 5.1"sv,
|
||||
6,
|
||||
SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT,
|
||||
},
|
||||
{
|
||||
format_t::surr71,
|
||||
"Surround 7.1"sv,
|
||||
8,
|
||||
SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_BACK_LEFT |
|
||||
SPEAKER_BACK_RIGHT |
|
||||
SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT,
|
||||
},
|
||||
};
|
||||
|
||||
static format_t surround_51_side_speakers {
|
||||
format_t::surr51,
|
||||
"Surround 5.1"sv,
|
||||
6,
|
||||
SPEAKER_FRONT_LEFT |
|
||||
SPEAKER_FRONT_RIGHT |
|
||||
SPEAKER_FRONT_CENTER |
|
||||
SPEAKER_LOW_FREQUENCY |
|
||||
SPEAKER_SIDE_LEFT |
|
||||
SPEAKER_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
|
||||
wave_format->nChannels = format.channels;
|
||||
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
|
||||
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
|
||||
|
||||
if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask;
|
||||
}
|
||||
}
|
||||
|
||||
int init_wave_format(audio::wave_format_t &wave_format, DWORD sample_rate) {
|
||||
wave_format->wBitsPerSample = 16;
|
||||
wave_format->nSamplesPerSec = sample_rate;
|
||||
switch(wave_format->wFormatTag) {
|
||||
case WAVE_FORMAT_PCM:
|
||||
break;
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
break;
|
||||
case WAVE_FORMAT_EXTENSIBLE: {
|
||||
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
|
||||
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
|
||||
wave_ex->Samples.wValidBitsPerSample = 16;
|
||||
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
break;
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']';
|
||||
}
|
||||
default:
|
||||
BOOST_LOG(error) << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']';
|
||||
return -1;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
audio_client_t make_audio_client(device_t &device, const format_t &format, int sample_rate) {
|
||||
audio_client_t audio_client;
|
||||
auto status = device->Activate(
|
||||
IID_IAudioClient,
|
||||
CLSCTX_ALL,
|
||||
nullptr,
|
||||
(void **)&audio_client);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
wave_format_t wave_format;
|
||||
status = audio_client->GetMixFormat(&wave_format);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(init_wave_format(wave_format, sample_rate)) {
|
||||
return nullptr;
|
||||
}
|
||||
set_wave_format(wave_format, format);
|
||||
|
||||
status = audio_client->Initialize(
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
0, 0,
|
||||
wave_format.get(),
|
||||
nullptr);
|
||||
|
||||
if(status) {
|
||||
BOOST_LOG(debug) << "Couldn't initialize audio client for ["sv << format.name << "]: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return audio_client;
|
||||
}
|
||||
|
||||
const wchar_t *no_null(const wchar_t *str) {
|
||||
return str ? str : L"Unknown";
|
||||
}
|
||||
|
||||
format_t::type_e validate_device(device_t &device, int sample_rate) {
|
||||
for(const auto &format : formats) {
|
||||
// Ensure WaveFromat is compatible
|
||||
auto audio_client = make_audio_client(device, format, sample_rate);
|
||||
|
||||
BOOST_LOG(debug) << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv);
|
||||
|
||||
if(audio_client) {
|
||||
return format.type;
|
||||
}
|
||||
}
|
||||
|
||||
return format_t::none;
|
||||
}
|
||||
|
||||
device_t default_device(device_enum_t &device_enum) {
|
||||
device_t device;
|
||||
HRESULT status;
|
||||
status = device_enum->GetDefaultAudioEndpoint(
|
||||
eRender,
|
||||
eConsole,
|
||||
&device);
|
||||
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create audio Device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
class mic_wasapi_t : public mic_t {
|
||||
public:
|
||||
capture_e sample(std::vector<std::int16_t> &sample_out) override {
|
||||
auto sample_size = sample_out.size() / channels_out * channels_in;
|
||||
while(sample_buf_pos - std::begin(sample_buf) < sample_size) {
|
||||
//FIXME: Use IAudioClient3 instead of IAudioClient, that would allows for adjusting the latency of the audio samples
|
||||
auto capture_result = _fill_buffer();
|
||||
|
||||
if(capture_result != capture_e::ok) {
|
||||
return capture_result;
|
||||
}
|
||||
}
|
||||
|
||||
switch(channels_out) {
|
||||
case 2:
|
||||
pipe->to_stereo(sample_out, sample_buf);
|
||||
break;
|
||||
case 6:
|
||||
pipe->to_51(sample_out, sample_buf);
|
||||
break;
|
||||
case 8:
|
||||
pipe->to_71(sample_out, sample_buf);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "converting to ["sv << channels_out << "] channels is not supported"sv;
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
// The excess samples should be in front of the queue
|
||||
std::move(&sample_buf[sample_size], sample_buf_pos, std::begin(sample_buf));
|
||||
sample_buf_pos -= sample_size;
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
|
||||
int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out) {
|
||||
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
|
||||
if(!audio_event) {
|
||||
BOOST_LOG(error) << "Couldn't create Event handle"sv;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
HRESULT status;
|
||||
|
||||
status = CoCreateInstance(
|
||||
CLSID_MMDeviceEnumerator,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator,
|
||||
(void **)&device_enum);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create Device Enumerator [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto device = default_device(device_enum);
|
||||
if(!device) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(auto &format : formats) {
|
||||
BOOST_LOG(debug) << "Trying audio format ["sv << format.name << ']';
|
||||
audio_client = make_audio_client(device, format, sample_rate);
|
||||
|
||||
if(audio_client) {
|
||||
BOOST_LOG(debug) << "Found audio format ["sv << format.name << ']';
|
||||
channels_in = format.channels;
|
||||
this->channels_out = channels_out;
|
||||
|
||||
switch(channels_in) {
|
||||
case 1:
|
||||
pipe = std::make_unique<mono_t>();
|
||||
break;
|
||||
case 2:
|
||||
pipe = std::make_unique<stereo_t>();
|
||||
break;
|
||||
case 6:
|
||||
pipe = std::make_unique<surr51_t>();
|
||||
break;
|
||||
case 8:
|
||||
pipe = std::make_unique<surr71_t>();
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "converting from ["sv << channels_in << "] channels is not supported"sv;
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!audio_client) {
|
||||
BOOST_LOG(error) << "Couldn't find supported format for audio"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
REFERENCE_TIME default_latency;
|
||||
audio_client->GetDevicePeriod(&default_latency, nullptr);
|
||||
default_latency_ms = default_latency / 1000;
|
||||
|
||||
std::uint32_t frames;
|
||||
status = audio_client->GetBufferSize(&frames);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't acquire the number of audio frames [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// *2 --> needs to fit double
|
||||
sample_buf = util::buffer_t<std::int16_t> { std::max(frames, frame_size) * 2 * channels_in };
|
||||
sample_buf_pos = std::begin(sample_buf);
|
||||
|
||||
status = audio_client->GetService(IID_IAudioCaptureClient, (void **)&audio_capture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't initialize audio capture client [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = audio_client->SetEventHandle(audio_event.get());
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't set event handle [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = audio_client->Start();
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't start recording [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
~mic_wasapi_t() override {
|
||||
if(audio_client) {
|
||||
audio_client->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
capture_e _fill_buffer() {
|
||||
HRESULT status;
|
||||
|
||||
// Total number of samples
|
||||
struct sample_aligned_t {
|
||||
std::uint32_t uninitialized;
|
||||
std::int16_t *samples;
|
||||
} sample_aligned;
|
||||
|
||||
// number of samples / number of channels
|
||||
struct block_aligned_t {
|
||||
std::uint32_t audio_sample_size;
|
||||
} block_aligned;
|
||||
|
||||
status = WaitForSingleObjectEx(audio_event.get(), default_latency_ms, FALSE);
|
||||
switch(status) {
|
||||
case WAIT_OBJECT_0:
|
||||
break;
|
||||
case WAIT_TIMEOUT:
|
||||
return capture_e::timeout;
|
||||
default:
|
||||
BOOST_LOG(error) << "Couldn't wait for audio event: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
std::uint32_t packet_size {};
|
||||
for(
|
||||
status = audio_capture->GetNextPacketSize(&packet_size);
|
||||
SUCCEEDED(status) && packet_size > 0;
|
||||
status = audio_capture->GetNextPacketSize(&packet_size)) {
|
||||
DWORD buffer_flags;
|
||||
status = audio_capture->GetBuffer(
|
||||
(BYTE **)&sample_aligned.samples,
|
||||
&block_aligned.audio_sample_size,
|
||||
&buffer_flags,
|
||||
nullptr, nullptr);
|
||||
|
||||
switch(status) {
|
||||
case S_OK:
|
||||
break;
|
||||
case AUDCLNT_E_DEVICE_INVALIDATED:
|
||||
return capture_e::reinit;
|
||||
default:
|
||||
BOOST_LOG(error) << "Couldn't capture audio [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
sample_aligned.uninitialized = std::end(sample_buf) - sample_buf_pos;
|
||||
auto n = std::min(sample_aligned.uninitialized, block_aligned.audio_sample_size * channels_in);
|
||||
|
||||
if(buffer_flags & AUDCLNT_BUFFERFLAGS_SILENT) {
|
||||
std::fill_n(sample_buf_pos, n, 0);
|
||||
}
|
||||
else {
|
||||
std::copy_n(sample_aligned.samples, n, sample_buf_pos);
|
||||
}
|
||||
|
||||
sample_buf_pos += n;
|
||||
|
||||
audio_capture->ReleaseBuffer(block_aligned.audio_sample_size);
|
||||
}
|
||||
|
||||
if(status == AUDCLNT_E_DEVICE_INVALIDATED) {
|
||||
return capture_e::reinit;
|
||||
}
|
||||
|
||||
if(FAILED(status)) {
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
public:
|
||||
handle_t audio_event;
|
||||
|
||||
device_enum_t device_enum;
|
||||
device_t device;
|
||||
audio_client_t audio_client;
|
||||
audio_capture_t audio_capture;
|
||||
|
||||
REFERENCE_TIME default_latency_ms;
|
||||
|
||||
util::buffer_t<std::int16_t> sample_buf;
|
||||
std::int16_t *sample_buf_pos;
|
||||
|
||||
// out --> our audio output
|
||||
int channels_out;
|
||||
// in --> our wasapi input
|
||||
int channels_in;
|
||||
|
||||
std::unique_ptr<audio_pipe_t> pipe;
|
||||
};
|
||||
|
||||
class audio_control_t : public ::platf::audio_control_t {
|
||||
public:
|
||||
std::optional<sink_t> sink_info() override {
|
||||
auto virtual_adapter_name = L"Steam Streaming Speakers"sv;
|
||||
|
||||
sink_t sink;
|
||||
|
||||
audio::device_enum_t device_enum;
|
||||
auto status = CoCreateInstance(
|
||||
CLSID_MMDeviceEnumerator,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator,
|
||||
(void **)&device_enum);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto device = default_device(device_enum);
|
||||
if(!device) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
audio::wstring_t wstring;
|
||||
device->GetId(&wstring);
|
||||
|
||||
sink.host = converter.to_bytes(wstring.get());
|
||||
|
||||
collection_t collection;
|
||||
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &collection);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
UINT count;
|
||||
collection->GetCount(&count);
|
||||
|
||||
std::string virtual_device_id = config::audio.virtual_sink;
|
||||
for(auto x = 0; x < count; ++x) {
|
||||
audio::device_t device;
|
||||
collection->Item(x, &device);
|
||||
|
||||
auto type = validate_device(device, SAMPLE_RATE);
|
||||
if(type == format_t::none) {
|
||||
continue;
|
||||
}
|
||||
|
||||
audio::wstring_t wstring;
|
||||
device->GetId(&wstring);
|
||||
|
||||
audio::prop_t prop;
|
||||
device->OpenPropertyStore(STGM_READ, &prop);
|
||||
|
||||
prop_var_t adapter_friendly_name;
|
||||
prop_var_t device_friendly_name;
|
||||
prop_var_t device_desc;
|
||||
|
||||
prop->GetValue(PKEY_Device_FriendlyName, &device_friendly_name.prop);
|
||||
prop->GetValue(PKEY_DeviceInterface_FriendlyName, &adapter_friendly_name.prop);
|
||||
prop->GetValue(PKEY_Device_DeviceDesc, &device_desc.prop);
|
||||
|
||||
auto adapter_name = no_null((LPWSTR)adapter_friendly_name.prop.pszVal);
|
||||
BOOST_LOG(verbose)
|
||||
<< L"===== Device ====="sv << std::endl
|
||||
<< L"Device ID : "sv << wstring.get() << std::endl
|
||||
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
|
||||
<< L"Adapter name : "sv << adapter_name << std::endl
|
||||
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
|
||||
<< std::endl;
|
||||
|
||||
if(virtual_device_id.empty() && adapter_name == virtual_adapter_name) {
|
||||
virtual_device_id = converter.to_bytes(wstring.get());
|
||||
}
|
||||
}
|
||||
|
||||
if(!virtual_device_id.empty()) {
|
||||
sink.null = std::make_optional(sink_t::null_t {
|
||||
"virtual-"s.append(formats[format_t::stereo - 1].name) + virtual_device_id,
|
||||
"virtual-"s.append(formats[format_t::surr51 - 1].name) + virtual_device_id,
|
||||
"virtual-"s.append(formats[format_t::surr71 - 1].name) + virtual_device_id,
|
||||
});
|
||||
}
|
||||
|
||||
return sink;
|
||||
}
|
||||
|
||||
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
|
||||
auto mic = std::make_unique<mic_wasapi_t>();
|
||||
|
||||
if(mic->init(sample_rate, frame_size, channels)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mic;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the requested sink is a virtual sink, meaning no speakers attached to
|
||||
* the host, then we can seamlessly set the format to stereo and surround sound.
|
||||
*
|
||||
* Any virtual sink detected will be prefixed by:
|
||||
* virtual-(format name)
|
||||
* If it doesn't contain that prefix, then the format will not be changed
|
||||
*/
|
||||
std::optional<std::wstring> set_format(const std::string &sink) {
|
||||
std::string_view sv { sink.c_str(), sink.size() };
|
||||
|
||||
format_t::type_e type = format_t::none;
|
||||
// sink format:
|
||||
// [virtual-(format name)]device_id
|
||||
auto prefix = "virtual-"sv;
|
||||
if(sv.find(prefix) == 0) {
|
||||
sv = sv.substr(prefix.size(), sv.size() - prefix.size());
|
||||
|
||||
for(auto &format : formats) {
|
||||
auto &name = format.name;
|
||||
if(sv.find(name) == 0) {
|
||||
type = format.type;
|
||||
sv = sv.substr(name.size(), sv.size() - name.size());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto wstring_device_id = converter.from_bytes(sv.data());
|
||||
|
||||
if(type == format_t::none) {
|
||||
// wstring_device_id does not contain virtual-(format name)
|
||||
// It's a simple deviceId, just pass it back
|
||||
return std::make_optional(std::move(wstring_device_id));
|
||||
}
|
||||
|
||||
wave_format_t wave_format;
|
||||
auto status = policy->GetMixFormat(wstring_device_id.c_str(), &wave_format);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if(init_wave_format(wave_format, SAMPLE_RATE)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
set_wave_format(wave_format, formats[(int)type - 1]);
|
||||
|
||||
WAVEFORMATEXTENSIBLE p {};
|
||||
status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p);
|
||||
|
||||
// Surround 5.1 might contain side-{left, right} instead of speaker in the back
|
||||
// Try again with different speaker mask.
|
||||
if(status == 0x88890008 && type == format_t::surr51) {
|
||||
set_wave_format(wave_format, surround_51_side_speakers);
|
||||
status = policy->SetDeviceFormat(wstring_device_id.c_str(), wave_format.get(), (WAVEFORMATEX *)&p);
|
||||
}
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't set Wave Format [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_optional(std::move(wstring_device_id));
|
||||
}
|
||||
|
||||
int set_sink(const std::string &sink) override {
|
||||
auto wstring_device_id = set_format(sink);
|
||||
if(!wstring_device_id) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int failure {};
|
||||
for(int x = 0; x < (int)ERole_enum_count; ++x) {
|
||||
auto status = policy->SetDefaultEndpoint(wstring_device_id->c_str(), (ERole)x);
|
||||
if(status) {
|
||||
BOOST_LOG(warning) << "Couldn't set ["sv << sink << "] to role ["sv << x << ']';
|
||||
|
||||
++failure;
|
||||
}
|
||||
}
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
int init() {
|
||||
auto status = CoCreateInstance(
|
||||
CLSID_CPolicyConfigClient,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_IPolicyConfig,
|
||||
(void **)&policy);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't create audio policy config: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
~audio_control_t() override {}
|
||||
|
||||
policy_t policy;
|
||||
};
|
||||
} // namespace platf::audio
|
||||
|
||||
namespace platf {
|
||||
|
||||
// It's not big enough to justify it's own source file :/
|
||||
namespace dxgi {
|
||||
int init();
|
||||
}
|
||||
|
||||
std::unique_ptr<audio_control_t> audio_control() {
|
||||
auto control = std::make_unique<audio::audio_control_t>();
|
||||
|
||||
if(control->init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
if(dxgi::init()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<platf::audio::co_init_t>();
|
||||
}
|
||||
} // namespace platf
|
||||
177
src/platform/windows/display.h
Normal file
177
src/platform/windows/display.h
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// Created by loki on 4/23/20.
|
||||
//
|
||||
|
||||
#ifndef SUNSHINE_DISPLAY_H
|
||||
#define SUNSHINE_DISPLAY_H
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <d3dcommon.h>
|
||||
#include <dwmapi.h>
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_2.h>
|
||||
|
||||
#include "src/platform/common.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
namespace platf::dxgi {
|
||||
extern const char *format_str[];
|
||||
|
||||
template<class T>
|
||||
void Release(T *dxgi) {
|
||||
dxgi->Release();
|
||||
}
|
||||
|
||||
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
|
||||
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
|
||||
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
|
||||
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
|
||||
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
|
||||
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
|
||||
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
|
||||
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
|
||||
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
|
||||
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
|
||||
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
|
||||
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
|
||||
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
|
||||
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
|
||||
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
|
||||
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
|
||||
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
|
||||
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
|
||||
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
|
||||
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
|
||||
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
|
||||
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
|
||||
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
|
||||
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
|
||||
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
|
||||
|
||||
namespace video {
|
||||
using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
|
||||
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
|
||||
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
|
||||
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
|
||||
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
|
||||
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
|
||||
} // namespace video
|
||||
|
||||
class hwdevice_t;
|
||||
struct cursor_t {
|
||||
std::vector<std::uint8_t> img_data;
|
||||
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
|
||||
int x, y;
|
||||
bool visible;
|
||||
};
|
||||
|
||||
class gpu_cursor_t {
|
||||
public:
|
||||
gpu_cursor_t() : cursor_view { 0, 0, 0, 0, 0.0f, 1.0f } {};
|
||||
void set_pos(LONG rel_x, LONG rel_y, bool visible) {
|
||||
cursor_view.TopLeftX = rel_x;
|
||||
cursor_view.TopLeftY = rel_y;
|
||||
|
||||
this->visible = visible;
|
||||
}
|
||||
|
||||
void set_texture(LONG width, LONG height, texture2d_t &&texture) {
|
||||
cursor_view.Width = width;
|
||||
cursor_view.Height = height;
|
||||
|
||||
this->texture = std::move(texture);
|
||||
}
|
||||
|
||||
texture2d_t texture;
|
||||
shader_res_t input_res;
|
||||
|
||||
D3D11_VIEWPORT cursor_view;
|
||||
|
||||
bool visible;
|
||||
};
|
||||
|
||||
class duplication_t {
|
||||
public:
|
||||
dup_t dup;
|
||||
bool has_frame {};
|
||||
bool use_dwmflush {};
|
||||
|
||||
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
|
||||
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
|
||||
capture_e release_frame();
|
||||
|
||||
~duplication_t();
|
||||
};
|
||||
|
||||
class display_base_t : public display_t {
|
||||
public:
|
||||
int init(int framerate, const std::string &display_name);
|
||||
|
||||
std::chrono::nanoseconds delay;
|
||||
|
||||
factory1_t factory;
|
||||
adapter_t adapter;
|
||||
output_t output;
|
||||
device_t device;
|
||||
device_ctx_t device_ctx;
|
||||
duplication_t dup;
|
||||
|
||||
DXGI_FORMAT format;
|
||||
D3D_FEATURE_LEVEL feature_level;
|
||||
|
||||
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
|
||||
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
|
||||
} D3DKMT_SCHEDULINGPRIORITYCLASS;
|
||||
|
||||
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
|
||||
};
|
||||
|
||||
class display_ram_t : public display_base_t {
|
||||
public:
|
||||
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
|
||||
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible);
|
||||
|
||||
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img) override;
|
||||
|
||||
int init(int framerate, const std::string &display_name);
|
||||
|
||||
cursor_t cursor;
|
||||
D3D11_MAPPED_SUBRESOURCE img_info;
|
||||
texture2d_t texture;
|
||||
};
|
||||
|
||||
class display_vram_t : public display_base_t, public std::enable_shared_from_this<display_vram_t> {
|
||||
public:
|
||||
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override;
|
||||
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible);
|
||||
|
||||
std::shared_ptr<img_t> alloc_img() override;
|
||||
int dummy_img(img_t *img_base) override;
|
||||
|
||||
int init(int framerate, const std::string &display_name);
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(pix_fmt_e pix_fmt) override;
|
||||
|
||||
sampler_state_t sampler_linear;
|
||||
|
||||
blend_t blend_enable;
|
||||
blend_t blend_disable;
|
||||
|
||||
ps_t scene_ps;
|
||||
vs_t scene_vs;
|
||||
|
||||
texture2d_t src;
|
||||
gpu_cursor_t cursor;
|
||||
};
|
||||
} // namespace platf::dxgi
|
||||
|
||||
#endif
|
||||
533
src/platform/windows/display_base.cpp
Normal file
533
src/platform/windows/display_base.cpp
Normal file
@@ -0,0 +1,533 @@
|
||||
//
|
||||
// Created by loki on 1/12/20.
|
||||
//
|
||||
|
||||
#include <cmath>
|
||||
#include <codecvt>
|
||||
|
||||
#include "display.h"
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
namespace platf::dxgi {
|
||||
capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) {
|
||||
auto capture_status = release_frame();
|
||||
if(capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
if(use_dwmflush) {
|
||||
DwmFlush();
|
||||
}
|
||||
|
||||
auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p);
|
||||
|
||||
switch(status) {
|
||||
case S_OK:
|
||||
has_frame = true;
|
||||
return capture_e::ok;
|
||||
case DXGI_ERROR_WAIT_TIMEOUT:
|
||||
return capture_e::timeout;
|
||||
case WAIT_ABANDONED:
|
||||
case DXGI_ERROR_ACCESS_LOST:
|
||||
case DXGI_ERROR_ACCESS_DENIED:
|
||||
return capture_e::reinit;
|
||||
default:
|
||||
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
capture_e duplication_t::reset(dup_t::pointer dup_p) {
|
||||
auto capture_status = release_frame();
|
||||
|
||||
dup.reset(dup_p);
|
||||
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
capture_e duplication_t::release_frame() {
|
||||
if(!has_frame) {
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
auto status = dup->ReleaseFrame();
|
||||
switch(status) {
|
||||
case S_OK:
|
||||
has_frame = false;
|
||||
return capture_e::ok;
|
||||
case DXGI_ERROR_WAIT_TIMEOUT:
|
||||
return capture_e::timeout;
|
||||
case WAIT_ABANDONED:
|
||||
case DXGI_ERROR_ACCESS_LOST:
|
||||
case DXGI_ERROR_ACCESS_DENIED:
|
||||
has_frame = false;
|
||||
return capture_e::reinit;
|
||||
default:
|
||||
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
duplication_t::~duplication_t() {
|
||||
release_frame();
|
||||
}
|
||||
|
||||
int display_base_t::init(int framerate, const std::string &display_name) {
|
||||
/* Uncomment when use of IDXGIOutput5 is implemented
|
||||
std::call_once(windows_cpp_once_flag, []() {
|
||||
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||
const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4);
|
||||
|
||||
typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value);
|
||||
|
||||
auto user32 = LoadLibraryA("user32.dll");
|
||||
auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext");
|
||||
if(f) {
|
||||
f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
}
|
||||
|
||||
FreeLibrary(user32);
|
||||
});
|
||||
*/
|
||||
|
||||
// Ensure we can duplicate the current display
|
||||
syncThreadDesktop();
|
||||
|
||||
delay = std::chrono::nanoseconds { 1s } / framerate;
|
||||
|
||||
// Get rectangle of full desktop for absolute mouse coordinates
|
||||
env_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
env_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
|
||||
HRESULT status;
|
||||
|
||||
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
auto adapter_name = converter.from_bytes(config::video.adapter_name);
|
||||
auto output_name = converter.from_bytes(display_name);
|
||||
|
||||
adapter_t::pointer adapter_p;
|
||||
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
|
||||
dxgi::adapter_t adapter_tmp { adapter_p };
|
||||
|
||||
DXGI_ADAPTER_DESC1 adapter_desc;
|
||||
adapter_tmp->GetDesc1(&adapter_desc);
|
||||
|
||||
if(!adapter_name.empty() && adapter_desc.Description != adapter_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dxgi::output_t::pointer output_p;
|
||||
for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
|
||||
dxgi::output_t output_tmp { output_p };
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output_tmp->GetDesc(&desc);
|
||||
|
||||
if(!output_name.empty() && desc.DeviceName != output_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(desc.AttachedToDesktop) {
|
||||
output = std::move(output_tmp);
|
||||
|
||||
offset_x = desc.DesktopCoordinates.left;
|
||||
offset_y = desc.DesktopCoordinates.top;
|
||||
width = desc.DesktopCoordinates.right - offset_x;
|
||||
height = desc.DesktopCoordinates.bottom - offset_y;
|
||||
|
||||
// left and bottom may be negative, yet absolute mouse coordinates start at 0x0
|
||||
// Ensure offset starts at 0x0
|
||||
offset_x -= GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||
offset_y -= GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||
}
|
||||
}
|
||||
|
||||
if(output) {
|
||||
adapter = std::move(adapter_tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!output) {
|
||||
BOOST_LOG(error) << "Failed to locate an output device"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D_FEATURE_LEVEL featureLevels[] {
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
status = adapter->QueryInterface(IID_IDXGIAdapter, (void **)&adapter_p);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = D3D11CreateDevice(
|
||||
adapter_p,
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
nullptr,
|
||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
|
||||
featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL),
|
||||
D3D11_SDK_VERSION,
|
||||
&device,
|
||||
&feature_level,
|
||||
&device_ctx);
|
||||
|
||||
adapter_p->Release();
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DXGI_ADAPTER_DESC adapter_desc;
|
||||
adapter->GetDesc(&adapter_desc);
|
||||
|
||||
auto description = converter.to_bytes(adapter_desc.Description);
|
||||
BOOST_LOG(info)
|
||||
<< std::endl
|
||||
<< "Device Description : " << description << std::endl
|
||||
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
|
||||
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
|
||||
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl
|
||||
<< "Capture size : "sv << width << 'x' << height << std::endl
|
||||
<< "Offset : "sv << offset_x << 'x' << offset_y << std::endl
|
||||
<< "Virtual Desktop : "sv << env_width << 'x' << env_height;
|
||||
|
||||
// Enable DwmFlush() only if the current refresh rate can match the client framerate.
|
||||
auto refresh_rate = framerate;
|
||||
DWM_TIMING_INFO timing_info;
|
||||
timing_info.cbSize = sizeof(timing_info);
|
||||
|
||||
status = DwmGetCompositionTimingInfo(NULL, &timing_info);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to detect active refresh rate.";
|
||||
}
|
||||
else {
|
||||
refresh_rate = std::round((double)timing_info.rateRefresh.uiNumerator / (double)timing_info.rateRefresh.uiDenominator);
|
||||
}
|
||||
|
||||
dup.use_dwmflush = config::video.dwmflush && !(framerate > refresh_rate) ? true : false;
|
||||
|
||||
// Bump up thread priority
|
||||
{
|
||||
const DWORD flags = TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY;
|
||||
TOKEN_PRIVILEGES tp;
|
||||
HANDLE token;
|
||||
LUID val;
|
||||
|
||||
if(OpenProcessToken(GetCurrentProcess(), flags, &token) &&
|
||||
!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) {
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = val;
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if(!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
|
||||
BOOST_LOG(warning) << "Could not set privilege to increase GPU priority";
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(token);
|
||||
|
||||
HMODULE gdi32 = GetModuleHandleA("GDI32");
|
||||
if(gdi32) {
|
||||
PD3DKMTSetProcessSchedulingPriorityClass fn =
|
||||
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
|
||||
if(fn) {
|
||||
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dxgi::dxgi_t dxgi;
|
||||
status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
dxgi->SetGPUThreadPriority(7);
|
||||
}
|
||||
|
||||
// Try to reduce latency
|
||||
{
|
||||
dxgi::dxgi1_t dxgi {};
|
||||
status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = dxgi->SetMaximumFrameLatency(1);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(warning) << "Failed to set maximum frame latency [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
|
||||
//TODO: Use IDXGIOutput5 for improved performance
|
||||
{
|
||||
dxgi::output1_t output1 {};
|
||||
status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We try this twice, in case we still get an error on reinitialization
|
||||
for(int x = 0; x < 2; ++x) {
|
||||
status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup);
|
||||
if(SUCCEEDED(status)) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(200ms);
|
||||
}
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_OUTDUPL_DESC dup_desc;
|
||||
dup.dup->GetDesc(&dup_desc);
|
||||
|
||||
format = dup_desc.ModeDesc.Format;
|
||||
|
||||
BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *format_str[] = {
|
||||
"DXGI_FORMAT_UNKNOWN",
|
||||
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32B32A32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32B32A32_UINT",
|
||||
"DXGI_FORMAT_R32G32B32A32_SINT",
|
||||
"DXGI_FORMAT_R32G32B32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32B32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32B32_UINT",
|
||||
"DXGI_FORMAT_R32G32B32_SINT",
|
||||
"DXGI_FORMAT_R16G16B16A16_TYPELESS",
|
||||
"DXGI_FORMAT_R16G16B16A16_FLOAT",
|
||||
"DXGI_FORMAT_R16G16B16A16_UNORM",
|
||||
"DXGI_FORMAT_R16G16B16A16_UINT",
|
||||
"DXGI_FORMAT_R16G16B16A16_SNORM",
|
||||
"DXGI_FORMAT_R16G16B16A16_SINT",
|
||||
"DXGI_FORMAT_R32G32_TYPELESS",
|
||||
"DXGI_FORMAT_R32G32_FLOAT",
|
||||
"DXGI_FORMAT_R32G32_UINT",
|
||||
"DXGI_FORMAT_R32G32_SINT",
|
||||
"DXGI_FORMAT_R32G8X24_TYPELESS",
|
||||
"DXGI_FORMAT_D32_FLOAT_S8X24_UINT",
|
||||
"DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS",
|
||||
"DXGI_FORMAT_X32_TYPELESS_G8X24_UINT",
|
||||
"DXGI_FORMAT_R10G10B10A2_TYPELESS",
|
||||
"DXGI_FORMAT_R10G10B10A2_UNORM",
|
||||
"DXGI_FORMAT_R10G10B10A2_UINT",
|
||||
"DXGI_FORMAT_R11G11B10_FLOAT",
|
||||
"DXGI_FORMAT_R8G8B8A8_TYPELESS",
|
||||
"DXGI_FORMAT_R8G8B8A8_UNORM",
|
||||
"DXGI_FORMAT_R8G8B8A8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_R8G8B8A8_UINT",
|
||||
"DXGI_FORMAT_R8G8B8A8_SNORM",
|
||||
"DXGI_FORMAT_R8G8B8A8_SINT",
|
||||
"DXGI_FORMAT_R16G16_TYPELESS",
|
||||
"DXGI_FORMAT_R16G16_FLOAT",
|
||||
"DXGI_FORMAT_R16G16_UNORM",
|
||||
"DXGI_FORMAT_R16G16_UINT",
|
||||
"DXGI_FORMAT_R16G16_SNORM",
|
||||
"DXGI_FORMAT_R16G16_SINT",
|
||||
"DXGI_FORMAT_R32_TYPELESS",
|
||||
"DXGI_FORMAT_D32_FLOAT",
|
||||
"DXGI_FORMAT_R32_FLOAT",
|
||||
"DXGI_FORMAT_R32_UINT",
|
||||
"DXGI_FORMAT_R32_SINT",
|
||||
"DXGI_FORMAT_R24G8_TYPELESS",
|
||||
"DXGI_FORMAT_D24_UNORM_S8_UINT",
|
||||
"DXGI_FORMAT_R24_UNORM_X8_TYPELESS",
|
||||
"DXGI_FORMAT_X24_TYPELESS_G8_UINT",
|
||||
"DXGI_FORMAT_R8G8_TYPELESS",
|
||||
"DXGI_FORMAT_R8G8_UNORM",
|
||||
"DXGI_FORMAT_R8G8_UINT",
|
||||
"DXGI_FORMAT_R8G8_SNORM",
|
||||
"DXGI_FORMAT_R8G8_SINT",
|
||||
"DXGI_FORMAT_R16_TYPELESS",
|
||||
"DXGI_FORMAT_R16_FLOAT",
|
||||
"DXGI_FORMAT_D16_UNORM",
|
||||
"DXGI_FORMAT_R16_UNORM",
|
||||
"DXGI_FORMAT_R16_UINT",
|
||||
"DXGI_FORMAT_R16_SNORM",
|
||||
"DXGI_FORMAT_R16_SINT",
|
||||
"DXGI_FORMAT_R8_TYPELESS",
|
||||
"DXGI_FORMAT_R8_UNORM",
|
||||
"DXGI_FORMAT_R8_UINT",
|
||||
"DXGI_FORMAT_R8_SNORM",
|
||||
"DXGI_FORMAT_R8_SINT",
|
||||
"DXGI_FORMAT_A8_UNORM",
|
||||
"DXGI_FORMAT_R1_UNORM",
|
||||
"DXGI_FORMAT_R9G9B9E5_SHAREDEXP",
|
||||
"DXGI_FORMAT_R8G8_B8G8_UNORM",
|
||||
"DXGI_FORMAT_G8R8_G8B8_UNORM",
|
||||
"DXGI_FORMAT_BC1_TYPELESS",
|
||||
"DXGI_FORMAT_BC1_UNORM",
|
||||
"DXGI_FORMAT_BC1_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC2_TYPELESS",
|
||||
"DXGI_FORMAT_BC2_UNORM",
|
||||
"DXGI_FORMAT_BC2_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC3_TYPELESS",
|
||||
"DXGI_FORMAT_BC3_UNORM",
|
||||
"DXGI_FORMAT_BC3_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC4_TYPELESS",
|
||||
"DXGI_FORMAT_BC4_UNORM",
|
||||
"DXGI_FORMAT_BC4_SNORM",
|
||||
"DXGI_FORMAT_BC5_TYPELESS",
|
||||
"DXGI_FORMAT_BC5_UNORM",
|
||||
"DXGI_FORMAT_BC5_SNORM",
|
||||
"DXGI_FORMAT_B5G6R5_UNORM",
|
||||
"DXGI_FORMAT_B5G5R5A1_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8A8_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8X8_UNORM",
|
||||
"DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM",
|
||||
"DXGI_FORMAT_B8G8R8A8_TYPELESS",
|
||||
"DXGI_FORMAT_B8G8R8A8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_B8G8R8X8_TYPELESS",
|
||||
"DXGI_FORMAT_B8G8R8X8_UNORM_SRGB",
|
||||
"DXGI_FORMAT_BC6H_TYPELESS",
|
||||
"DXGI_FORMAT_BC6H_UF16",
|
||||
"DXGI_FORMAT_BC6H_SF16",
|
||||
"DXGI_FORMAT_BC7_TYPELESS",
|
||||
"DXGI_FORMAT_BC7_UNORM",
|
||||
"DXGI_FORMAT_BC7_UNORM_SRGB",
|
||||
"DXGI_FORMAT_AYUV",
|
||||
"DXGI_FORMAT_Y410",
|
||||
"DXGI_FORMAT_Y416",
|
||||
"DXGI_FORMAT_NV12",
|
||||
"DXGI_FORMAT_P010",
|
||||
"DXGI_FORMAT_P016",
|
||||
"DXGI_FORMAT_420_OPAQUE",
|
||||
"DXGI_FORMAT_YUY2",
|
||||
"DXGI_FORMAT_Y210",
|
||||
"DXGI_FORMAT_Y216",
|
||||
"DXGI_FORMAT_NV11",
|
||||
"DXGI_FORMAT_AI44",
|
||||
"DXGI_FORMAT_IA44",
|
||||
"DXGI_FORMAT_P8",
|
||||
"DXGI_FORMAT_A8P8",
|
||||
"DXGI_FORMAT_B4G4R4A4_UNORM",
|
||||
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
|
||||
"DXGI_FORMAT_P208",
|
||||
"DXGI_FORMAT_V208",
|
||||
"DXGI_FORMAT_V408"
|
||||
};
|
||||
|
||||
} // namespace platf::dxgi
|
||||
|
||||
namespace platf {
|
||||
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
if(hwdevice_type == mem_type_e::dxgi) {
|
||||
auto disp = std::make_shared<dxgi::display_vram_t>();
|
||||
|
||||
if(!disp->init(framerate, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
else if(hwdevice_type == mem_type_e::system) {
|
||||
auto disp = std::make_shared<dxgi::display_ram_t>();
|
||||
|
||||
if(!disp->init(framerate, display_name)) {
|
||||
return disp;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> display_names(mem_type_e) {
|
||||
std::vector<std::string> display_names;
|
||||
|
||||
HRESULT status;
|
||||
|
||||
BOOST_LOG(debug) << "Detecting monitors..."sv;
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
dxgi::factory1_t factory;
|
||||
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
dxgi::adapter_t adapter;
|
||||
for(int x = 0; factory->EnumAdapters1(x, &adapter) != DXGI_ERROR_NOT_FOUND; ++x) {
|
||||
DXGI_ADAPTER_DESC1 adapter_desc;
|
||||
adapter->GetDesc1(&adapter_desc);
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< std::endl
|
||||
<< "====== ADAPTER ====="sv << std::endl
|
||||
<< "Device Name : "sv << converter.to_bytes(adapter_desc.Description) << std::endl
|
||||
<< "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl
|
||||
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
|
||||
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
|
||||
<< std::endl
|
||||
<< " ====== OUTPUT ======"sv << std::endl;
|
||||
|
||||
dxgi::output_t::pointer output_p {};
|
||||
for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
|
||||
dxgi::output_t output { output_p };
|
||||
|
||||
DXGI_OUTPUT_DESC desc;
|
||||
output->GetDesc(&desc);
|
||||
|
||||
auto device_name = converter.to_bytes(desc.DeviceName);
|
||||
|
||||
auto width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
|
||||
auto height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< " Output Name : "sv << device_name << std::endl
|
||||
<< " AttachedToDesktop : "sv << (desc.AttachedToDesktop ? "yes"sv : "no"sv) << std::endl
|
||||
<< " Resolution : "sv << width << 'x' << height << std::endl
|
||||
<< std::endl;
|
||||
|
||||
display_names.emplace_back(std::move(device_name));
|
||||
}
|
||||
}
|
||||
|
||||
return display_names;
|
||||
}
|
||||
|
||||
} // namespace platf
|
||||
327
src/platform/windows/display_ram.cpp
Normal file
327
src/platform/windows/display_ram.cpp
Normal file
@@ -0,0 +1,327 @@
|
||||
#include "display.h"
|
||||
#include "src/main.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
namespace platf::dxgi {
|
||||
struct img_t : public ::platf::img_t {
|
||||
~img_t() override {
|
||||
delete[] data;
|
||||
data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
|
||||
int height = cursor.shape_info.Height / 2;
|
||||
int width = cursor.shape_info.Width;
|
||||
int pitch = cursor.shape_info.Pitch;
|
||||
|
||||
// img cursor.{x,y} < 0, skip parts of the cursor.img_data
|
||||
auto cursor_skip_y = -std::min(0, cursor.y);
|
||||
auto cursor_skip_x = -std::min(0, cursor.x);
|
||||
|
||||
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
|
||||
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
|
||||
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
|
||||
|
||||
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
|
||||
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
|
||||
|
||||
if(cursor_height > height || cursor_width > width) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto img_skip_y = std::max(0, cursor.y);
|
||||
auto img_skip_x = std::max(0, cursor.x);
|
||||
|
||||
auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch;
|
||||
|
||||
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
|
||||
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
|
||||
|
||||
auto pixels_per_byte = width / pitch;
|
||||
auto bytes_per_row = delta_width / pixels_per_byte;
|
||||
|
||||
auto img_data = (int *)img.data;
|
||||
for(int i = 0; i < delta_height; ++i) {
|
||||
auto and_mask = &cursor_img_data[i * pitch];
|
||||
auto xor_mask = &cursor_img_data[(i + height) * pitch];
|
||||
|
||||
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
|
||||
|
||||
auto skip_x = cursor_skip_x;
|
||||
for(int x = 0; x < bytes_per_row; ++x) {
|
||||
for(auto bit = 0u; bit < 8; ++bit) {
|
||||
if(skip_x > 0) {
|
||||
--skip_x;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0;
|
||||
int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0;
|
||||
|
||||
*img_pixel_p &= and_;
|
||||
*img_pixel_p ^= xor_;
|
||||
|
||||
++img_pixel_p;
|
||||
}
|
||||
|
||||
++and_mask;
|
||||
++xor_mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
|
||||
auto colors_out = (std::uint8_t *)&cursor_pixel;
|
||||
auto colors_in = (std::uint8_t *)img_pixel_p;
|
||||
|
||||
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
|
||||
auto alpha = colors_out[3];
|
||||
if(alpha == 255) {
|
||||
*img_pixel_p = cursor_pixel;
|
||||
}
|
||||
else {
|
||||
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
|
||||
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
|
||||
}
|
||||
}
|
||||
|
||||
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
|
||||
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
|
||||
auto alpha = ((std::uint8_t *)&cursor_pixel)[3];
|
||||
if(alpha == 0xFF) {
|
||||
*img_pixel_p ^= cursor_pixel;
|
||||
}
|
||||
else {
|
||||
*img_pixel_p = cursor_pixel;
|
||||
}
|
||||
}
|
||||
|
||||
void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
|
||||
int height = cursor.shape_info.Height;
|
||||
int width = cursor.shape_info.Width;
|
||||
int pitch = cursor.shape_info.Pitch;
|
||||
|
||||
// img cursor.y < 0, skip parts of the cursor.img_data
|
||||
auto cursor_skip_y = -std::min(0, cursor.y);
|
||||
auto cursor_skip_x = -std::min(0, cursor.x);
|
||||
|
||||
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
|
||||
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
|
||||
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
|
||||
|
||||
auto img_skip_y = std::max(0, cursor.y);
|
||||
auto img_skip_x = std::max(0, cursor.x);
|
||||
|
||||
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
|
||||
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
|
||||
|
||||
if(cursor_height > height || cursor_width > width) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch];
|
||||
|
||||
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
|
||||
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
|
||||
|
||||
auto img_data = (int *)img.data;
|
||||
|
||||
for(int i = 0; i < delta_height; ++i) {
|
||||
auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x];
|
||||
auto cursor_end = &cursor_begin[delta_width];
|
||||
|
||||
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
|
||||
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
|
||||
if(masked) {
|
||||
apply_color_masked(img_pixel_p, cursor_pixel);
|
||||
}
|
||||
else {
|
||||
apply_color_alpha(img_pixel_p, cursor_pixel);
|
||||
}
|
||||
++img_pixel_p;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void blend_cursor(const cursor_t &cursor, img_t &img) {
|
||||
switch(cursor.shape_info.Type) {
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
blend_cursor_color(cursor, img, false);
|
||||
break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
|
||||
blend_cursor_monochrome(cursor, img);
|
||||
break;
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
|
||||
blend_cursor_color(cursor, img, true);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
|
||||
}
|
||||
}
|
||||
|
||||
capture_e display_ram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while(img) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
while(next_frame > now) {
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
std::this_thread::sleep_for(1ms);
|
||||
continue;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
auto img = (img_t *)img_base;
|
||||
|
||||
HRESULT status;
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
|
||||
resource_t::pointer res_p {};
|
||||
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
|
||||
resource_t res { res_p };
|
||||
|
||||
if(capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
if(frame_info.PointerShapeBufferSize > 0) {
|
||||
auto &img_data = cursor.img_data;
|
||||
|
||||
img_data.resize(frame_info.PointerShapeBufferSize);
|
||||
|
||||
UINT dummy;
|
||||
status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
if(frame_info.LastMouseUpdateTime.QuadPart) {
|
||||
cursor.x = frame_info.PointerPosition.Position.x;
|
||||
cursor.y = frame_info.PointerPosition.Position.y;
|
||||
cursor.visible = frame_info.PointerPosition.Visible;
|
||||
}
|
||||
|
||||
// If frame has been updated
|
||||
if(frame_info.LastPresentTime.QuadPart != 0) {
|
||||
{
|
||||
texture2d_t src {};
|
||||
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
//Copy from GPU to CPU
|
||||
device_ctx->CopyResource(texture.get(), src.get());
|
||||
}
|
||||
|
||||
if(img_info.pData) {
|
||||
device_ctx->Unmap(texture.get(), 0);
|
||||
img_info.pData = nullptr;
|
||||
}
|
||||
|
||||
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
const bool mouse_update =
|
||||
(frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) &&
|
||||
(cursor_visible && cursor.visible);
|
||||
|
||||
const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update;
|
||||
|
||||
if(!update_flag) {
|
||||
return capture_e::timeout;
|
||||
}
|
||||
|
||||
std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);
|
||||
|
||||
if(cursor_visible && cursor.visible) {
|
||||
blend_cursor(cursor, *img);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
|
||||
auto img = std::make_shared<img_t>();
|
||||
|
||||
img->pixel_pitch = 4;
|
||||
img->row_pitch = img_info.RowPitch;
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->data = new std::uint8_t[img->row_pitch * height];
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
int display_ram_t::dummy_img(platf::img_t *img) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int display_ram_t::init(int framerate, const std::string &display_name) {
|
||||
if(display_base_t::init(framerate, display_name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_STAGING;
|
||||
t.Format = format;
|
||||
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, nullptr, &texture);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// map the texture simply to get the pitch and stride
|
||||
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace platf::dxgi
|
||||
888
src/platform/windows/display_vram.cpp
Normal file
888
src/platform/windows/display_vram.cpp
Normal file
@@ -0,0 +1,888 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <codecvt>
|
||||
|
||||
#include <d3dcompiler.h>
|
||||
#include <directxmath.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/hwcontext_d3d11va.h>
|
||||
}
|
||||
|
||||
#include "display.h"
|
||||
#include "src/main.h"
|
||||
#include "src/video.h"
|
||||
|
||||
|
||||
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx"
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
}
|
||||
|
||||
static void free_frame(AVFrame *frame) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
using frame_t = util::safe_ptr<AVFrame, free_frame>;
|
||||
|
||||
namespace platf::dxgi {
|
||||
|
||||
template<class T>
|
||||
buf_t make_buffer(device_t::pointer device, const T &t) {
|
||||
static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment");
|
||||
|
||||
D3D11_BUFFER_DESC buffer_desc {
|
||||
sizeof(T),
|
||||
D3D11_USAGE_IMMUTABLE,
|
||||
D3D11_BIND_CONSTANT_BUFFER
|
||||
};
|
||||
|
||||
D3D11_SUBRESOURCE_DATA init_data {
|
||||
&t
|
||||
};
|
||||
|
||||
buf_t::pointer buf_p;
|
||||
auto status = device->CreateBuffer(&buffer_desc, &init_data, &buf_p);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create buffer: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return buf_t { buf_p };
|
||||
}
|
||||
|
||||
blend_t make_blend(device_t::pointer device, bool enable) {
|
||||
D3D11_BLEND_DESC bdesc {};
|
||||
auto &rt = bdesc.RenderTarget[0];
|
||||
rt.BlendEnable = enable;
|
||||
rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
||||
|
||||
if(enable) {
|
||||
rt.BlendOp = D3D11_BLEND_OP_ADD;
|
||||
rt.BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||
|
||||
rt.SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
||||
rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||||
|
||||
rt.SrcBlendAlpha = D3D11_BLEND_ZERO;
|
||||
rt.DestBlendAlpha = D3D11_BLEND_ZERO;
|
||||
}
|
||||
|
||||
blend_t blend;
|
||||
auto status = device->CreateBlendState(&bdesc, &blend);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create blend state: [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return blend;
|
||||
}
|
||||
|
||||
blob_t convert_UV_vs_hlsl;
|
||||
blob_t convert_UV_ps_hlsl;
|
||||
blob_t scene_vs_hlsl;
|
||||
blob_t convert_Y_ps_hlsl;
|
||||
blob_t scene_ps_hlsl;
|
||||
|
||||
struct img_d3d_t : public platf::img_t {
|
||||
std::shared_ptr<platf::display_t> display;
|
||||
|
||||
shader_res_t input_res;
|
||||
render_target_t scene_rt;
|
||||
|
||||
texture2d_t texture;
|
||||
|
||||
~img_d3d_t() override = default;
|
||||
};
|
||||
|
||||
util::buffer_t<std::uint8_t> make_cursor_image(util::buffer_t<std::uint8_t> &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
|
||||
constexpr std::uint32_t black = 0xFF000000;
|
||||
constexpr std::uint32_t white = 0xFFFFFFFF;
|
||||
constexpr std::uint32_t transparent = 0;
|
||||
|
||||
switch(shape_info.Type) {
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
|
||||
std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) {
|
||||
if(pixel & 0xFF000000) {
|
||||
pixel = transparent;
|
||||
}
|
||||
});
|
||||
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
|
||||
return std::move(img_data);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
shape_info.Height /= 2;
|
||||
|
||||
util::buffer_t<std::uint8_t> cursor_img { shape_info.Width * shape_info.Height * 4 };
|
||||
|
||||
auto bytes = shape_info.Pitch * shape_info.Height;
|
||||
auto pixel_begin = (std::uint32_t *)std::begin(cursor_img);
|
||||
auto pixel_data = pixel_begin;
|
||||
auto and_mask = std::begin(img_data);
|
||||
auto xor_mask = std::begin(img_data) + bytes;
|
||||
|
||||
for(auto x = 0; x < bytes; ++x) {
|
||||
for(auto c = 7; c >= 0; --c) {
|
||||
auto bit = 1 << c;
|
||||
auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0);
|
||||
|
||||
switch(color_type) {
|
||||
case 0: //black
|
||||
*pixel_data = black;
|
||||
break;
|
||||
case 2: //white
|
||||
*pixel_data = white;
|
||||
break;
|
||||
case 1: //transparent
|
||||
{
|
||||
*pixel_data = transparent;
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: //inverse
|
||||
{
|
||||
auto top_p = pixel_data - shape_info.Width;
|
||||
auto left_p = pixel_data - 1;
|
||||
auto right_p = pixel_data + 1;
|
||||
auto bottom_p = pixel_data + shape_info.Width;
|
||||
|
||||
// Get the x coordinate of the pixel
|
||||
auto column = (pixel_data - pixel_begin) % shape_info.Width != 0;
|
||||
|
||||
if(top_p >= pixel_begin && *top_p == transparent) {
|
||||
*top_p = black;
|
||||
}
|
||||
|
||||
if(column != 0 && left_p >= pixel_begin && *left_p == transparent) {
|
||||
*left_p = black;
|
||||
}
|
||||
|
||||
if(bottom_p < (std::uint32_t *)std::end(cursor_img)) {
|
||||
*bottom_p = black;
|
||||
}
|
||||
|
||||
if(column != shape_info.Width - 1) {
|
||||
*right_p = black;
|
||||
}
|
||||
*pixel_data = white;
|
||||
}
|
||||
}
|
||||
|
||||
++pixel_data;
|
||||
}
|
||||
++and_mask;
|
||||
++xor_mask;
|
||||
}
|
||||
|
||||
return cursor_img;
|
||||
}
|
||||
|
||||
blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
|
||||
blob_t::pointer msg_p = nullptr;
|
||||
blob_t::pointer compiled_p;
|
||||
|
||||
DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS;
|
||||
|
||||
#ifndef NDEBUG
|
||||
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
|
||||
#endif
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
auto wFile = converter.from_bytes(file);
|
||||
auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p);
|
||||
|
||||
if(msg_p) {
|
||||
BOOST_LOG(warning) << std::string_view { (const char *)msg_p->GetBufferPointer(), msg_p->GetBufferSize() - 1 };
|
||||
msg_p->Release();
|
||||
}
|
||||
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Couldn't compile ["sv << file << "] [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return blob_t { compiled_p };
|
||||
}
|
||||
|
||||
blob_t compile_pixel_shader(LPCSTR file) {
|
||||
return compile_shader(file, "main_ps", "ps_5_0");
|
||||
}
|
||||
|
||||
blob_t compile_vertex_shader(LPCSTR file) {
|
||||
return compile_shader(file, "main_vs", "vs_5_0");
|
||||
}
|
||||
|
||||
int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format, texture2d_t::pointer tex) {
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc {
|
||||
format,
|
||||
D3D11_SRV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
shader_resource_desc.Texture2D.MipLevels = 1;
|
||||
|
||||
auto status = device->CreateShaderResourceView(tex, &shader_resource_desc, &shader_res);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_RENDER_TARGET_VIEW_DESC render_target_desc {
|
||||
format,
|
||||
D3D11_RTV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
|
||||
status = device->CreateRenderTargetView(tex, &render_target_desc, &render_target);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_rt(device_t::pointer device, shader_res_t &shader_res, render_target_t &render_target, int width, int height, DXGI_FORMAT format) {
|
||||
D3D11_TEXTURE2D_DESC desc {};
|
||||
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.Format = format;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.SampleDesc.Count = 1;
|
||||
|
||||
texture2d_t tex;
|
||||
auto status = device->CreateTexture2D(&desc, nullptr, &tex);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create render target texture for luma [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
return init_rt(device, shader_res, render_target, width, height, format, tex.get());
|
||||
}
|
||||
|
||||
class hwdevice_t : public platf::hwdevice_t {
|
||||
public:
|
||||
int convert(platf::img_t &img_base) override {
|
||||
auto &img = (img_d3d_t &)img_base;
|
||||
|
||||
device_ctx_p->IASetInputLayout(input_layout.get());
|
||||
|
||||
_init_view_port(this->img.width, this->img.height);
|
||||
device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
|
||||
device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0);
|
||||
device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0);
|
||||
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
|
||||
device_ctx_p->Draw(3, 0);
|
||||
|
||||
device_ctx_p->RSSetViewports(1, &outY_view);
|
||||
device_ctx_p->PSSetShaderResources(0, 1, &img.input_res);
|
||||
device_ctx_p->Draw(3, 0);
|
||||
|
||||
// Artifacts start appearing on the rendered image if Sunshine doesn't flush
|
||||
// before rendering on the UV part of the image.
|
||||
device_ctx_p->Flush();
|
||||
|
||||
_init_view_port(this->img.width / 2, this->img.height / 2);
|
||||
device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
|
||||
device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0);
|
||||
device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0);
|
||||
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
|
||||
device_ctx_p->Draw(3, 0);
|
||||
|
||||
device_ctx_p->RSSetViewports(1, &outUV_view);
|
||||
device_ctx_p->PSSetShaderResources(0, 1, &img.input_res);
|
||||
device_ctx_p->Draw(3, 0);
|
||||
device_ctx_p->Flush();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {
|
||||
switch(colorspace) {
|
||||
case 5: // SWS_CS_SMPTE170M
|
||||
color_p = &::video::colors[0];
|
||||
break;
|
||||
case 1: // SWS_CS_ITU709
|
||||
color_p = &::video::colors[2];
|
||||
break;
|
||||
case 9: // SWS_CS_BT2020
|
||||
default:
|
||||
BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv;
|
||||
color_p = &::video::colors[0];
|
||||
};
|
||||
|
||||
if(color_range > 1) {
|
||||
// Full range
|
||||
++color_p;
|
||||
}
|
||||
|
||||
auto color_matrix = make_buffer((device_t::pointer)data, *color_p);
|
||||
if(!color_matrix) {
|
||||
BOOST_LOG(warning) << "Failed to create color matrix"sv;
|
||||
return;
|
||||
}
|
||||
|
||||
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
|
||||
this->color_matrix = std::move(color_matrix);
|
||||
}
|
||||
|
||||
int set_frame(AVFrame *frame) {
|
||||
this->hwframe.reset(frame);
|
||||
this->frame = frame;
|
||||
|
||||
auto device_p = (device_t::pointer)data;
|
||||
|
||||
auto out_width = frame->width;
|
||||
auto out_height = frame->height;
|
||||
|
||||
float in_width = img.display->width;
|
||||
float in_height = img.display->height;
|
||||
|
||||
// // Ensure aspect ratio is maintained
|
||||
auto scalar = std::fminf(out_width / in_width, out_height / in_height);
|
||||
auto out_width_f = in_width * scalar;
|
||||
auto out_height_f = in_height * scalar;
|
||||
|
||||
// result is always positive
|
||||
auto offsetX = (out_width - out_width_f) / 2;
|
||||
auto offsetY = (out_height - out_height_f) / 2;
|
||||
|
||||
outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f };
|
||||
outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f };
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = out_width;
|
||||
t.Height = out_height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
auto status = device_p->CreateTexture2D(&t, nullptr, &img.texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
img.width = out_width;
|
||||
img.height = out_height;
|
||||
img.data = (std::uint8_t *)img.texture.get();
|
||||
img.row_pitch = out_width * 4;
|
||||
img.pixel_pitch = 4;
|
||||
|
||||
float info_in[16 / sizeof(float)] { 1.0f / (float)out_width }; //aligned to 16-byte
|
||||
info_scene = make_buffer(device_p, info_in);
|
||||
|
||||
if(!info_in) {
|
||||
BOOST_LOG(error) << "Failed to create info scene buffer"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc {
|
||||
DXGI_FORMAT_R8_UNORM,
|
||||
D3D11_RTV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
|
||||
status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_Y_rt);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM;
|
||||
|
||||
status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Need to have something refcounted
|
||||
if(!frame->buf[0]) {
|
||||
frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor));
|
||||
}
|
||||
|
||||
auto desc = (AVD3D11FrameDescriptor *)frame->buf[0]->data;
|
||||
desc->texture = (ID3D11Texture2D *)img.data;
|
||||
desc->index = 0;
|
||||
|
||||
frame->data[0] = img.data;
|
||||
frame->data[1] = 0;
|
||||
|
||||
frame->linesize[0] = img.row_pitch;
|
||||
|
||||
frame->height = img.height;
|
||||
frame->width = img.width;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init(
|
||||
std::shared_ptr<platf::display_t> display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p,
|
||||
pix_fmt_e pix_fmt) {
|
||||
|
||||
HRESULT status;
|
||||
|
||||
device_p->AddRef();
|
||||
data = device_p;
|
||||
|
||||
this->device_ctx_p = device_ctx_p;
|
||||
|
||||
format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010);
|
||||
status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device_p->CreatePixelShader(convert_Y_ps_hlsl->GetBufferPointer(), convert_Y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device_p->CreatePixelShader(convert_UV_ps_hlsl->GetBufferPointer(), convert_UV_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device_p->CreateVertexShader(convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device_p->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
color_matrix = make_buffer(device_p, ::video::colors[0]);
|
||||
if(!color_matrix) {
|
||||
BOOST_LOG(error) << "Failed to create color matrix buffer"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_INPUT_ELEMENT_DESC layout_desc {
|
||||
"SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0
|
||||
};
|
||||
|
||||
status = device_p->CreateInputLayout(
|
||||
&layout_desc, 1,
|
||||
convert_UV_vs_hlsl->GetBufferPointer(), convert_UV_vs_hlsl->GetBufferSize(),
|
||||
&input_layout);
|
||||
|
||||
img.display = std::move(display);
|
||||
|
||||
// Color the background black, so that the padding for keeping the aspect ratio
|
||||
// is black
|
||||
if(img.display->dummy_img(&back_img)) {
|
||||
BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
D3D11_SRV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
desc.Texture2D.MipLevels = 1;
|
||||
|
||||
status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx_p->IASetInputLayout(input_layout.get());
|
||||
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
|
||||
device_ctx_p->VSSetConstantBuffers(0, 1, &info_scene);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
~hwdevice_t() override {
|
||||
if(data) {
|
||||
((ID3D11Device *)data)->Release();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void _init_view_port(float x, float y, float width, float height) {
|
||||
D3D11_VIEWPORT view {
|
||||
x, y,
|
||||
width, height,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
|
||||
device_ctx_p->RSSetViewports(1, &view);
|
||||
}
|
||||
|
||||
void _init_view_port(float width, float height) {
|
||||
_init_view_port(0.0f, 0.0f, width, height);
|
||||
}
|
||||
|
||||
public:
|
||||
frame_t hwframe;
|
||||
|
||||
::video::color_t *color_p;
|
||||
|
||||
buf_t info_scene;
|
||||
buf_t color_matrix;
|
||||
|
||||
input_layout_t input_layout;
|
||||
|
||||
render_target_t nv12_Y_rt;
|
||||
render_target_t nv12_UV_rt;
|
||||
|
||||
// The image referenced by hwframe
|
||||
// The resulting image is stored here.
|
||||
img_d3d_t img;
|
||||
|
||||
// Clear nv12 render target to black
|
||||
img_d3d_t back_img;
|
||||
|
||||
vs_t convert_UV_vs;
|
||||
ps_t convert_UV_ps;
|
||||
ps_t convert_Y_ps;
|
||||
ps_t scene_ps;
|
||||
vs_t scene_vs;
|
||||
|
||||
D3D11_VIEWPORT outY_view;
|
||||
D3D11_VIEWPORT outUV_view;
|
||||
|
||||
DXGI_FORMAT format;
|
||||
|
||||
device_ctx_t::pointer device_ctx_p;
|
||||
};
|
||||
|
||||
capture_e display_vram_t::capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<::platf::img_t> img, bool *cursor) {
|
||||
auto next_frame = std::chrono::steady_clock::now();
|
||||
|
||||
while(img) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
while(next_frame > now) {
|
||||
now = std::chrono::steady_clock::now();
|
||||
}
|
||||
next_frame = now + delay;
|
||||
|
||||
auto status = snapshot(img.get(), 1000ms, *cursor);
|
||||
switch(status) {
|
||||
case platf::capture_e::reinit:
|
||||
case platf::capture_e::error:
|
||||
return status;
|
||||
case platf::capture_e::timeout:
|
||||
std::this_thread::sleep_for(1ms);
|
||||
continue;
|
||||
case platf::capture_e::ok:
|
||||
img = snapshot_cb(img);
|
||||
break;
|
||||
default:
|
||||
BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
|
||||
auto img = (img_d3d_t *)img_base;
|
||||
|
||||
HRESULT status;
|
||||
|
||||
DXGI_OUTDUPL_FRAME_INFO frame_info;
|
||||
|
||||
resource_t::pointer res_p {};
|
||||
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
|
||||
resource_t res { res_p };
|
||||
|
||||
if(capture_status != capture_e::ok) {
|
||||
return capture_status;
|
||||
}
|
||||
|
||||
const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0;
|
||||
const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0;
|
||||
const bool update_flag = mouse_update_flag || frame_update_flag;
|
||||
|
||||
if(!update_flag) {
|
||||
return capture_e::timeout;
|
||||
}
|
||||
|
||||
if(frame_info.PointerShapeBufferSize > 0) {
|
||||
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {};
|
||||
|
||||
util::buffer_t<std::uint8_t> img_data { frame_info.PointerShapeBufferSize };
|
||||
|
||||
UINT dummy;
|
||||
status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
auto cursor_img = make_cursor_image(std::move(img_data), shape_info);
|
||||
|
||||
D3D11_SUBRESOURCE_DATA data {
|
||||
std::begin(cursor_img),
|
||||
4 * shape_info.Width,
|
||||
0
|
||||
};
|
||||
|
||||
// Create texture for cursor
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = shape_info.Width;
|
||||
t.Height = cursor_img.size() / data.SysMemPitch;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
texture2d_t texture;
|
||||
auto status = device->CreateTexture2D(&t, &data, &texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create mouse texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
D3D11_SRV_DIMENSION_TEXTURE2D
|
||||
};
|
||||
desc.Texture2D.MipLevels = 1;
|
||||
|
||||
// Free resources before allocating on the next line.
|
||||
cursor.input_res.reset();
|
||||
status = device->CreateShaderResourceView(texture.get(), &desc, &cursor.input_res);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create cursor shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
|
||||
cursor.set_texture(t.Width, t.Height, std::move(texture));
|
||||
}
|
||||
|
||||
if(frame_info.LastMouseUpdateTime.QuadPart) {
|
||||
cursor.set_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible);
|
||||
}
|
||||
|
||||
if(frame_update_flag) {
|
||||
src.reset();
|
||||
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
|
||||
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return capture_e::error;
|
||||
}
|
||||
}
|
||||
|
||||
device_ctx->CopyResource(img->texture.get(), src.get());
|
||||
if(cursor.visible) {
|
||||
D3D11_VIEWPORT view {
|
||||
0.0f, 0.0f,
|
||||
(float)width, (float)height,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
|
||||
device_ctx->VSSetShader(scene_vs.get(), nullptr, 0);
|
||||
device_ctx->PSSetShader(scene_ps.get(), nullptr, 0);
|
||||
device_ctx->RSSetViewports(1, &view);
|
||||
device_ctx->OMSetRenderTargets(1, &img->scene_rt, nullptr);
|
||||
device_ctx->PSSetShaderResources(0, 1, &cursor.input_res);
|
||||
device_ctx->OMSetBlendState(blend_enable.get(), nullptr, 0xFFFFFFFFu);
|
||||
device_ctx->RSSetViewports(1, &cursor.cursor_view);
|
||||
device_ctx->Draw(3, 0);
|
||||
device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
|
||||
}
|
||||
|
||||
return capture_e::ok;
|
||||
}
|
||||
|
||||
int display_vram_t::init(int framerate, const std::string &display_name) {
|
||||
if(display_base_t::init(framerate, display_name)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
D3D11_SAMPLER_DESC sampler_desc {};
|
||||
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
||||
sampler_desc.MinLOD = 0;
|
||||
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
|
||||
|
||||
auto status = device->CreateSamplerState(&sampler_desc, &sampler_linear);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create point sampler state [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = device->CreatePixelShader(scene_ps_hlsl->GetBufferPointer(), scene_ps_hlsl->GetBufferSize(), nullptr, &scene_ps);
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "Failed to create scene pixel shader [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
blend_enable = make_blend(device.get(), true);
|
||||
blend_disable = make_blend(device.get(), false);
|
||||
|
||||
if(!blend_disable || !blend_enable) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
|
||||
device_ctx->PSSetSamplers(0, 1, &sampler_linear);
|
||||
device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
|
||||
auto img = std::make_shared<img_d3d_t>();
|
||||
|
||||
img->pixel_pitch = 4;
|
||||
img->row_pitch = img->pixel_pitch * width;
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->display = shared_from_this();
|
||||
|
||||
auto dummy_data = std::make_unique<std::uint8_t[]>(img->row_pitch * height);
|
||||
D3D11_SUBRESOURCE_DATA data {
|
||||
dummy_data.get(),
|
||||
(UINT)img->row_pitch
|
||||
};
|
||||
std::fill_n(dummy_data.get(), img->row_pitch * height, 0);
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
auto status = device->CreateTexture2D(&t, &data, &img->texture);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(init_rt(device.get(), img->input_res, img->scene_rt, width, height, format, img->texture.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
img->data = (std::uint8_t *)img->texture.get();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
int display_vram_t::dummy_img(platf::img_t *img_base) {
|
||||
auto img = (img_d3d_t *)img_base;
|
||||
|
||||
if(img->texture) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
img->row_pitch = width * 4;
|
||||
auto dummy_data = std::make_unique<int[]>(width * height);
|
||||
D3D11_SUBRESOURCE_DATA data {
|
||||
dummy_data.get(),
|
||||
(UINT)img->row_pitch
|
||||
};
|
||||
std::fill_n(dummy_data.get(), width * height, 0);
|
||||
|
||||
D3D11_TEXTURE2D_DESC t {};
|
||||
t.Width = width;
|
||||
t.Height = height;
|
||||
t.MipLevels = 1;
|
||||
t.ArraySize = 1;
|
||||
t.SampleDesc.Count = 1;
|
||||
t.Usage = D3D11_USAGE_DEFAULT;
|
||||
t.Format = format;
|
||||
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
|
||||
dxgi::texture2d_t tex;
|
||||
auto status = device->CreateTexture2D(&t, &data, &tex);
|
||||
if(FAILED(status)) {
|
||||
BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']';
|
||||
return -1;
|
||||
}
|
||||
|
||||
img->texture = std::move(tex);
|
||||
img->data = (std::uint8_t *)img->texture.get();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(pix_fmt_e pix_fmt) {
|
||||
if(pix_fmt != platf::pix_fmt_e::nv12) {
|
||||
BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << from_pix_fmt(pix_fmt) << ']';
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto hwdevice = std::make_shared<hwdevice_t>();
|
||||
|
||||
auto ret = hwdevice->init(
|
||||
shared_from_this(),
|
||||
device.get(),
|
||||
device_ctx.get(),
|
||||
pix_fmt);
|
||||
|
||||
if(ret) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return hwdevice;
|
||||
}
|
||||
|
||||
int init() {
|
||||
BOOST_LOG(info) << "Compiling shaders..."sv;
|
||||
scene_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/SceneVS.hlsl");
|
||||
if(!scene_vs_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_Y_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertYPS.hlsl");
|
||||
if(!convert_Y_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_UV_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ConvertUVPS.hlsl");
|
||||
if(!convert_UV_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
convert_UV_vs_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/ConvertUVVS.hlsl");
|
||||
if(!convert_UV_vs_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
scene_ps_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/ScenePS.hlsl");
|
||||
if(!scene_ps_hlsl) {
|
||||
return -1;
|
||||
}
|
||||
BOOST_LOG(info) << "Compiled shaders"sv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace platf::dxgi
|
||||
491
src/platform/windows/input.cpp
Normal file
491
src/platform/windows/input.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <ViGEm/Client.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/platform/common.h"
|
||||
|
||||
namespace platf {
|
||||
using namespace std::literals;
|
||||
|
||||
thread_local HDESK _lastKnownInputDesktop = nullptr;
|
||||
|
||||
constexpr touch_port_t target_touch_port {
|
||||
0, 0,
|
||||
65535, 65535
|
||||
};
|
||||
|
||||
using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>;
|
||||
using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>;
|
||||
|
||||
static VIGEM_TARGET_TYPE map(const std::string_view &gp) {
|
||||
if(gp == "x360"sv) {
|
||||
return Xbox360Wired;
|
||||
}
|
||||
|
||||
return DualShock4Wired;
|
||||
}
|
||||
|
||||
void CALLBACK x360_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
std::uint8_t /* led_number */,
|
||||
void *userdata);
|
||||
|
||||
void CALLBACK ds4_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
DS4_LIGHTBAR_COLOR /* led_color */,
|
||||
void *userdata);
|
||||
|
||||
class vigem_t {
|
||||
public:
|
||||
int init() {
|
||||
VIGEM_ERROR status;
|
||||
|
||||
client.reset(vigem_alloc());
|
||||
|
||||
status = vigem_connect(client.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't setup connection to ViGEm for gamepad support ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
gamepads.resize(MAX_GAMEPADS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int alloc_gamepad_interal(int nr, rumble_queue_t &rumble_queue, VIGEM_TARGET_TYPE gp_type) {
|
||||
auto &[rumble, gp] = gamepads[nr];
|
||||
assert(!gp);
|
||||
|
||||
if(gp_type == Xbox360Wired) {
|
||||
gp.reset(vigem_target_x360_alloc());
|
||||
}
|
||||
else {
|
||||
gp.reset(vigem_target_ds4_alloc());
|
||||
}
|
||||
|
||||
auto status = vigem_target_add(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(error) << "Couldn't add Gamepad to ViGEm connection ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
rumble = std::move(rumble_queue);
|
||||
|
||||
if(gp_type == Xbox360Wired) {
|
||||
status = vigem_target_x360_register_notification(client.get(), gp.get(), x360_notify, this);
|
||||
}
|
||||
else {
|
||||
status = vigem_target_ds4_register_notification(client.get(), gp.get(), ds4_notify, this);
|
||||
}
|
||||
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't register notifications for rumble support ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void free_target(int nr) {
|
||||
auto &[_, gp] = gamepads[nr];
|
||||
|
||||
if(gp && vigem_target_is_attached(gp.get())) {
|
||||
auto status = vigem_target_remove(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
}
|
||||
|
||||
gp.reset();
|
||||
}
|
||||
|
||||
void rumble(target_t::pointer target, std::uint8_t smallMotor, std::uint8_t largeMotor) {
|
||||
for(int x = 0; x < gamepads.size(); ++x) {
|
||||
auto &[rumble_queue, gp] = gamepads[x];
|
||||
|
||||
if(gp.get() == target) {
|
||||
rumble_queue->raise(x, ((std::uint16_t)smallMotor) << 8, ((std::uint16_t)largeMotor) << 8);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~vigem_t() {
|
||||
if(client) {
|
||||
for(auto &[_, gp] : gamepads) {
|
||||
if(gp && vigem_target_is_attached(gp.get())) {
|
||||
auto status = vigem_target_remove(client.get(), gp.get());
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vigem_disconnect(client.get());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<rumble_queue_t, target_t>> gamepads;
|
||||
|
||||
client_t client;
|
||||
};
|
||||
|
||||
void CALLBACK x360_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
std::uint8_t /* led_number */,
|
||||
void *userdata) {
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< "largeMotor: "sv << (int)largeMotor << std::endl
|
||||
<< "smallMotor: "sv << (int)smallMotor;
|
||||
|
||||
task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, smallMotor, largeMotor);
|
||||
}
|
||||
|
||||
void CALLBACK ds4_notify(
|
||||
client_t::pointer client,
|
||||
target_t::pointer target,
|
||||
std::uint8_t largeMotor, std::uint8_t smallMotor,
|
||||
DS4_LIGHTBAR_COLOR /* led_color */,
|
||||
void *userdata) {
|
||||
|
||||
BOOST_LOG(debug)
|
||||
<< "largeMotor: "sv << (int)largeMotor << std::endl
|
||||
<< "smallMotor: "sv << (int)smallMotor;
|
||||
|
||||
task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, smallMotor, largeMotor);
|
||||
}
|
||||
|
||||
input_t input() {
|
||||
input_t result { new vigem_t {} };
|
||||
|
||||
auto vigem = (vigem_t *)result.get();
|
||||
if(vigem->init()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void send_input(INPUT &i) {
|
||||
retry:
|
||||
auto send = SendInput(1, &i, sizeof(INPUT));
|
||||
if(send != 1) {
|
||||
auto hDesk = syncThreadDesktop();
|
||||
if(_lastKnownInputDesktop != hDesk) {
|
||||
_lastKnownInputDesktop = hDesk;
|
||||
goto retry;
|
||||
}
|
||||
BOOST_LOG(error) << "Couldn't send input"sv;
|
||||
}
|
||||
}
|
||||
|
||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags =
|
||||
MOUSEEVENTF_MOVE |
|
||||
MOUSEEVENTF_ABSOLUTE |
|
||||
|
||||
// MOUSEEVENTF_VIRTUALDESK maps to the entirety of the desktop rather than the primary desktop
|
||||
MOUSEEVENTF_VIRTUALDESK;
|
||||
|
||||
auto scaled_x = std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
|
||||
auto scaled_y = std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
|
||||
|
||||
mi.dx = scaled_x;
|
||||
mi.dy = scaled_y;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void move_mouse(input_t &input, int deltaX, int deltaY) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags = MOUSEEVENTF_MOVE;
|
||||
mi.dx = deltaX;
|
||||
mi.dy = deltaY;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void button_mouse(input_t &input, int button, bool release) {
|
||||
constexpr auto KEY_STATE_DOWN = (SHORT)0x8000;
|
||||
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
int mouse_button;
|
||||
if(button == 1) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
|
||||
mouse_button = VK_LBUTTON;
|
||||
}
|
||||
else if(button == 2) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
|
||||
mouse_button = VK_MBUTTON;
|
||||
}
|
||||
else if(button == 3) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
|
||||
mouse_button = VK_RBUTTON;
|
||||
}
|
||||
else if(button == 4) {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
|
||||
mi.mouseData = XBUTTON1;
|
||||
mouse_button = VK_XBUTTON1;
|
||||
}
|
||||
else {
|
||||
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
|
||||
mi.mouseData = XBUTTON2;
|
||||
mouse_button = VK_XBUTTON2;
|
||||
}
|
||||
|
||||
auto key_state = GetAsyncKeyState(mouse_button);
|
||||
bool key_state_down = (key_state & KEY_STATE_DOWN) != 0;
|
||||
if(key_state_down != release) {
|
||||
BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void scroll(input_t &input, int distance) {
|
||||
INPUT i {};
|
||||
|
||||
i.type = INPUT_MOUSE;
|
||||
auto &mi = i.mi;
|
||||
|
||||
mi.dwFlags = MOUSEEVENTF_WHEEL;
|
||||
mi.mouseData = distance;
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
void keyboard(input_t &input, uint16_t modcode, bool release) {
|
||||
INPUT i {};
|
||||
i.type = INPUT_KEYBOARD;
|
||||
auto &ki = i.ki;
|
||||
|
||||
// For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
|
||||
if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
|
||||
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
|
||||
ki.dwFlags = KEYEVENTF_SCANCODE;
|
||||
}
|
||||
else {
|
||||
ki.wVk = modcode;
|
||||
}
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags
|
||||
switch(modcode) {
|
||||
case VK_RMENU:
|
||||
case VK_RCONTROL:
|
||||
case VK_INSERT:
|
||||
case VK_DELETE:
|
||||
case VK_HOME:
|
||||
case VK_END:
|
||||
case VK_PRIOR:
|
||||
case VK_NEXT:
|
||||
case VK_UP:
|
||||
case VK_DOWN:
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
case VK_DIVIDE:
|
||||
ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(release) {
|
||||
ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
send_input(i);
|
||||
}
|
||||
|
||||
int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) {
|
||||
if(!input) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ((vigem_t *)input.get())->alloc_gamepad_interal(nr, rumble_queue, map(config::input.gamepad));
|
||||
}
|
||||
|
||||
void free_gamepad(input_t &input, int nr) {
|
||||
if(!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
((vigem_t *)input.get())->free_target(nr);
|
||||
}
|
||||
|
||||
static VIGEM_ERROR x360_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) {
|
||||
auto &xusb = *(PXUSB_REPORT)&gamepad_state;
|
||||
|
||||
return vigem_target_x360_update(client, gp, xusb);
|
||||
}
|
||||
|
||||
static DS4_DPAD_DIRECTIONS ds4_dpad(const gamepad_state_t &gamepad_state) {
|
||||
auto flags = gamepad_state.buttonFlags;
|
||||
if(flags & DPAD_UP) {
|
||||
if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_NORTHEAST;
|
||||
}
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_NORTHWEST;
|
||||
}
|
||||
else {
|
||||
return DS4_BUTTON_DPAD_NORTH;
|
||||
}
|
||||
}
|
||||
|
||||
else if(flags & DPAD_DOWN) {
|
||||
if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_SOUTHEAST;
|
||||
}
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_SOUTHWEST;
|
||||
}
|
||||
else {
|
||||
return DS4_BUTTON_DPAD_SOUTH;
|
||||
}
|
||||
}
|
||||
|
||||
else if(flags & DPAD_RIGHT) {
|
||||
return DS4_BUTTON_DPAD_EAST;
|
||||
}
|
||||
|
||||
else if(flags & DPAD_LEFT) {
|
||||
return DS4_BUTTON_DPAD_WEST;
|
||||
}
|
||||
|
||||
return DS4_BUTTON_DPAD_NONE;
|
||||
}
|
||||
|
||||
static DS4_BUTTONS ds4_buttons(const gamepad_state_t &gamepad_state) {
|
||||
int buttons {};
|
||||
|
||||
auto flags = gamepad_state.buttonFlags;
|
||||
// clang-format off
|
||||
if(flags & LEFT_STICK) buttons |= DS4_BUTTON_THUMB_LEFT;
|
||||
if(flags & RIGHT_STICK) buttons |= DS4_BUTTON_THUMB_RIGHT;
|
||||
if(flags & LEFT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_LEFT;
|
||||
if(flags & RIGHT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_RIGHT;
|
||||
if(flags & START) buttons |= DS4_BUTTON_OPTIONS;
|
||||
if(flags & A) buttons |= DS4_BUTTON_CROSS;
|
||||
if(flags & B) buttons |= DS4_BUTTON_CIRCLE;
|
||||
if(flags & X) buttons |= DS4_BUTTON_SQUARE;
|
||||
if(flags & Y) buttons |= DS4_BUTTON_TRIANGLE;
|
||||
|
||||
if(gamepad_state.lt > 0) buttons |= DS4_BUTTON_TRIGGER_LEFT;
|
||||
if(gamepad_state.rt > 0) buttons |= DS4_BUTTON_TRIGGER_RIGHT;
|
||||
// clang-format on
|
||||
|
||||
return (DS4_BUTTONS)buttons;
|
||||
}
|
||||
|
||||
static DS4_SPECIAL_BUTTONS ds4_special_buttons(const gamepad_state_t &gamepad_state) {
|
||||
int buttons {};
|
||||
|
||||
if(gamepad_state.buttonFlags & BACK) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD;
|
||||
if(gamepad_state.buttonFlags & HOME) buttons |= DS4_SPECIAL_BUTTON_PS;
|
||||
|
||||
return (DS4_SPECIAL_BUTTONS)buttons;
|
||||
}
|
||||
|
||||
static std::uint8_t to_ds4_triggerX(std::int16_t v) {
|
||||
return (v + std::numeric_limits<std::uint16_t>::max() / 2 + 1) / 257;
|
||||
}
|
||||
|
||||
static std::uint8_t to_ds4_triggerY(std::int16_t v) {
|
||||
auto new_v = -((std::numeric_limits<std::uint16_t>::max() / 2 + v - 1)) / 257;
|
||||
|
||||
return new_v == 0 ? 0xFF : (std::uint8_t)new_v;
|
||||
}
|
||||
|
||||
static VIGEM_ERROR ds4_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) {
|
||||
DS4_REPORT report;
|
||||
|
||||
DS4_REPORT_INIT(&report);
|
||||
DS4_SET_DPAD(&report, ds4_dpad(gamepad_state));
|
||||
report.wButtons |= ds4_buttons(gamepad_state);
|
||||
report.bSpecial = ds4_special_buttons(gamepad_state);
|
||||
|
||||
report.bTriggerL = gamepad_state.lt;
|
||||
report.bTriggerR = gamepad_state.rt;
|
||||
|
||||
report.bThumbLX = to_ds4_triggerX(gamepad_state.lsX);
|
||||
report.bThumbLY = to_ds4_triggerY(gamepad_state.lsY);
|
||||
|
||||
report.bThumbRX = to_ds4_triggerX(gamepad_state.rsX);
|
||||
report.bThumbRY = to_ds4_triggerY(gamepad_state.rsY);
|
||||
|
||||
return vigem_target_ds4_update(client, gp, report);
|
||||
}
|
||||
|
||||
|
||||
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
|
||||
// If there is no gamepad support
|
||||
if(!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto vigem = (vigem_t *)input.get();
|
||||
|
||||
auto &[_, gp] = vigem->gamepads[nr];
|
||||
|
||||
VIGEM_ERROR status;
|
||||
|
||||
if(vigem_target_get_type(gp.get()) == Xbox360Wired) {
|
||||
status = x360_update(vigem->client.get(), gp.get(), gamepad_state);
|
||||
}
|
||||
else {
|
||||
status = ds4_update(vigem->client.get(), gp.get(), gamepad_state);
|
||||
}
|
||||
|
||||
if(!VIGEM_SUCCESS(status)) {
|
||||
BOOST_LOG(fatal) << "Couldn't send gamepad input to ViGEm ["sv << util::hex(status).to_string_view() << ']';
|
||||
|
||||
log_flush();
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void freeInput(void *p) {
|
||||
auto vigem = (vigem_t *)p;
|
||||
|
||||
delete vigem;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> &supported_gamepads() {
|
||||
// ds4 == ps4
|
||||
static std::vector<std::string_view> gps {
|
||||
"x360"sv, "ds4"sv, "ps4"sv
|
||||
};
|
||||
|
||||
return gps;
|
||||
}
|
||||
} // namespace platf
|
||||
123
src/platform/windows/misc.cpp
Normal file
123
src/platform/windows/misc.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
// prevent clang format from "optimizing" the header include order
|
||||
// clang-format off
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
#include <ws2tcpip.h>
|
||||
// clang-format on
|
||||
|
||||
#include "src/main.h"
|
||||
#include "src/utility.h"
|
||||
|
||||
using namespace std::literals;
|
||||
namespace platf {
|
||||
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||
|
||||
std::filesystem::path appdata() {
|
||||
return L"."sv;
|
||||
}
|
||||
|
||||
std::string from_sockaddr(const sockaddr *const socket_address) {
|
||||
char data[INET6_ADDRSTRLEN];
|
||||
|
||||
auto family = socket_address->sa_family;
|
||||
if(family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
if(family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
}
|
||||
|
||||
return std::string { data };
|
||||
}
|
||||
|
||||
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
|
||||
char data[INET6_ADDRSTRLEN];
|
||||
|
||||
auto family = ip_addr->sa_family;
|
||||
std::uint16_t port;
|
||||
if(family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
|
||||
port = ((sockaddr_in6 *)ip_addr)->sin6_port;
|
||||
}
|
||||
|
||||
if(family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
|
||||
port = ((sockaddr_in *)ip_addr)->sin_port;
|
||||
}
|
||||
|
||||
return { port, std::string { data } };
|
||||
}
|
||||
|
||||
adapteraddrs_t get_adapteraddrs() {
|
||||
adapteraddrs_t info { nullptr };
|
||||
ULONG size = 0;
|
||||
|
||||
while(GetAdaptersAddresses(AF_UNSPEC, 0, nullptr, info.get(), &size) == ERROR_BUFFER_OVERFLOW) {
|
||||
info.reset((PIP_ADAPTER_ADDRESSES)malloc(size));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::string get_mac_address(const std::string_view &address) {
|
||||
adapteraddrs_t info = get_adapteraddrs();
|
||||
for(auto adapter_pos = info.get(); adapter_pos != nullptr; adapter_pos = adapter_pos->Next) {
|
||||
for(auto addr_pos = adapter_pos->FirstUnicastAddress; addr_pos != nullptr; addr_pos = addr_pos->Next) {
|
||||
if(adapter_pos->PhysicalAddressLength != 0 && address == from_sockaddr(addr_pos->Address.lpSockaddr)) {
|
||||
std::stringstream mac_addr;
|
||||
mac_addr << std::hex;
|
||||
for(int i = 0; i < adapter_pos->PhysicalAddressLength; i++) {
|
||||
if(i > 0) {
|
||||
mac_addr << ':';
|
||||
}
|
||||
mac_addr << std::setw(2) << std::setfill('0') << (int)adapter_pos->PhysicalAddress[i];
|
||||
}
|
||||
return mac_addr.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
|
||||
return "00:00:00:00:00:00"s;
|
||||
}
|
||||
|
||||
HDESK syncThreadDesktop() {
|
||||
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
|
||||
if(!hDesk) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to Open Input Desktop [0x"sv << util::hex(err).to_string_view() << ']';
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!SetThreadDesktop(hDesk)) {
|
||||
auto err = GetLastError();
|
||||
BOOST_LOG(error) << "Failed to sync desktop to thread [0x"sv << util::hex(err).to_string_view() << ']';
|
||||
}
|
||||
|
||||
CloseDesktop(hDesk);
|
||||
|
||||
return hDesk;
|
||||
}
|
||||
|
||||
void print_status(const std::string_view &prefix, HRESULT status) {
|
||||
char err_string[1024];
|
||||
|
||||
DWORD bytes = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr,
|
||||
status,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
err_string,
|
||||
sizeof(err_string),
|
||||
nullptr);
|
||||
|
||||
BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes };
|
||||
}
|
||||
} // namespace platf
|
||||
13
src/platform/windows/misc.h
Normal file
13
src/platform/windows/misc.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef SUNSHINE_WINDOWS_MISC_H
|
||||
#define SUNSHINE_WINDOWS_MISC_H
|
||||
|
||||
#include <string_view>
|
||||
#include <windows.h>
|
||||
#include <winnt.h>
|
||||
|
||||
namespace platf {
|
||||
void print_status(const std::string_view &prefix, HRESULT status);
|
||||
HDESK syncThreadDesktop();
|
||||
} // namespace platf
|
||||
|
||||
#endif
|
||||
195
src/platform/windows/publish.cpp
Normal file
195
src/platform/windows/publish.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include <winsock2.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <windns.h>
|
||||
#include <winerror.h>
|
||||
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
|
||||
#include "misc.h"
|
||||
#include "src/config.h"
|
||||
#include "src/main.h"
|
||||
#include "src/network.h"
|
||||
#include "src/nvhttp.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/thread_safe.h"
|
||||
|
||||
#define _FN(x, ret, args) \
|
||||
typedef ret(*x##_fn) args; \
|
||||
static x##_fn x
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#define __SV(quote) L##quote##sv
|
||||
#define SV(quote) __SV(quote)
|
||||
|
||||
extern "C" {
|
||||
#ifndef __MINGW32__
|
||||
constexpr auto DNS_REQUEST_PENDING = 9506L;
|
||||
constexpr auto DNS_QUERY_REQUEST_VERSION1 = 0x1;
|
||||
constexpr auto DNS_QUERY_RESULTS_VERSION1 = 0x1;
|
||||
#endif
|
||||
|
||||
#define SERVICE_DOMAIN "local"
|
||||
|
||||
constexpr auto SERVICE_INSTANCE_NAME = SV(SERVICE_NAME "." SERVICE_TYPE "." SERVICE_DOMAIN);
|
||||
constexpr auto SERVICE_TYPE_DOMAIN = SV(SERVICE_TYPE "." SERVICE_DOMAIN);
|
||||
|
||||
#ifndef __MINGW32__
|
||||
typedef struct _DNS_SERVICE_INSTANCE {
|
||||
LPWSTR pszInstanceName;
|
||||
LPWSTR pszHostName;
|
||||
|
||||
IP4_ADDRESS *ip4Address;
|
||||
IP6_ADDRESS *ip6Address;
|
||||
|
||||
WORD wPort;
|
||||
WORD wPriority;
|
||||
WORD wWeight;
|
||||
|
||||
// Property list
|
||||
DWORD dwPropertyCount;
|
||||
|
||||
PWSTR *keys;
|
||||
PWSTR *values;
|
||||
|
||||
DWORD dwInterfaceIndex;
|
||||
} DNS_SERVICE_INSTANCE, *PDNS_SERVICE_INSTANCE;
|
||||
#endif
|
||||
|
||||
typedef VOID WINAPI DNS_SERVICE_REGISTER_COMPLETE(
|
||||
_In_ DWORD Status,
|
||||
_In_ PVOID pQueryContext,
|
||||
_In_ PDNS_SERVICE_INSTANCE pInstance);
|
||||
|
||||
typedef DNS_SERVICE_REGISTER_COMPLETE *PDNS_SERVICE_REGISTER_COMPLETE;
|
||||
|
||||
#ifndef __MINGW32__
|
||||
typedef struct _DNS_SERVICE_CANCEL {
|
||||
PVOID reserved;
|
||||
} DNS_SERVICE_CANCEL, *PDNS_SERVICE_CANCEL;
|
||||
|
||||
typedef struct _DNS_SERVICE_REGISTER_REQUEST {
|
||||
ULONG Version;
|
||||
ULONG InterfaceIndex;
|
||||
PDNS_SERVICE_INSTANCE pServiceInstance;
|
||||
PDNS_SERVICE_REGISTER_COMPLETE pRegisterCompletionCallback;
|
||||
PVOID pQueryContext;
|
||||
HANDLE hCredentials;
|
||||
BOOL unicastEnabled;
|
||||
} DNS_SERVICE_REGISTER_REQUEST, *PDNS_SERVICE_REGISTER_REQUEST;
|
||||
#endif
|
||||
|
||||
_FN(_DnsServiceFreeInstance, VOID, (_In_ PDNS_SERVICE_INSTANCE pInstance));
|
||||
_FN(_DnsServiceDeRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
|
||||
_FN(_DnsServiceRegister, DWORD, (_In_ PDNS_SERVICE_REGISTER_REQUEST pRequest, _Inout_opt_ PDNS_SERVICE_CANCEL pCancel));
|
||||
} /* extern "C" */
|
||||
|
||||
namespace platf::publish {
|
||||
VOID WINAPI register_cb(DWORD status, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
|
||||
auto alarm = (safe::alarm_t<DNS_STATUS>::element_type *)pQueryContext;
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
if(pInstance) {
|
||||
_DnsServiceFreeInstance(pInstance);
|
||||
}
|
||||
});
|
||||
|
||||
if(status) {
|
||||
print_status("register_cb()"sv, status);
|
||||
alarm->ring(-1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
alarm->ring(0);
|
||||
}
|
||||
|
||||
static int service(bool enable) {
|
||||
auto alarm = safe::make_alarm<DNS_STATUS>();
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||
|
||||
std::wstring name { SERVICE_INSTANCE_NAME.data(), SERVICE_INSTANCE_NAME.size() };
|
||||
std::wstring domain { SERVICE_TYPE_DOMAIN.data(), SERVICE_TYPE_DOMAIN.size() };
|
||||
|
||||
auto host = converter.from_bytes(boost::asio::ip::host_name() + ".local");
|
||||
|
||||
DNS_SERVICE_INSTANCE instance {};
|
||||
instance.pszInstanceName = name.data();
|
||||
instance.wPort = map_port(nvhttp::PORT_HTTP);
|
||||
instance.pszHostName = host.data();
|
||||
|
||||
DNS_SERVICE_REGISTER_REQUEST req {};
|
||||
req.Version = DNS_QUERY_REQUEST_VERSION1;
|
||||
req.pQueryContext = alarm.get();
|
||||
req.pServiceInstance = &instance;
|
||||
req.pRegisterCompletionCallback = register_cb;
|
||||
|
||||
DNS_STATUS status {};
|
||||
|
||||
if(enable) {
|
||||
status = _DnsServiceRegister(&req, nullptr);
|
||||
}
|
||||
else {
|
||||
status = _DnsServiceDeRegister(&req, nullptr);
|
||||
}
|
||||
|
||||
alarm->wait();
|
||||
|
||||
status = *alarm->status();
|
||||
if(status) {
|
||||
BOOST_LOG(error) << "No mDNS service"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
class deinit_t : public ::platf::deinit_t {
|
||||
public:
|
||||
~deinit_t() override {
|
||||
if(service(false)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Unregistered Sunshine Gamestream service"sv;
|
||||
}
|
||||
};
|
||||
|
||||
int load_funcs(HMODULE handle) {
|
||||
auto fg = util::fail_guard([handle]() {
|
||||
FreeLibrary(handle);
|
||||
});
|
||||
|
||||
_DnsServiceFreeInstance = (_DnsServiceFreeInstance_fn)GetProcAddress(handle, "DnsServiceFreeInstance");
|
||||
_DnsServiceDeRegister = (_DnsServiceDeRegister_fn)GetProcAddress(handle, "DnsServiceDeRegister");
|
||||
_DnsServiceRegister = (_DnsServiceRegister_fn)GetProcAddress(handle, "DnsServiceRegister");
|
||||
|
||||
if(!(_DnsServiceFreeInstance && _DnsServiceDeRegister && _DnsServiceRegister)) {
|
||||
BOOST_LOG(error) << "mDNS service not available in dnsapi.dll"sv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fg.disable();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<::platf::deinit_t> start() {
|
||||
HMODULE handle = LoadLibrary("dnsapi.dll");
|
||||
|
||||
if(!handle || load_funcs(handle)) {
|
||||
BOOST_LOG(error) << "Couldn't load dnsapi.dll, You'll need to add PC manually from Moonlight"sv;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(service(true)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Registered Sunshine Gamestream service"sv;
|
||||
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
} // namespace platf::publish
|
||||
1
src/platform/windows/windows.rs.in
Normal file
1
src/platform/windows/windows.rs.in
Normal file
@@ -0,0 +1 @@
|
||||
SuperDuperAmazing ICON DISCARDABLE "@SUNSHINE_ICON_PATH@"
|
||||
Reference in New Issue
Block a user