312 lines
9.9 KiB
C++
312 lines
9.9 KiB
C++
#define GLFW_INCLUDE_NONE
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <mpv/client.h>
|
|
|
|
#include "nanovg/nanovg.h"
|
|
|
|
#ifdef USE_D3D11
|
|
#include <mpv/render_dxgi.h>
|
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
|
#include <GLFW/glfw3native.h>
|
|
#define NANOVG_D3D11_IMPLEMENTATION
|
|
#include "nanovg/nanovg_d3d11.h"
|
|
|
|
static ID3D11Device* d3dDevice = nullptr;
|
|
static ID3D11DeviceContext* d3dDeviceContext = nullptr;
|
|
static IDXGISwapChain* d3dSwapChain = nullptr;
|
|
static ID3D11RenderTargetView* d3dRenderTargetView = nullptr;
|
|
|
|
// Forward declarations of helper functions
|
|
bool InitD3D(HWND hWnd);
|
|
void CleanupD3D();
|
|
bool ResizeD3D(GLFWwindow* window, int width, int height);
|
|
#else
|
|
#include <mpv/render_gl.h>
|
|
#include <glad/glad.h>
|
|
#define NANOVG_GL3_IMPLEMENTATION
|
|
#include "nanovg/nanovg_gl.h"
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
|
#include <GLFW/glfw3native.h>
|
|
#elif defined( __SWITCH__)
|
|
#include <switch.h>
|
|
|
|
extern "C" void userAppInit() {
|
|
socketInitializeDefault();
|
|
nxlinkStdio();
|
|
appletSetWirelessPriorityMode(AppletWirelessPriorityMode_OptimizedForWlan);
|
|
plInitialize(PlServiceType_User);
|
|
}
|
|
|
|
extern "C" void userAppExit() {
|
|
plExit();
|
|
socketExit();
|
|
}
|
|
#endif
|
|
|
|
static int osdShow = 0;
|
|
|
|
static void die(const char* msg) {
|
|
fprintf(stderr, "%s\n", msg);
|
|
exit(1);
|
|
}
|
|
|
|
static int64_t mpv_property_int(mpv_handle* mpv, const char* key) {
|
|
int64_t value = -1;
|
|
mpv_get_property(mpv, key, MPV_FORMAT_INT64, &value);
|
|
return value;
|
|
}
|
|
|
|
// Main code
|
|
int main(int argc, char* argv[]) {
|
|
if (argc < 2) die("missing file path");
|
|
|
|
if (!glfwInit()) die("init glfw failed");
|
|
|
|
NVGcontext* vg = nullptr;
|
|
mpv_render_context* mpv_context = nullptr;
|
|
mpv_handle* mpv = mpv_create();
|
|
mpv_set_option_string(mpv, "subs-fallback", "yes");
|
|
mpv_set_option_string(mpv, "video-timing-offset", "0"); // 60fps
|
|
mpv_set_option_string(mpv, "vd-lavc-dr", "yes");
|
|
mpv_set_option_string(mpv, "terminal", "yes");
|
|
mpv_set_option_string(mpv, "hwdec", "auto");
|
|
mpv_set_option_string(mpv, "vo", "gpu-next");
|
|
#ifdef _DEBUG
|
|
mpv_set_option_string(this->mpv, "msg-level", "ffmpeg=trace");
|
|
mpv_set_option_string(this->mpv, "msg-level", "all=v");
|
|
#endif
|
|
if (mpv_initialize(mpv) < 0) die("init mpv failed");
|
|
|
|
// Create window with graphics context
|
|
GLFWwindow* window = glfwCreateWindow(1280, 720, argv[1], nullptr, nullptr);
|
|
if (!window) {
|
|
glfwTerminate();
|
|
return -1;
|
|
}
|
|
glfwSetWindowUserPointer(window, mpv);
|
|
glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int scancode, int action, int mods) {
|
|
mpv_handle* mpv = static_cast<mpv_handle*>(glfwGetWindowUserPointer(window));
|
|
if (action == GLFW_PRESS) {
|
|
if (key == GLFW_KEY_ESCAPE)
|
|
glfwSetWindowShouldClose(window, 1);
|
|
else if (key == GLFW_KEY_ENTER)
|
|
osdShow = !osdShow;
|
|
else if (key == GLFW_KEY_LEFT)
|
|
mpv_command_string(mpv, "seek -15");
|
|
else if (key == GLFW_KEY_RIGHT)
|
|
mpv_command_string(mpv, "seek 15");
|
|
else if (key == GLFW_KEY_SPACE)
|
|
mpv_command_string(mpv, "");
|
|
else
|
|
printf("glfwKeyCallback key press %d\n", key);
|
|
}
|
|
});
|
|
#ifdef _WIN32
|
|
HWND wid = glfwGetWin32Window(window);
|
|
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
|
#endif
|
|
#ifdef USE_D3D11
|
|
// Initialize Direct3D
|
|
HWND hwnd = glfwGetWin32Window(window);
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
if (!InitD3D(hwnd)) die("init dx11 failed");
|
|
glfwSetFramebufferSizeCallback(window, (GLFWframebuffersizefun)ResizeD3D);
|
|
|
|
vg = nvgCreateD3D11(d3dDevice, NVG_STENCIL_STROKES | NVG_ANTIALIAS);
|
|
|
|
mpv_dxgi_init_params init_params = {d3dDevice, d3dSwapChain};
|
|
mpv_render_param params[] = {
|
|
{MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_DXGI},
|
|
{MPV_RENDER_PARAM_DXGI_INIT_PARAMS, &init_params},
|
|
{MPV_RENDER_PARAM_INVALID, nullptr},
|
|
};
|
|
#else
|
|
glfwMakeContextCurrent(window);
|
|
// Load OpenGL routines using glad
|
|
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
|
|
glfwSwapInterval(1);
|
|
glfwSetTime(0);
|
|
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
vg = nvgCreateGL3(NVG_STENCIL_STROKES | NVG_ANTIALIAS);
|
|
|
|
mpv_opengl_init_params gl_init_params{
|
|
[](void* fn_ctx, const char* name) { return (void*)glfwGetProcAddress(name); }, nullptr};
|
|
mpv_render_param params[] = {
|
|
{MPV_RENDER_PARAM_API_TYPE, const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL)},
|
|
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
|
|
{MPV_RENDER_PARAM_INVALID, nullptr},
|
|
};
|
|
#endif
|
|
if (mpv_render_context_create(&mpv_context, mpv, params) < 0) die("init mpv context failed");
|
|
mpv_render_context_set_update_callback(
|
|
mpv_context,
|
|
[](void* obj) {
|
|
mpv_render_context* ctx = static_cast<mpv_render_context*>(obj);
|
|
uint64_t flags = mpv_render_context_update(ctx);
|
|
if (flags & MPV_RENDER_UPDATE_FRAME) glfwPostEmptyEvent();
|
|
},
|
|
mpv_context);
|
|
|
|
#ifdef __SWITCH__
|
|
PlFontData fd;
|
|
if (R_SUCCEEDED(plGetSharedFontByType(&fd, PlSharedFontType_Standard))) {
|
|
int font = nvgCreateFontMem(vg, "sans", (unsigned char*)fd.address, fd.size, 0);
|
|
if (font < 0) die("load font failed");
|
|
}
|
|
#else
|
|
int font = nvgCreateFont(vg, "sans", "Roboto-Regular.ttf");
|
|
if (font < 0) die("load font failed");
|
|
#endif
|
|
|
|
const char* args[] = {"loadfile", argv[1], nullptr};
|
|
if (mpv_command(mpv, args) < 0) die("load file failed");
|
|
|
|
int fbw = 0, fbh = 0;
|
|
// Main loop
|
|
while (!glfwWindowShouldClose(window)) {
|
|
glfwGetFramebufferSize(window, &fbw, &fbh);
|
|
#if defined(USE_D3D11)
|
|
mpv_render_param mpv_params[] = {
|
|
{MPV_RENDER_PARAM_INVALID, nullptr},
|
|
};
|
|
#else
|
|
mpv_opengl_fbo mpv_fbo{0, fbw, fbh};
|
|
int flip_y{1};
|
|
mpv_render_param mpv_params[3] = {
|
|
{MPV_RENDER_PARAM_OPENGL_FBO, &mpv_fbo},
|
|
{MPV_RENDER_PARAM_FLIP_Y, &flip_y},
|
|
{MPV_RENDER_PARAM_INVALID, nullptr},
|
|
};
|
|
#endif
|
|
// Rendering
|
|
mpv_render_context_render(mpv_context, mpv_params);
|
|
mpv_render_context_report_swap(mpv_context);
|
|
#ifdef USE_D3D11
|
|
d3dDeviceContext->OMSetRenderTargets(1, &d3dRenderTargetView, nullptr);
|
|
#else
|
|
glViewport(0, 0, fbw, fbh);
|
|
#endif
|
|
if (osdShow) {
|
|
// Draw OSD
|
|
std::string profiles[4] = {
|
|
"Video Codec: " + std::string(mpv_get_property_string(mpv, "video-codec")),
|
|
"Pixel Format: " + std::string(mpv_get_property_string(mpv, "video-params/pixelformat")),
|
|
"Hardware Decode: " + std::string(mpv_get_property_string(mpv, "hwdec-current")),
|
|
"Video Bitrate: " + std::to_string(mpv_property_int(mpv, "video-bitrate") / 1024),
|
|
};
|
|
|
|
nvgBeginFrame(vg, fbw, fbh, 1.0f);
|
|
nvgBeginPath(vg);
|
|
nvgRect(vg, 10, 10, 800, 400);
|
|
nvgFillColor(vg, nvgRGBA(0, 0, 0, 128));
|
|
nvgFill(vg);
|
|
|
|
nvgFontSize(vg, 20.0f);
|
|
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
|
nvgFillColor(vg, nvgRGBA(240, 240, 240, 255));
|
|
nvgFontFace(vg, "sans");
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
nvgText(vg, 30, 30 * (i + 1), profiles[i].c_str(), nullptr);
|
|
}
|
|
nvgEndFrame(vg);
|
|
}
|
|
#ifdef USE_D3D11
|
|
d3dSwapChain->Present(1, 0); // Present with vsync
|
|
#else
|
|
glfwSwapBuffers(window);
|
|
#endif
|
|
glfwWaitEvents();
|
|
}
|
|
|
|
mpv_command_string(mpv, "quit");
|
|
mpv_render_context_free(mpv_context);
|
|
mpv_terminate_destroy(mpv);
|
|
|
|
#ifdef USE_D3D11
|
|
nvgDeleteD3D11(vg);
|
|
CleanupD3D();
|
|
#elif defined(NANOVG_GL3)
|
|
nvgDeleteGL3(vg);
|
|
#endif
|
|
glfwDestroyWindow(window);
|
|
glfwTerminate();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef USE_D3D11
|
|
|
|
bool InitD3D(HWND hWnd) {
|
|
static const D3D_DRIVER_TYPE driverAttempts[] = {
|
|
D3D_DRIVER_TYPE_HARDWARE,
|
|
D3D_DRIVER_TYPE_WARP,
|
|
D3D_DRIVER_TYPE_REFERENCE,
|
|
};
|
|
|
|
static const D3D_FEATURE_LEVEL levelAttempts[] = {
|
|
D3D_FEATURE_LEVEL_11_1, // Direct3D 11.1 SM 6
|
|
D3D_FEATURE_LEVEL_11_0, // Direct3D 11.0 SM 5
|
|
D3D_FEATURE_LEVEL_10_1, // Direct3D 10.1 SM 4
|
|
};
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
// Setup swap chain
|
|
DXGI_SWAP_CHAIN_DESC sd;
|
|
ZeroMemory(&sd, sizeof(sd));
|
|
sd.BufferCount = 2;
|
|
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
sd.OutputWindow = hWnd;
|
|
sd.SampleDesc.Count = 1;
|
|
sd.SampleDesc.Quality = 0;
|
|
sd.Windowed = TRUE;
|
|
|
|
for (size_t i = 0; i < ARRAYSIZE(driverAttempts); i++) {
|
|
hr = D3D11CreateDeviceAndSwapChain(nullptr, driverAttempts[i], nullptr, 0, levelAttempts,
|
|
ARRAYSIZE(levelAttempts), D3D11_SDK_VERSION, &sd, &d3dSwapChain, &d3dDevice, nullptr, &d3dDeviceContext);
|
|
if (SUCCEEDED(hr)) break;
|
|
}
|
|
|
|
if (FAILED(hr)) {
|
|
CleanupD3D();
|
|
return false;
|
|
}
|
|
return ResizeD3D(nullptr, 0, 0);
|
|
}
|
|
|
|
void CleanupD3D() {
|
|
if (d3dRenderTargetView) d3dRenderTargetView->Release();
|
|
if (d3dSwapChain) d3dSwapChain->Release();
|
|
if (d3dDeviceContext) d3dDeviceContext->Release();
|
|
if (d3dDevice) d3dDevice->Release();
|
|
}
|
|
|
|
bool ResizeD3D(GLFWwindow* window, int width, int height) {
|
|
ID3D11Texture2D* backBuffer = nullptr;
|
|
HRESULT hr = S_OK;
|
|
if (d3dRenderTargetView) d3dRenderTargetView->Release();
|
|
// Resize render target buffers
|
|
if (width > 0 && height > 0) {
|
|
hr = d3dSwapChain->ResizeBuffers(2, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0);
|
|
if (FAILED(hr)) return false;
|
|
}
|
|
hr = d3dSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
|
|
if (FAILED(hr)) return false;
|
|
hr = d3dDevice->CreateRenderTargetView(backBuffer, nullptr, &d3dRenderTargetView);
|
|
backBuffer->Release();
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
#endif
|