#define GLFW_INCLUDE_NONE #include #include #include #include #include "nanovg/nanovg.h" #ifdef USE_D3D11 #include #define GLFW_EXPOSE_NATIVE_WIN32 #include #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 #include #define NANOVG_GL3_IMPLEMENTATION #include "nanovg/nanovg_gl.h" #endif #ifdef _WIN32 #define GLFW_EXPOSE_NATIVE_WIN32 #include #elif defined( __SWITCH__) #include 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(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(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(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