2019-08-19 23:08:54 +00:00
|
|
|
#include "lib/win/Win32Common.hpp"
|
|
|
|
|
2015-11-03 04:19:41 +00:00
|
|
|
#include <Windowsx.h>
|
2019-08-19 23:08:54 +00:00
|
|
|
|
2016-07-17 21:15:57 +00:00
|
|
|
#include "boo/IApplication.hpp"
|
2015-08-31 03:40:58 +00:00
|
|
|
#include "boo/IGraphicsContext.hpp"
|
2019-08-19 23:08:54 +00:00
|
|
|
#include "boo/IWindow.hpp"
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-19 23:08:54 +00:00
|
|
|
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
2015-11-06 03:20:58 +00:00
|
|
|
#include "boo/graphicsdev/D3D.hpp"
|
2015-11-07 01:43:12 +00:00
|
|
|
#include "boo/graphicsdev/GL.hpp"
|
2015-11-10 20:11:08 +00:00
|
|
|
#include "boo/graphicsdev/glew.h"
|
2015-11-07 01:43:12 +00:00
|
|
|
#include "boo/graphicsdev/wglew.h"
|
|
|
|
|
2016-07-17 21:15:57 +00:00
|
|
|
#if BOO_HAS_VULKAN
|
|
|
|
#include "boo/graphicsdev/Vulkan.hpp"
|
|
|
|
#endif
|
|
|
|
|
2018-01-20 03:02:29 +00:00
|
|
|
#if _WIN32_WINNT_WIN10
|
|
|
|
#include <dxgi1_5.h>
|
|
|
|
#endif
|
|
|
|
|
2019-08-19 23:08:54 +00:00
|
|
|
#include <logvisor/logvisor.hpp>
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static const int ContextAttribs[] = {WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3,
|
|
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
|
|
// WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
|
|
|
|
// WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
|
|
|
|
0, 0};
|
2015-05-06 00:50:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
namespace boo {
|
2016-03-04 23:02:18 +00:00
|
|
|
static logvisor::Module Log("boo::WindowWin32");
|
2018-05-25 06:30:42 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IGraphicsCommandQueue> _NewD3D11CommandQueue(D3D11Context* ctx, D3D11Context::Window* windowCtx,
|
|
|
|
IGraphicsContext* parent);
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> _NewD3D11DataFactory(D3D11Context* ctx, IGraphicsContext* parent);
|
|
|
|
std::unique_ptr<IGraphicsCommandQueue> _NewGLCommandQueue(IGraphicsContext* parent, GLContext* glCtx);
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> _NewGLDataFactory(IGraphicsContext* parent, GLContext* glCtx);
|
2016-07-17 21:15:57 +00:00
|
|
|
#if BOO_HAS_VULKAN
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IGraphicsCommandQueue> _NewVulkanCommandQueue(VulkanContext* ctx, VulkanContext::Window* windowCtx,
|
|
|
|
IGraphicsContext* parent);
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> _NewVulkanDataFactory(IGraphicsContext* parent, VulkanContext* ctx);
|
2016-07-17 21:15:57 +00:00
|
|
|
#endif
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
struct GraphicsContextWin32 : IGraphicsContext {
|
|
|
|
EGraphicsAPI m_api;
|
|
|
|
EPixelFormat m_pf;
|
|
|
|
IWindow* m_parentWindow;
|
|
|
|
Boo3DAppContextWin32& m_3dCtx;
|
|
|
|
ComPtr<IDXGIOutput> m_output;
|
|
|
|
GraphicsContextWin32(EGraphicsAPI api, IWindow* parentWindow, Boo3DAppContextWin32& b3dCtx)
|
|
|
|
: m_api(api), m_pf(EPixelFormat::RGBA8), m_parentWindow(parentWindow), m_3dCtx(b3dCtx) {}
|
|
|
|
|
|
|
|
virtual void resized(const SWindowRect& rect) { m_3dCtx.resize(m_parentWindow, rect.size[0], rect.size[1]); }
|
2015-11-07 01:43:12 +00:00
|
|
|
};
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
struct GraphicsContextWin32D3D : GraphicsContextWin32 {
|
|
|
|
ComPtr<IDXGISwapChain1> m_swapChain;
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
std::unique_ptr<IGraphicsDataFactory> m_dataFactory;
|
|
|
|
std::unique_ptr<IGraphicsCommandQueue> m_commandQueue;
|
2015-08-31 03:40:58 +00:00
|
|
|
|
|
|
|
public:
|
2018-12-08 05:17:51 +00:00
|
|
|
IWindowCallback* m_callback;
|
|
|
|
|
|
|
|
GraphicsContextWin32D3D(EGraphicsAPI api, IWindow* parentWindow, HWND hwnd, Boo3DAppContextWin32& b3dCtx)
|
|
|
|
: GraphicsContextWin32(api, parentWindow, b3dCtx) {
|
|
|
|
/* Create Swap Chain */
|
|
|
|
DXGI_SWAP_CHAIN_DESC1 scDesc = {};
|
|
|
|
scDesc.SampleDesc.Count = 1;
|
|
|
|
scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
|
|
scDesc.BufferCount = 2;
|
|
|
|
scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
|
|
|
scDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
|
|
|
|
|
|
|
scDesc.Format = b3dCtx.m_ctx11.m_fbFormat;
|
|
|
|
if (FAILED(b3dCtx.m_ctx11.m_dxFactory->CreateSwapChainForHwnd(b3dCtx.m_ctx11.m_dev.Get(), hwnd, &scDesc, nullptr,
|
|
|
|
nullptr, &m_swapChain)))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to create swap chain"));
|
2018-12-08 05:17:51 +00:00
|
|
|
b3dCtx.m_ctx11.m_dxFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
|
|
|
|
|
|
|
|
auto insIt = b3dCtx.m_ctx11.m_windows.emplace(std::make_pair(parentWindow, D3D11Context::Window()));
|
|
|
|
D3D11Context::Window& w = insIt.first->second;
|
|
|
|
w.setupRTV(m_swapChain, b3dCtx.m_ctx11.m_dev.Get());
|
|
|
|
|
|
|
|
m_dataFactory = _NewD3D11DataFactory(&b3dCtx.m_ctx11, this);
|
|
|
|
m_commandQueue = _NewD3D11CommandQueue(&b3dCtx.m_ctx11, &insIt.first->second, this);
|
|
|
|
m_commandQueue->startRenderer();
|
|
|
|
|
|
|
|
if (FAILED(m_swapChain->GetContainingOutput(&m_output)))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to get DXGI output"));
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~GraphicsContextWin32D3D() override { m_3dCtx.m_ctx11.m_windows.erase(m_parentWindow); }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void _setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EGraphicsAPI getAPI() const override { return m_api; }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EPixelFormat getPixelFormat() const override { return m_pf; }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setPixelFormat(EPixelFormat pf) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (pf > EPixelFormat::RGBAF32_Z24)
|
|
|
|
return;
|
|
|
|
m_pf = pf;
|
|
|
|
}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool initializeContext(void*) override { return true; }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void makeCurrent() override {}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void postInit() override {}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void present() override {}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_commandQueue.get(); }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_dataFactory.get(); }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override { return m_dataFactory.get(); }
|
2015-11-18 06:14:49 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override { return m_dataFactory.get(); }
|
2015-11-07 01:43:12 +00:00
|
|
|
};
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
struct GraphicsContextWin32GL : GraphicsContextWin32 {
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> m_dataFactory;
|
|
|
|
std::unique_ptr<IGraphicsCommandQueue> m_commandQueue;
|
2015-11-07 01:43:12 +00:00
|
|
|
|
|
|
|
public:
|
2018-12-08 05:17:51 +00:00
|
|
|
IWindowCallback* m_callback;
|
|
|
|
|
|
|
|
GraphicsContextWin32GL(EGraphicsAPI api, IWindow* parentWindow, HWND hwnd, Boo3DAppContextWin32& b3dCtx)
|
|
|
|
: GraphicsContextWin32(api, parentWindow, b3dCtx) {
|
|
|
|
|
|
|
|
HMONITOR testMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
ComPtr<IDXGIAdapter1> adapter;
|
|
|
|
ComPtr<IDXGIOutput> foundOut;
|
|
|
|
int i = 0;
|
|
|
|
while (b3dCtx.m_ctxOgl.m_dxFactory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND) {
|
|
|
|
int j = 0;
|
|
|
|
ComPtr<IDXGIOutput> out;
|
|
|
|
while (adapter->EnumOutputs(j, &out) != DXGI_ERROR_NOT_FOUND) {
|
|
|
|
DXGI_OUTPUT_DESC desc;
|
|
|
|
out->GetDesc(&desc);
|
|
|
|
if (desc.Monitor == testMon) {
|
|
|
|
out.As<IDXGIOutput>(&m_output);
|
|
|
|
break;
|
2015-11-07 01:43:12 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
++j;
|
|
|
|
}
|
|
|
|
if (m_output)
|
|
|
|
break;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_output)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to find window's IDXGIOutput"));
|
2018-12-08 05:17:51 +00:00
|
|
|
|
|
|
|
auto insIt = b3dCtx.m_ctxOgl.m_windows.emplace(std::make_pair(parentWindow, OGLContext::Window()));
|
|
|
|
OGLContext::Window& w = insIt.first->second;
|
|
|
|
w.m_hwnd = hwnd;
|
|
|
|
w.m_deviceContext = GetDC(hwnd);
|
|
|
|
if (!w.m_deviceContext)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to create window's device context"));
|
2018-12-08 05:17:51 +00:00
|
|
|
|
|
|
|
if (!m_3dCtx.m_ctxOgl.m_lastContext) {
|
|
|
|
PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR),
|
|
|
|
1,
|
|
|
|
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags
|
|
|
|
PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
|
|
|
|
32, // Colordepth of the framebuffer.
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
24, // Number of bits for the depthbuffer
|
|
|
|
8, // Number of bits for the stencilbuffer
|
|
|
|
0, // Number of Aux buffers in the framebuffer.
|
|
|
|
PFD_MAIN_PLANE,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0};
|
|
|
|
|
|
|
|
int pf = ChoosePixelFormat(w.m_deviceContext, &pfd);
|
|
|
|
SetPixelFormat(w.m_deviceContext, pf, &pfd);
|
2018-01-20 03:02:29 +00:00
|
|
|
|
2018-01-20 05:50:01 +00:00
|
|
|
#if 1
|
2018-12-08 05:17:51 +00:00
|
|
|
HGLRC tmpCtx = wglCreateContext(w.m_deviceContext);
|
|
|
|
wglMakeCurrent(w.m_deviceContext, tmpCtx);
|
|
|
|
if (glewInit() != GLEW_OK)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("glewInit failed"));
|
2018-12-08 05:17:51 +00:00
|
|
|
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
|
|
|
|
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
|
2019-08-16 07:47:57 +00:00
|
|
|
wglMakeCurrent(w.m_deviceContext, nullptr);
|
2018-12-08 05:17:51 +00:00
|
|
|
wglDeleteContext(tmpCtx);
|
|
|
|
|
|
|
|
if (b3dCtx.m_ctxOgl.m_glCtx.m_deepColor) {
|
|
|
|
const int attribs1[] = {
|
|
|
|
WGL_SUPPORT_OPENGL_ARB,
|
|
|
|
TRUE,
|
|
|
|
WGL_DRAW_TO_WINDOW_ARB,
|
|
|
|
TRUE,
|
|
|
|
WGL_PIXEL_TYPE_ARB,
|
|
|
|
WGL_TYPE_RGBA_ARB,
|
|
|
|
WGL_RED_BITS_ARB,
|
|
|
|
10,
|
|
|
|
WGL_GREEN_BITS_ARB,
|
|
|
|
10,
|
|
|
|
WGL_BLUE_BITS_ARB,
|
|
|
|
10,
|
|
|
|
WGL_ALPHA_BITS_ARB,
|
|
|
|
2,
|
|
|
|
WGL_DOUBLE_BUFFER_ARB,
|
|
|
|
TRUE,
|
|
|
|
0, // zero terminates the list
|
|
|
|
};
|
|
|
|
float fattribs[] = {
|
|
|
|
0.0f, // zero terminates the list
|
|
|
|
};
|
|
|
|
|
|
|
|
int pixelFormat;
|
|
|
|
UINT numFormats;
|
|
|
|
|
|
|
|
wglChoosePixelFormatARB(w.m_deviceContext, attribs1, fattribs, 1, &pixelFormat, &numFormats);
|
|
|
|
if (numFormats)
|
|
|
|
SetPixelFormat(w.m_deviceContext, pixelFormat, nullptr);
|
|
|
|
}
|
2018-01-20 03:02:29 +00:00
|
|
|
#endif
|
2015-11-07 01:43:12 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
// w.m_mainContext = wglCreateContext(w.m_deviceContext);
|
|
|
|
w.m_mainContext = wglCreateContextAttribsARB(w.m_deviceContext, 0, ContextAttribs);
|
|
|
|
if (!w.m_mainContext)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to create window's main context"));
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_3dCtx.m_ctxOgl.m_lastContext)
|
|
|
|
if (!wglShareLists(w.m_mainContext, m_3dCtx.m_ctxOgl.m_lastContext))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to share contexts"));
|
2018-12-08 05:17:51 +00:00
|
|
|
m_3dCtx.m_ctxOgl.m_lastContext = w.m_mainContext;
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
m_dataFactory = _NewGLDataFactory(this, &b3dCtx.m_ctxOgl.m_glCtx);
|
|
|
|
m_commandQueue = _NewGLCommandQueue(this, &b3dCtx.m_ctxOgl.m_glCtx);
|
|
|
|
m_commandQueue->startRenderer();
|
|
|
|
}
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~GraphicsContextWin32GL() override { m_3dCtx.m_ctxOgl.m_windows.erase(m_parentWindow); }
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void _setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EGraphicsAPI getAPI() const override { return m_api; }
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EPixelFormat getPixelFormat() const override { return m_pf; }
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setPixelFormat(EPixelFormat pf) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (pf > EPixelFormat::RGBAF32_Z24)
|
|
|
|
return;
|
|
|
|
m_pf = pf;
|
|
|
|
}
|
2018-01-20 05:50:01 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool initializeContext(void*) override { return true; }
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void makeCurrent() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
OGLContext::Window& w = m_3dCtx.m_ctxOgl.m_windows[m_parentWindow];
|
|
|
|
// if (!wglMakeCurrent(w.m_deviceContext, w.m_mainContext))
|
2019-07-20 04:22:36 +00:00
|
|
|
// Log.report(logvisor::Fatal, fmt("unable to make WGL context current"));
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
w.m_renderContext = wglCreateContextAttribsARB(w.m_deviceContext, w.m_mainContext, ContextAttribs);
|
|
|
|
if (!w.m_renderContext)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make new WGL context"));
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!wglMakeCurrent(w.m_deviceContext, w.m_renderContext))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make WGL context current"));
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void postInit() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
// OGLContext::Window& w = m_3dCtx.m_ctxOgl.m_windows[m_parentWindow];
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
// wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)
|
|
|
|
// wglGetProcAddress("wglCreateContextAttribsARB");
|
|
|
|
// w.m_renderContext = wglCreateContextAttribsARB(w.m_deviceContext, w.m_mainContext, ContextAttribs);
|
|
|
|
// if (!w.m_renderContext)
|
2019-07-20 04:22:36 +00:00
|
|
|
// Log.report(logvisor::Fatal, fmt("unable to make new WGL context"));
|
2018-12-08 05:17:51 +00:00
|
|
|
// if (!wglMakeCurrent(w.m_deviceContext, w.m_renderContext))
|
2019-07-20 04:22:36 +00:00
|
|
|
// Log.report(logvisor::Fatal, fmt("unable to make WGL context current"));
|
2015-11-07 01:43:12 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!WGLEW_EXT_swap_control)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("WGL_EXT_swap_control not available"));
|
2018-12-08 05:17:51 +00:00
|
|
|
wglSwapIntervalEXT(1);
|
|
|
|
}
|
2015-11-18 06:14:49 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void present() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
OGLContext::Window& w = m_3dCtx.m_ctxOgl.m_windows[m_parentWindow];
|
|
|
|
SwapBuffers(w.m_deviceContext);
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_commandQueue.get(); }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_dataFactory.get(); }
|
2018-12-08 05:17:51 +00:00
|
|
|
|
|
|
|
/* Creates a new context on current thread!! Call from client loading thread */
|
2019-08-16 07:47:57 +00:00
|
|
|
HGLRC m_mainCtx = nullptr;
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
OGLContext::Window& w = m_3dCtx.m_ctxOgl.m_windows[m_parentWindow];
|
|
|
|
if (!m_mainCtx) {
|
|
|
|
m_mainCtx = wglCreateContextAttribsARB(w.m_deviceContext, w.m_mainContext, ContextAttribs);
|
|
|
|
if (!m_mainCtx)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make main WGL context"));
|
2015-08-31 03:40:58 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!wglMakeCurrent(w.m_deviceContext, m_mainCtx))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make main WGL context current"));
|
2018-12-08 05:17:51 +00:00
|
|
|
return m_dataFactory.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Creates a new context on current thread!! Call from client loading thread */
|
2019-08-16 07:47:57 +00:00
|
|
|
HGLRC m_loadCtx = nullptr;
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
OGLContext::Window& w = m_3dCtx.m_ctxOgl.m_windows[m_parentWindow];
|
|
|
|
if (!m_loadCtx) {
|
|
|
|
m_loadCtx = wglCreateContextAttribsARB(w.m_deviceContext, w.m_mainContext, ContextAttribs);
|
|
|
|
if (!m_loadCtx)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make load WGL context"));
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
if (!wglMakeCurrent(w.m_deviceContext, m_loadCtx))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to make load WGL context current"));
|
2018-12-08 05:17:51 +00:00
|
|
|
return m_dataFactory.get();
|
|
|
|
}
|
2015-08-31 03:40:58 +00:00
|
|
|
};
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2016-07-17 21:15:57 +00:00
|
|
|
#if BOO_HAS_VULKAN
|
2018-12-08 05:17:51 +00:00
|
|
|
struct GraphicsContextWin32Vulkan : GraphicsContextWin32 {
|
|
|
|
HINSTANCE m_appInstance;
|
|
|
|
HWND m_hwnd;
|
|
|
|
VulkanContext* m_ctx;
|
|
|
|
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
|
|
|
VkFormat m_format = VK_FORMAT_UNDEFINED;
|
|
|
|
VkColorSpaceKHR m_colorspace;
|
|
|
|
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> m_dataFactory;
|
|
|
|
std::unique_ptr<IGraphicsCommandQueue> m_commandQueue;
|
|
|
|
|
|
|
|
std::thread m_vsyncThread;
|
|
|
|
bool m_vsyncRunning;
|
|
|
|
|
|
|
|
static void ThrowIfFailed(VkResult res) {
|
2019-08-24 20:54:11 +00:00
|
|
|
if (res == VK_SUCCESS) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.report(logvisor::Fatal, fmt("{}\n"), res);
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
|
|
|
public:
|
2018-12-08 05:17:51 +00:00
|
|
|
IWindowCallback* m_callback;
|
|
|
|
|
|
|
|
GraphicsContextWin32Vulkan(IWindow* parentWindow, HINSTANCE appInstance, HWND hwnd, VulkanContext* ctx,
|
|
|
|
Boo3DAppContextWin32& b3dCtx)
|
|
|
|
: GraphicsContextWin32(EGraphicsAPI::Vulkan, parentWindow, b3dCtx)
|
|
|
|
, m_appInstance(appInstance)
|
|
|
|
, m_hwnd(hwnd)
|
|
|
|
, m_ctx(ctx) {
|
|
|
|
HMONITOR testMon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
ComPtr<IDXGIAdapter1> adapter;
|
|
|
|
ComPtr<IDXGIOutput> foundOut;
|
|
|
|
int i = 0;
|
|
|
|
while (b3dCtx.m_vulkanDxFactory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND) {
|
|
|
|
int j = 0;
|
|
|
|
ComPtr<IDXGIOutput> out;
|
|
|
|
while (adapter->EnumOutputs(j, &out) != DXGI_ERROR_NOT_FOUND) {
|
|
|
|
DXGI_OUTPUT_DESC desc;
|
|
|
|
out->GetDesc(&desc);
|
|
|
|
if (desc.Monitor == testMon) {
|
|
|
|
out.As<IDXGIOutput>(&m_output);
|
|
|
|
break;
|
2016-07-17 21:15:57 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
++j;
|
|
|
|
}
|
|
|
|
if (m_output)
|
|
|
|
break;
|
|
|
|
++i;
|
2016-07-17 21:15:57 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!m_output)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("unable to find window's IDXGIOutput"));
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void destroy() {
|
|
|
|
VulkanContext::Window& m_windowCtx = *m_ctx->m_windows[m_parentWindow];
|
|
|
|
m_windowCtx.m_swapChains[0].destroy(m_ctx->m_dev);
|
|
|
|
m_windowCtx.m_swapChains[1].destroy(m_ctx->m_dev);
|
|
|
|
// vk::DestroySurfaceKHR(m_ctx->m_instance, m_surface, nullptr);
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_vsyncRunning) {
|
|
|
|
m_vsyncRunning = false;
|
|
|
|
if (m_vsyncThread.joinable())
|
|
|
|
m_vsyncThread.join();
|
2016-07-17 21:15:57 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
m_ctx->m_windows.erase(m_parentWindow);
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
~GraphicsContextWin32Vulkan() override { destroy(); }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
VulkanContext::Window* m_windowCtx = nullptr;
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void resized(const SWindowRect& rect) {
|
|
|
|
if (m_windowCtx)
|
|
|
|
m_ctx->resizeSwapChain(*m_windowCtx, m_surface, m_format, m_colorspace, rect);
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void _setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EGraphicsAPI getAPI() const override { return m_api; }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EPixelFormat getPixelFormat() const override { return m_pf; }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setPixelFormat(EPixelFormat pf) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (pf > EPixelFormat::RGBAF32_Z24)
|
|
|
|
return;
|
|
|
|
m_pf = pf;
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool initializeContext(void*) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
m_windowCtx = m_ctx->m_windows.emplace(std::make_pair(m_parentWindow, std::make_unique<VulkanContext::Window>()))
|
|
|
|
.first->second.get();
|
|
|
|
m_windowCtx->m_hwnd = m_hwnd;
|
|
|
|
|
|
|
|
VkWin32SurfaceCreateInfoKHR surfaceInfo = {};
|
|
|
|
surfaceInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
|
|
|
surfaceInfo.hinstance = m_appInstance;
|
|
|
|
surfaceInfo.hwnd = m_hwnd;
|
|
|
|
ThrowIfFailed(vk::CreateWin32SurfaceKHR(m_ctx->m_instance, &surfaceInfo, nullptr, &m_surface));
|
|
|
|
|
|
|
|
/* Iterate over each queue to learn whether it supports presenting */
|
|
|
|
VkBool32* supportsPresent = (VkBool32*)malloc(m_ctx->m_queueCount * sizeof(VkBool32));
|
|
|
|
for (uint32_t i = 0; i < m_ctx->m_queueCount; ++i)
|
|
|
|
vk::GetPhysicalDeviceSurfaceSupportKHR(m_ctx->m_gpus[0], i, m_surface, &supportsPresent[i]);
|
|
|
|
|
|
|
|
/* Search for a graphics queue and a present queue in the array of queue
|
|
|
|
* families, try to find one that supports both */
|
|
|
|
if (m_ctx->m_graphicsQueueFamilyIndex == UINT32_MAX) {
|
|
|
|
/* First window, init device */
|
|
|
|
for (uint32_t i = 0; i < m_ctx->m_queueCount; ++i) {
|
|
|
|
if ((m_ctx->m_queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
|
|
|
|
if (supportsPresent[i] == VK_TRUE) {
|
|
|
|
m_ctx->m_graphicsQueueFamilyIndex = i;
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate error if could not find a queue that supports both a graphics
|
|
|
|
* and present */
|
|
|
|
if (m_ctx->m_graphicsQueueFamilyIndex == UINT32_MAX)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("Could not find a queue that supports both graphics and present"));
|
2018-12-08 05:17:51 +00:00
|
|
|
|
|
|
|
m_ctx->initDevice();
|
|
|
|
} else {
|
|
|
|
/* Subsequent window, verify present */
|
|
|
|
if (supportsPresent[m_ctx->m_graphicsQueueFamilyIndex] == VK_FALSE)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("subsequent surface doesn't support present"));
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
free(supportsPresent);
|
|
|
|
|
|
|
|
if (!vk::GetPhysicalDeviceWin32PresentationSupportKHR(m_ctx->m_gpus[0], m_ctx->m_graphicsQueueFamilyIndex)) {
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("Win32 doesn't support vulkan present"));
|
2018-12-08 05:17:51 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the list of VkFormats that are supported */
|
|
|
|
uint32_t formatCount;
|
|
|
|
ThrowIfFailed(vk::GetPhysicalDeviceSurfaceFormatsKHR(m_ctx->m_gpus[0], m_surface, &formatCount, nullptr));
|
|
|
|
VkSurfaceFormatKHR* surfFormats = (VkSurfaceFormatKHR*)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
|
|
|
|
ThrowIfFailed(vk::GetPhysicalDeviceSurfaceFormatsKHR(m_ctx->m_gpus[0], m_surface, &formatCount, surfFormats));
|
|
|
|
|
|
|
|
/* If the format list includes just one entry of VK_FORMAT_UNDEFINED,
|
|
|
|
* the surface has no preferred format. Otherwise, at least one
|
|
|
|
* supported format will be returned. */
|
|
|
|
if (formatCount >= 1) {
|
|
|
|
if (m_ctx->m_deepColor) {
|
|
|
|
for (int i = 0; i < formatCount; ++i) {
|
|
|
|
if (surfFormats[i].format == VK_FORMAT_R16G16B16A16_UNORM) {
|
|
|
|
m_format = surfFormats[i].format;
|
|
|
|
m_colorspace = surfFormats[i].colorSpace;
|
|
|
|
break;
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
|
|
|
if (m_format == VK_FORMAT_UNDEFINED) {
|
|
|
|
for (int i = 0; i < formatCount; ++i) {
|
|
|
|
if (surfFormats[i].format == VK_FORMAT_B8G8R8A8_UNORM || surfFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
|
|
|
|
m_format = surfFormats[i].format;
|
|
|
|
m_colorspace = surfFormats[i].colorSpace;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("no surface formats available for Vulkan swapchain"));
|
2017-02-16 04:47:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_format == VK_FORMAT_UNDEFINED)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("no UNORM formats available for Vulkan swapchain"));
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
m_ctx->initSwapChain(*m_windowCtx, m_surface, m_format, m_colorspace);
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
m_dataFactory = _NewVulkanDataFactory(this, m_ctx);
|
|
|
|
m_commandQueue = _NewVulkanCommandQueue(m_ctx, m_ctx->m_windows[m_parentWindow].get(), this);
|
|
|
|
m_commandQueue->startRenderer();
|
|
|
|
return true;
|
|
|
|
}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void makeCurrent() override {}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void postInit() override {}
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_commandQueue.get(); }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_dataFactory.get(); }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override { return getDataFactory(); }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override { return getDataFactory(); }
|
2016-07-17 21:15:57 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void present() override {}
|
2016-07-17 21:15:57 +00:00
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static void genFrameDefault(MONITORINFO* screen, int& xOut, int& yOut, int& wOut, int& hOut) {
|
|
|
|
float width = screen->rcMonitor.right * 2.0 / 3.0;
|
|
|
|
float height = screen->rcMonitor.bottom * 2.0 / 3.0;
|
|
|
|
xOut = (screen->rcMonitor.right - width) / 2.0;
|
|
|
|
yOut = (screen->rcMonitor.bottom - height) / 2.0;
|
|
|
|
wOut = width;
|
|
|
|
hOut = height;
|
2015-11-03 04:19:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static uint32_t translateKeysym(WPARAM sym, UINT scancode, ESpecialKey& specialSym, EModifierKey& modifierSym) {
|
|
|
|
specialSym = ESpecialKey::None;
|
|
|
|
modifierSym = EModifierKey::None;
|
|
|
|
if (sym >= VK_F1 && sym <= VK_F12)
|
|
|
|
specialSym = ESpecialKey(uint32_t(ESpecialKey::F1) + sym - VK_F1);
|
|
|
|
else if (sym == VK_ESCAPE)
|
|
|
|
specialSym = ESpecialKey::Esc;
|
|
|
|
else if (sym == VK_RETURN)
|
|
|
|
specialSym = ESpecialKey::Enter;
|
|
|
|
else if (sym == VK_BACK)
|
|
|
|
specialSym = ESpecialKey::Backspace;
|
|
|
|
else if (sym == VK_INSERT)
|
|
|
|
specialSym = ESpecialKey::Insert;
|
|
|
|
else if (sym == VK_DELETE)
|
|
|
|
specialSym = ESpecialKey::Delete;
|
|
|
|
else if (sym == VK_HOME)
|
|
|
|
specialSym = ESpecialKey::Home;
|
|
|
|
else if (sym == VK_END)
|
|
|
|
specialSym = ESpecialKey::End;
|
|
|
|
else if (sym == VK_PRIOR)
|
|
|
|
specialSym = ESpecialKey::PgUp;
|
|
|
|
else if (sym == VK_NEXT)
|
|
|
|
specialSym = ESpecialKey::PgDown;
|
|
|
|
else if (sym == VK_LEFT)
|
|
|
|
specialSym = ESpecialKey::Left;
|
|
|
|
else if (sym == VK_RIGHT)
|
|
|
|
specialSym = ESpecialKey::Right;
|
|
|
|
else if (sym == VK_UP)
|
|
|
|
specialSym = ESpecialKey::Up;
|
|
|
|
else if (sym == VK_DOWN)
|
|
|
|
specialSym = ESpecialKey::Down;
|
|
|
|
else if (sym == VK_SHIFT)
|
|
|
|
modifierSym = EModifierKey::Shift;
|
|
|
|
else if (sym == VK_CONTROL)
|
|
|
|
modifierSym = EModifierKey::Ctrl;
|
|
|
|
else if (sym == VK_MENU)
|
|
|
|
modifierSym = EModifierKey::Alt;
|
|
|
|
else {
|
|
|
|
BYTE kbState[256];
|
|
|
|
GetKeyboardState(kbState);
|
|
|
|
kbState[VK_CONTROL] = 0;
|
|
|
|
WORD ch = 0;
|
|
|
|
ToAscii(sym, scancode, kbState, &ch, 0);
|
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
return 0;
|
2015-11-03 04:19:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static EModifierKey translateModifiers(UINT msg) {
|
|
|
|
EModifierKey retval = EModifierKey::None;
|
|
|
|
if ((GetKeyState(VK_SHIFT) & 0x8000) != 0)
|
|
|
|
retval |= EModifierKey::Shift;
|
|
|
|
if ((GetKeyState(VK_CONTROL) & 0x8000) != 0)
|
|
|
|
retval |= EModifierKey::Ctrl;
|
|
|
|
if ((GetKeyState(VK_MENU) & 0x8000) != 0)
|
|
|
|
retval |= EModifierKey::Alt;
|
|
|
|
if (msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP)
|
|
|
|
retval |= EModifierKey::Alt;
|
|
|
|
return retval;
|
2015-11-03 04:19:41 +00:00
|
|
|
}
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static HGLOBAL MakeANSICRLF(const char* data, size_t sz) {
|
|
|
|
size_t retSz = 1;
|
|
|
|
char lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
char ch = data[i];
|
|
|
|
if (ch == '\n' && lastCh != '\r')
|
|
|
|
retSz += 2;
|
|
|
|
else
|
|
|
|
retSz += 1;
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
HGLOBAL ret = GlobalAlloc(GMEM_MOVEABLE, retSz);
|
|
|
|
char* retData = reinterpret_cast<char*>(GlobalLock(ret));
|
|
|
|
lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
char ch = data[i];
|
|
|
|
if (ch == '\n' && lastCh != '\r') {
|
|
|
|
*retData = '\r';
|
|
|
|
++retData;
|
|
|
|
*retData = '\n';
|
|
|
|
++retData;
|
|
|
|
} else {
|
|
|
|
*retData = ch;
|
|
|
|
++retData;
|
|
|
|
}
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
*retData = '\0';
|
|
|
|
GlobalUnlock(ret);
|
|
|
|
return ret;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static std::unique_ptr<uint8_t[]> MakeANSILF(const char* data, size_t sz, size_t& szOut) {
|
|
|
|
szOut = 0;
|
|
|
|
char lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
char ch = data[i];
|
|
|
|
if (ch == '\n' && lastCh == '\r') {
|
|
|
|
} else
|
|
|
|
szOut += 1;
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> ret(new uint8_t[szOut]);
|
|
|
|
uint8_t* retPtr = ret.get();
|
|
|
|
lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
char ch = data[i];
|
|
|
|
if (ch == '\n' && lastCh == '\r')
|
|
|
|
retPtr[-1] = uint8_t('\n');
|
|
|
|
else {
|
|
|
|
*retPtr = uint8_t(ch);
|
|
|
|
++retPtr;
|
|
|
|
}
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
return ret;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Memory could not be allocated. */
|
|
|
|
#define UTF8PROC_ERROR_NOMEM -1
|
|
|
|
/** The given string is too long to be processed. */
|
|
|
|
#define UTF8PROC_ERROR_OVERFLOW -2
|
|
|
|
/** The given string is not a legal UTF-8 string. */
|
|
|
|
#define UTF8PROC_ERROR_INVALIDUTF8 -3
|
|
|
|
/** The @ref UTF8PROC_REJECTNA flag was set and an unassigned codepoint was found. */
|
|
|
|
#define UTF8PROC_ERROR_NOTASSIGNED -4
|
|
|
|
/** Invalid options have been used. */
|
|
|
|
#define UTF8PROC_ERROR_INVALIDOPTS -5
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
#define UTF8PROC_cont(ch) (((ch)&0xc0) == 0x80)
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static inline int utf8proc_iterate(const uint8_t* str, int strlen, int32_t* dst) {
|
2015-12-25 01:08:46 +00:00
|
|
|
uint32_t uc;
|
2018-12-08 05:17:51 +00:00
|
|
|
const uint8_t* end;
|
2015-12-25 01:08:46 +00:00
|
|
|
|
|
|
|
*dst = -1;
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!strlen)
|
|
|
|
return 0;
|
2015-12-25 01:08:46 +00:00
|
|
|
end = str + ((strlen < 0) ? 4 : strlen);
|
|
|
|
uc = *str++;
|
|
|
|
if (uc < 0x80) {
|
|
|
|
*dst = uc;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
// Must be between 0xc2 and 0xf4 inclusive to be valid
|
2018-12-08 05:17:51 +00:00
|
|
|
if ((uc - 0xc2) > (0xf4 - 0xc2))
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
|
|
|
if (uc < 0xe0) { // 2-byte sequence
|
|
|
|
// Must have valid continuation character
|
|
|
|
if (!UTF8PROC_cont(*str))
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
|
|
|
*dst = ((uc & 0x1f) << 6) | (*str & 0x3f);
|
|
|
|
return 2;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
if (uc < 0xf0) { // 3-byte sequence
|
|
|
|
if ((str + 1 >= end) || !UTF8PROC_cont(*str) || !UTF8PROC_cont(str[1]))
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
|
|
|
// Check for surrogate chars
|
|
|
|
if (uc == 0xed && *str > 0x9f)
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
|
|
|
uc = ((uc & 0xf) << 12) | ((*str & 0x3f) << 6) | (str[1] & 0x3f);
|
|
|
|
if (uc < 0x800)
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
|
|
|
*dst = uc;
|
|
|
|
return 3;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
|
|
|
// 4-byte sequence
|
|
|
|
// Must have 3 valid continuation characters
|
|
|
|
if ((str + 2 >= end) || !UTF8PROC_cont(*str) || !UTF8PROC_cont(str[1]) || !UTF8PROC_cont(str[2]))
|
2018-12-08 05:17:51 +00:00
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
2015-12-25 01:08:46 +00:00
|
|
|
// Make sure in correct range (0x10000 - 0x10ffff)
|
|
|
|
if (uc == 0xf0) {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (*str < 0x90)
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
2015-12-25 01:08:46 +00:00
|
|
|
} else if (uc == 0xf4) {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (*str > 0x8f)
|
|
|
|
return UTF8PROC_ERROR_INVALIDUTF8;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
*dst = ((uc & 7) << 18) | ((*str & 0x3f) << 12) | ((str[1] & 0x3f) << 6) | (str[2] & 0x3f);
|
2015-12-25 01:08:46 +00:00
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static HGLOBAL MakeUnicodeCRLF(const char* data, size_t sz) {
|
|
|
|
size_t retSz = 2;
|
|
|
|
int32_t lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz;) {
|
|
|
|
int32_t ch;
|
|
|
|
int chSz = utf8proc_iterate(reinterpret_cast<const uint8_t*>(data + i), -1, &ch);
|
|
|
|
if (chSz < 0)
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("invalid UTF-8 char"));
|
2018-12-08 05:17:51 +00:00
|
|
|
if (ch <= 0xffff) {
|
|
|
|
if (ch == '\n' && lastCh != '\r')
|
|
|
|
retSz += 4;
|
|
|
|
else
|
|
|
|
retSz += 2;
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
i += chSz;
|
|
|
|
}
|
|
|
|
HGLOBAL ret = GlobalAlloc(GMEM_MOVEABLE, retSz);
|
|
|
|
wchar_t* retData = reinterpret_cast<wchar_t*>(GlobalLock(ret));
|
|
|
|
lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz;) {
|
|
|
|
int32_t ch;
|
|
|
|
int chSz = utf8proc_iterate(reinterpret_cast<const uint8_t*>(data + i), -1, &ch);
|
|
|
|
if (ch <= 0xffff) {
|
|
|
|
if (ch == '\n' && lastCh != '\r') {
|
|
|
|
*retData = L'\r';
|
|
|
|
++retData;
|
|
|
|
*retData = L'\n';
|
|
|
|
++retData;
|
|
|
|
} else {
|
|
|
|
*retData = wchar_t(ch);
|
|
|
|
++retData;
|
|
|
|
}
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
i += chSz;
|
|
|
|
}
|
|
|
|
*retData = L'\0';
|
|
|
|
GlobalUnlock(ret);
|
|
|
|
return ret;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static inline int utf8proc_encode_char(int32_t uc, uint8_t* dst) {
|
2015-12-25 01:08:46 +00:00
|
|
|
if (uc < 0x00) {
|
|
|
|
return 0;
|
|
|
|
} else if (uc < 0x80) {
|
|
|
|
dst[0] = uc;
|
|
|
|
return 1;
|
|
|
|
} else if (uc < 0x800) {
|
|
|
|
dst[0] = 0xC0 + (uc >> 6);
|
|
|
|
dst[1] = 0x80 + (uc & 0x3F);
|
|
|
|
return 2;
|
2018-12-08 05:17:51 +00:00
|
|
|
// Note: we allow encoding 0xd800-0xdfff here, so as not to change
|
|
|
|
// the API, however, these are actually invalid in UTF-8
|
2015-12-25 01:08:46 +00:00
|
|
|
} else if (uc < 0x10000) {
|
|
|
|
dst[0] = 0xE0 + (uc >> 12);
|
|
|
|
dst[1] = 0x80 + ((uc >> 6) & 0x3F);
|
|
|
|
dst[2] = 0x80 + (uc & 0x3F);
|
|
|
|
return 3;
|
|
|
|
} else if (uc < 0x110000) {
|
|
|
|
dst[0] = 0xF0 + (uc >> 18);
|
|
|
|
dst[1] = 0x80 + ((uc >> 12) & 0x3F);
|
|
|
|
dst[2] = 0x80 + ((uc >> 6) & 0x3F);
|
|
|
|
dst[3] = 0x80 + (uc & 0x3F);
|
|
|
|
return 4;
|
2018-12-08 05:17:51 +00:00
|
|
|
} else
|
|
|
|
return 0;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static std::unique_ptr<uint8_t[]> MakeUnicodeLF(const wchar_t* data, size_t sz, size_t& szOut) {
|
|
|
|
szOut = 0;
|
|
|
|
wchar_t lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
wchar_t ch = data[i];
|
|
|
|
if (ch == L'\n' && lastCh == L'\r') {
|
|
|
|
} else {
|
|
|
|
uint8_t dummy[4];
|
|
|
|
szOut += utf8proc_encode_char(ch, dummy);
|
|
|
|
}
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> ret(new uint8_t[szOut]);
|
|
|
|
uint8_t* retPtr = ret.get();
|
|
|
|
lastCh = 0;
|
|
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
|
|
wchar_t ch = data[i];
|
|
|
|
if (ch == L'\n' && lastCh == L'\r')
|
|
|
|
retPtr[-1] = uint8_t('\n');
|
|
|
|
else
|
|
|
|
retPtr += utf8proc_encode_char(ch, retPtr);
|
|
|
|
lastCh = ch;
|
|
|
|
}
|
|
|
|
return ret;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2016-02-24 20:52:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
class WindowWin32 : public IWindow {
|
|
|
|
friend struct GraphicsContextWin32;
|
|
|
|
HWND m_hwnd;
|
|
|
|
HIMC m_imc;
|
|
|
|
std::unique_ptr<GraphicsContextWin32> m_gfxCtx;
|
|
|
|
IWindowCallback* m_callback = nullptr;
|
|
|
|
EMouseCursor m_cursor = EMouseCursor::None;
|
|
|
|
bool m_cursorWait = false;
|
|
|
|
bool m_openGL = false;
|
|
|
|
static HCURSOR GetWin32Cursor(EMouseCursor cur) {
|
|
|
|
switch (cur) {
|
|
|
|
case EMouseCursor::Pointer:
|
|
|
|
return WIN32_CURSORS.m_arrow;
|
|
|
|
case EMouseCursor::HorizontalArrow:
|
|
|
|
return WIN32_CURSORS.m_hResize;
|
|
|
|
case EMouseCursor::VerticalArrow:
|
|
|
|
return WIN32_CURSORS.m_vResize;
|
|
|
|
case EMouseCursor::IBeam:
|
|
|
|
return WIN32_CURSORS.m_ibeam;
|
|
|
|
case EMouseCursor::Crosshairs:
|
|
|
|
return WIN32_CURSORS.m_crosshairs;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return WIN32_CURSORS.m_arrow;
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2015-05-06 00:50:57 +00:00
|
|
|
public:
|
2018-12-08 05:17:51 +00:00
|
|
|
WindowWin32(SystemStringView title, Boo3DAppContextWin32& b3dCtx) {
|
|
|
|
const POINT ptZero = {0, 0};
|
|
|
|
HMONITOR monitor = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
MONITORINFO monInfo = {};
|
|
|
|
monInfo.cbSize = sizeof(MONITORINFO);
|
|
|
|
GetMonitorInfo(monitor, &monInfo);
|
|
|
|
int x, y, w, h;
|
|
|
|
genFrameDefault(&monInfo, x, y, w, h);
|
|
|
|
RECT r = {x, y, x + w, y + h};
|
|
|
|
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
|
|
|
|
|
|
|
m_hwnd = CreateWindowW(L"BooWindow", title.data(), WS_OVERLAPPEDWINDOW, r.left, r.top, r.right - r.left,
|
2019-08-16 07:47:57 +00:00
|
|
|
r.bottom - r.top, nullptr, nullptr, nullptr, nullptr);
|
2018-12-08 05:17:51 +00:00
|
|
|
m_imc = ImmGetContext(m_hwnd);
|
2017-11-25 02:49:20 +00:00
|
|
|
|
|
|
|
#if BOO_HAS_VULKAN
|
2019-09-10 01:48:17 +00:00
|
|
|
HINSTANCE wndInstance = HINSTANCE(GetWindowLongPtr(m_hwnd, GWLP_HINSTANCE));
|
2018-12-08 05:17:51 +00:00
|
|
|
if (b3dCtx.m_vulkanDxFactory) {
|
|
|
|
m_gfxCtx.reset(new GraphicsContextWin32Vulkan(this, wndInstance, m_hwnd, &g_VulkanContext, b3dCtx));
|
|
|
|
if (m_gfxCtx->initializeContext(nullptr))
|
|
|
|
return;
|
|
|
|
}
|
2017-11-25 02:49:20 +00:00
|
|
|
#endif
|
2019-09-10 01:48:17 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
IGraphicsContext::EGraphicsAPI api = IGraphicsContext::EGraphicsAPI::D3D11;
|
|
|
|
if (b3dCtx.m_ctxOgl.m_dxFactory) {
|
|
|
|
m_gfxCtx.reset(new GraphicsContextWin32GL(IGraphicsContext::EGraphicsAPI::OpenGL3_3, this, m_hwnd, b3dCtx));
|
|
|
|
m_openGL = true;
|
|
|
|
return;
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
m_gfxCtx.reset(new GraphicsContextWin32D3D(api, this, m_hwnd, b3dCtx));
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void _cleanup() override { m_gfxCtx.reset(); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void closeWindow() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
// TODO: Perform thread-coalesced deallocation
|
|
|
|
ShowWindow(m_hwnd, SW_HIDE);
|
|
|
|
}
|
2017-07-17 04:01:13 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void showWindow() override { ShowWindow(m_hwnd, SW_SHOW); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void hideWindow() override { ShowWindow(m_hwnd, SW_HIDE); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
SystemString getTitle() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
wchar_t title[256];
|
|
|
|
int c = GetWindowTextW(m_hwnd, title, 256);
|
|
|
|
return SystemString(title, c);
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setTitle(SystemStringView title) override { SetWindowTextW(m_hwnd, title.data()); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
static void _setCursor(HCURSOR cur) { PostThreadMessageW(g_mainThreadId, WM_USER + 2, WPARAM(cur), 0); }
|
2015-12-01 00:33:14 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setCursor(EMouseCursor cursor) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (cursor == m_cursor && !m_cursorWait)
|
|
|
|
return;
|
|
|
|
m_cursor = cursor;
|
|
|
|
_setCursor(GetWin32Cursor(cursor));
|
|
|
|
}
|
2015-12-01 00:33:14 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setWaitCursor(bool wait) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (wait && !m_cursorWait) {
|
|
|
|
_setCursor(WIN32_CURSORS.m_wait);
|
|
|
|
m_cursorWait = true;
|
|
|
|
} else if (!wait && m_cursorWait) {
|
|
|
|
setCursor(m_cursor);
|
|
|
|
m_cursorWait = false;
|
2015-12-01 00:33:14 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-12-01 00:33:14 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
double getWindowRefreshRate() const override {
|
2019-02-12 07:18:35 +00:00
|
|
|
/* TODO: Actually get refresh rate */
|
|
|
|
return 60.0;
|
|
|
|
}
|
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setWindowFrameDefault() override {
|
2018-12-08 05:17:51 +00:00
|
|
|
MONITORINFO monInfo = {};
|
|
|
|
monInfo.cbSize = sizeof(MONITORINFO);
|
|
|
|
HMONITOR mon = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
GetMonitorInfo(mon, &monInfo);
|
|
|
|
int x, y, w, h;
|
|
|
|
genFrameDefault(&monInfo, x, y, w, h);
|
|
|
|
setWindowFrame(x, y, w, h);
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void getWindowFrame(float& xOut, float& yOut, float& wOut, float& hOut) const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
RECT rct;
|
|
|
|
GetClientRect(m_hwnd, &rct);
|
|
|
|
POINT pt;
|
|
|
|
pt.x = rct.left;
|
|
|
|
pt.y = rct.top;
|
|
|
|
MapWindowPoints(m_hwnd, HWND_DESKTOP, &pt, 1);
|
|
|
|
xOut = pt.x;
|
|
|
|
yOut = pt.y;
|
|
|
|
wOut = rct.right;
|
|
|
|
hOut = rct.bottom;
|
|
|
|
}
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void getWindowFrame(int& xOut, int& yOut, int& wOut, int& hOut) const override {
|
2018-12-08 05:17:51 +00:00
|
|
|
RECT rct;
|
|
|
|
GetClientRect(m_hwnd, &rct);
|
|
|
|
POINT pt;
|
|
|
|
pt.x = rct.left;
|
|
|
|
pt.y = rct.top;
|
|
|
|
MapWindowPoints(m_hwnd, HWND_DESKTOP, &pt, 1);
|
|
|
|
xOut = pt.x;
|
|
|
|
yOut = pt.y;
|
|
|
|
wOut = rct.right;
|
|
|
|
hOut = rct.bottom;
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setWindowFrame(float x, float y, float w, float h) override { setWindowFrame(int(x), int(y), int(w), int(h)); }
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setWindowFrame(int x, int y, int w, int h) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
RECT r = {x, y, x + w, y + h};
|
|
|
|
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
|
|
|
MoveWindow(m_hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top, true);
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
float getVirtualPixelFactor() const override {
|
2016-12-11 01:50:26 +00:00
|
|
|
#if _WIN32_WINNT_WINBLUE
|
2018-12-08 05:17:51 +00:00
|
|
|
if (MyGetScaleFactorForMonitor) {
|
|
|
|
DEVICE_SCALE_FACTOR Factor;
|
|
|
|
HMONITOR mon = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
|
|
MyGetScaleFactorForMonitor(mon, &Factor);
|
|
|
|
if (Factor == 0)
|
2016-12-11 01:50:26 +00:00
|
|
|
return 1.f;
|
2018-12-08 05:17:51 +00:00
|
|
|
return Factor / 100.f;
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
#endif
|
|
|
|
return 1.f;
|
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool isFullscreen() const override { return m_gfxCtx->m_3dCtx.isFullscreen(this); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setFullscreen(bool fs) override { m_gfxCtx->m_3dCtx.setFullscreen(this, fs); }
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void _immSetOpenStatus(bool open) {
|
|
|
|
if (GetCurrentThreadId() != g_mainThreadId) {
|
|
|
|
if (!PostThreadMessageW(g_mainThreadId, WM_USER + 3, WPARAM(m_imc), LPARAM(open)))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("PostThreadMessage error"));
|
2018-12-08 05:17:51 +00:00
|
|
|
return;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
ImmSetOpenStatus(m_imc, open);
|
|
|
|
}
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
COMPOSITIONFORM m_cForm = {CFS_POINT};
|
|
|
|
void _immSetCompositionWindow(const int coord[2]) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
m_cForm.ptCurrentPos.x = coord[0];
|
|
|
|
m_cForm.ptCurrentPos.y = h - coord[1];
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (GetCurrentThreadId() != g_mainThreadId) {
|
|
|
|
if (!PostThreadMessageW(g_mainThreadId, WM_USER + 4, WPARAM(m_imc), LPARAM(&m_cForm)))
|
2019-07-20 04:22:36 +00:00
|
|
|
Log.report(logvisor::Fatal, fmt("PostThreadMessage error"));
|
2018-12-08 05:17:51 +00:00
|
|
|
return;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
ImmSetCompositionWindow(m_imc, &m_cForm);
|
|
|
|
}
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void claimKeyboardFocus(const int coord[2]) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
if (!coord) {
|
|
|
|
//_immSetOpenStatus(false);
|
|
|
|
return;
|
2015-12-25 01:08:46 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
_immSetCompositionWindow(coord);
|
|
|
|
//_immSetOpenStatus(true);
|
|
|
|
}
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
switch (type) {
|
|
|
|
case EClipboardType::String: {
|
|
|
|
HGLOBAL gStr = MakeANSICRLF(reinterpret_cast<const char*>(data), sz);
|
|
|
|
OpenClipboard(m_hwnd);
|
|
|
|
EmptyClipboard();
|
|
|
|
SetClipboardData(CF_TEXT, gStr);
|
|
|
|
CloseClipboard();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
case EClipboardType::UTF8String: {
|
|
|
|
HGLOBAL gStr = MakeUnicodeCRLF(reinterpret_cast<const char*>(data), sz);
|
|
|
|
OpenClipboard(m_hwnd);
|
|
|
|
EmptyClipboard();
|
|
|
|
SetClipboardData(CF_UNICODETEXT, gStr);
|
|
|
|
CloseClipboard();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-25 01:08:46 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
std::unique_ptr<uint8_t[]> clipboardPaste(EClipboardType type, size_t& sz) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
switch (type) {
|
|
|
|
case EClipboardType::String: {
|
|
|
|
OpenClipboard(m_hwnd);
|
|
|
|
HGLOBAL gStr = GetClipboardData(CF_TEXT);
|
|
|
|
if (!gStr)
|
|
|
|
break;
|
|
|
|
const char* str = reinterpret_cast<const char*>(GlobalLock(gStr));
|
|
|
|
std::unique_ptr<uint8_t[]> ret = MakeANSILF(str, GlobalSize(gStr), sz);
|
|
|
|
GlobalUnlock(gStr);
|
|
|
|
CloseClipboard();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
case EClipboardType::UTF8String: {
|
|
|
|
OpenClipboard(m_hwnd);
|
|
|
|
HGLOBAL gStr = GetClipboardData(CF_UNICODETEXT);
|
|
|
|
if (!gStr)
|
|
|
|
break;
|
|
|
|
const wchar_t* str = reinterpret_cast<const wchar_t*>(GlobalLock(gStr));
|
|
|
|
std::unique_ptr<uint8_t[]> ret = MakeUnicodeLF(str, GlobalSize(gStr) / 2, sz);
|
|
|
|
GlobalUnlock(gStr);
|
|
|
|
CloseClipboard();
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return std::unique_ptr<uint8_t[]>();
|
|
|
|
}
|
2015-08-31 03:40:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
int waitForRetrace() override {
|
|
|
|
m_gfxCtx->m_output->WaitForVBlank();
|
|
|
|
return 1;
|
|
|
|
}
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
uintptr_t getPlatformHandle() const override { return uintptr_t(m_hwnd); }
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void buttonDown(HWNDEvent& e, EMouseButton button) {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
EModifierKey modifierMask = translateModifiers(e.uMsg);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
m_callback->mouseDown(coord, button, modifierMask);
|
2015-11-03 04:19:41 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void buttonUp(HWNDEvent& e, EMouseButton button) {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
EModifierKey modifierMask = translateModifiers(e.uMsg);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
m_callback->mouseUp(coord, button, modifierMask);
|
2015-11-07 07:49:53 +00:00
|
|
|
}
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
void _trackMouse() {
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
|
|
tme.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
|
|
tme.dwFlags = TME_NONCLIENT | TME_HOVER | TME_LEAVE;
|
|
|
|
tme.dwHoverTime = 500;
|
|
|
|
tme.hwndTrack = m_hwnd;
|
|
|
|
}
|
2018-05-06 22:44:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
bool mouseTracking = false;
|
2019-08-13 00:52:20 +00:00
|
|
|
bool _incomingEvent(void* ev) override {
|
2018-12-08 05:17:51 +00:00
|
|
|
HWNDEvent& e = *static_cast<HWNDEvent*>(ev);
|
|
|
|
switch (e.uMsg) {
|
|
|
|
case WM_CLOSE:
|
|
|
|
if (m_callback)
|
|
|
|
m_callback->destroyed();
|
|
|
|
return true;
|
|
|
|
case WM_SIZE: {
|
|
|
|
SWindowRect rect;
|
|
|
|
getWindowFrame(rect.location[0], rect.location[1], rect.size[0], rect.size[1]);
|
|
|
|
if (!rect.size[0] || !rect.size[1])
|
|
|
|
return false;
|
|
|
|
m_gfxCtx->resized(rect);
|
|
|
|
if (m_callback)
|
|
|
|
m_callback->resized(rect, m_openGL);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MOVING: {
|
|
|
|
SWindowRect rect;
|
|
|
|
getWindowFrame(rect.location[0], rect.location[1], rect.size[0], rect.size[1]);
|
|
|
|
if (!rect.size[0] || !rect.size[1])
|
2018-05-06 22:44:31 +00:00
|
|
|
return false;
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
if (m_callback)
|
|
|
|
m_callback->windowMoved(rect);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_KEYDOWN:
|
|
|
|
case WM_SYSKEYDOWN: {
|
|
|
|
if (m_callback) {
|
|
|
|
ESpecialKey specialKey;
|
|
|
|
EModifierKey modifierKey;
|
|
|
|
uint32_t charCode = translateKeysym(e.wParam, (e.lParam >> 16) & 0xff, specialKey, modifierKey);
|
|
|
|
EModifierKey modifierMask = translateModifiers(e.uMsg);
|
|
|
|
if (charCode)
|
|
|
|
m_callback->charKeyDown(charCode, modifierMask, (HIWORD(e.lParam) & KF_REPEAT) != 0);
|
|
|
|
else if (specialKey != ESpecialKey::None)
|
|
|
|
m_callback->specialKeyDown(specialKey, modifierMask, (HIWORD(e.lParam) & KF_REPEAT) != 0);
|
|
|
|
else if (modifierKey != EModifierKey::None)
|
|
|
|
m_callback->modKeyDown(modifierKey, (HIWORD(e.lParam) & KF_REPEAT) != 0);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_KEYUP:
|
|
|
|
case WM_SYSKEYUP: {
|
|
|
|
if (m_callback) {
|
|
|
|
ESpecialKey specialKey;
|
|
|
|
EModifierKey modifierKey;
|
|
|
|
uint32_t charCode = translateKeysym(e.wParam, (e.lParam >> 16) & 0xff, specialKey, modifierKey);
|
|
|
|
EModifierKey modifierMask = translateModifiers(e.uMsg);
|
|
|
|
if (charCode)
|
|
|
|
m_callback->charKeyUp(charCode, modifierMask);
|
|
|
|
else if (specialKey != ESpecialKey::None)
|
|
|
|
m_callback->specialKeyUp(specialKey, modifierMask);
|
|
|
|
else if (modifierKey != EModifierKey::None)
|
|
|
|
m_callback->modKeyUp(modifierKey);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_LBUTTONDOWN: {
|
|
|
|
buttonDown(e, EMouseButton::Primary);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_LBUTTONUP: {
|
|
|
|
buttonUp(e, EMouseButton::Primary);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_RBUTTONDOWN: {
|
|
|
|
buttonDown(e, EMouseButton::Secondary);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_RBUTTONUP: {
|
|
|
|
buttonUp(e, EMouseButton::Secondary);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MBUTTONDOWN: {
|
|
|
|
buttonDown(e, EMouseButton::Middle);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MBUTTONUP: {
|
|
|
|
buttonUp(e, EMouseButton::Middle);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_XBUTTONDOWN: {
|
|
|
|
if (HIWORD(e.wParam) == XBUTTON1)
|
|
|
|
buttonDown(e, EMouseButton::Aux1);
|
|
|
|
else if (HIWORD(e.wParam) == XBUTTON2)
|
|
|
|
buttonDown(e, EMouseButton::Aux2);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_XBUTTONUP: {
|
|
|
|
if (HIWORD(e.wParam) == XBUTTON1)
|
|
|
|
buttonUp(e, EMouseButton::Aux1);
|
|
|
|
else if (HIWORD(e.wParam) == XBUTTON2)
|
|
|
|
buttonUp(e, EMouseButton::Aux2);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MOUSEMOVE: {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
if (!mouseTracking) {
|
|
|
|
_trackMouse();
|
|
|
|
mouseTracking = true;
|
|
|
|
m_callback->mouseEnter(coord);
|
|
|
|
} else
|
|
|
|
m_callback->mouseMove(coord);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MOUSELEAVE:
|
|
|
|
case WM_NCMOUSELEAVE: {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
m_callback->mouseLeave(coord);
|
|
|
|
mouseTracking = false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_NCMOUSEHOVER:
|
|
|
|
case WM_MOUSEHOVER: {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
m_callback->mouseEnter(coord);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MOUSEWHEEL: {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
SScrollDelta scroll = {};
|
|
|
|
scroll.delta[1] = GET_WHEEL_DELTA_WPARAM(e.wParam) / double(WHEEL_DELTA);
|
|
|
|
m_callback->scroll(coord, scroll);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_MOUSEHWHEEL: {
|
|
|
|
if (m_callback) {
|
|
|
|
int x, y, w, h;
|
|
|
|
getWindowFrame(x, y, w, h);
|
|
|
|
SWindowCoord coord = {{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{GET_X_LPARAM(e.lParam), h - GET_Y_LPARAM(e.lParam)},
|
|
|
|
{float(GET_X_LPARAM(e.lParam)) / float(w), float(h - GET_Y_LPARAM(e.lParam)) / float(h)}};
|
|
|
|
SScrollDelta scroll = {};
|
|
|
|
scroll.delta[0] = GET_WHEEL_DELTA_WPARAM(e.wParam) / double(-WHEEL_DELTA);
|
|
|
|
m_callback->scroll(coord, scroll);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
case WM_CHAR:
|
|
|
|
case WM_UNICHAR: {
|
|
|
|
if (m_callback) {
|
|
|
|
ITextInputCallback* inputCb = m_callback->getTextInputCallback();
|
|
|
|
uint8_t utf8ch[4];
|
|
|
|
size_t len = utf8proc_encode_char(e.wParam, utf8ch);
|
|
|
|
if (inputCb && len)
|
|
|
|
inputCb->insertText(std::string((char*)utf8ch, len));
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
ETouchType getTouchType() const override { return ETouchType::None; }
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
void setStyle(EWindowStyle style) override {
|
2019-09-06 11:04:57 +00:00
|
|
|
LONG_PTR sty = GetWindowLongPtr(m_hwnd, GWL_STYLE);
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-09-06 11:04:57 +00:00
|
|
|
if ((style & EWindowStyle::Titlebar) != EWindowStyle::None) {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty |= WS_CAPTION;
|
2019-09-06 11:04:57 +00:00
|
|
|
} else {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty &= ~WS_CAPTION;
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-09-06 11:04:57 +00:00
|
|
|
if ((style & EWindowStyle::Resize) != EWindowStyle::None) {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty |= WS_THICKFRAME;
|
2019-09-06 11:04:57 +00:00
|
|
|
} else {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty &= ~WS_THICKFRAME;
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-09-06 11:04:57 +00:00
|
|
|
if ((style & EWindowStyle::Close) != EWindowStyle::None) {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty |= (WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
2019-09-06 11:04:57 +00:00
|
|
|
} else {
|
2018-12-08 05:17:51 +00:00
|
|
|
sty &= ~(WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-09-06 11:04:57 +00:00
|
|
|
SetWindowLongPtr(m_hwnd, GWL_STYLE, sty);
|
2018-12-08 05:17:51 +00:00
|
|
|
}
|
2015-11-06 03:20:58 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
EWindowStyle getStyle() const override {
|
2019-09-06 11:04:57 +00:00
|
|
|
const LONG_PTR sty = GetWindowLongPtr(m_hwnd, GWL_STYLE);
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
EWindowStyle retval = EWindowStyle::None;
|
2019-09-06 11:04:57 +00:00
|
|
|
if ((sty & WS_CAPTION) != 0) {
|
2018-12-08 05:17:51 +00:00
|
|
|
retval |= EWindowStyle::Titlebar;
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
|
|
|
if ((sty & WS_THICKFRAME) != 0) {
|
2018-12-08 05:17:51 +00:00
|
|
|
retval |= EWindowStyle::Resize;
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
|
|
|
if ((sty & WS_SYSMENU)) {
|
2018-12-08 05:17:51 +00:00
|
|
|
retval |= EWindowStyle::Close;
|
2019-09-06 11:04:57 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
return retval;
|
|
|
|
}
|
2015-11-03 04:19:41 +00:00
|
|
|
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_gfxCtx->getCommandQueue(); }
|
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_gfxCtx->getDataFactory(); }
|
2015-11-18 06:14:49 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
/* Creates a new context on current thread!! Call from main client thread */
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override { return m_gfxCtx->getMainContextDataFactory(); }
|
2015-11-07 07:49:53 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
/* Creates a new context on current thread!! Call from client loading thread */
|
2019-08-13 00:52:20 +00:00
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override { return m_gfxCtx->getLoadContextDataFactory(); }
|
2015-05-06 00:50:57 +00:00
|
|
|
};
|
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
std::shared_ptr<IWindow> _WindowWin32New(SystemStringView title, Boo3DAppContextWin32& d3dCtx) {
|
|
|
|
return std::make_shared<WindowWin32>(title, d3dCtx);
|
2015-05-06 00:50:57 +00:00
|
|
|
}
|
2016-02-24 20:52:31 +00:00
|
|
|
|
2018-12-08 05:17:51 +00:00
|
|
|
} // namespace boo
|