Realtime Video Routing (#1126)
* Syphon video routing with OpenGL and Vulkan (MacOS) * Spout video routing with DX11 and OpenGL (Windows) * Fix Xcode detection
This commit is contained in:
parent
6e6f75386b
commit
7cd308fcae
|
@ -22,3 +22,9 @@
|
|||
[submodule "core/deps/oboe"]
|
||||
path = core/deps/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
[submodule "core/deps/Syphon"]
|
||||
path = core/deps/Syphon
|
||||
url=https://github.com/vkedwardli/Syphon-Framework.git
|
||||
[submodule "core/deps/Spout"]
|
||||
path = core/deps/Spout
|
||||
url = https://github.com/leadedge/Spout2.git
|
||||
|
|
|
@ -214,10 +214,15 @@ else()
|
|||
$<$<COMPILE_LANGUAGE:CXX>:-Wall>)
|
||||
endif()
|
||||
|
||||
if(${CMAKE_OSX_SYSROOT} MATCHES "\/MacOSX.platform\/Developer\/SDKs")
|
||||
set(TARGET_MAC ON)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
$<$<BOOL:${APPLE}>:GL_SILENCE_DEPRECATION>
|
||||
$<$<BOOL:${ENABLE_LOG}>:DEBUGFAST>
|
||||
$<$<BOOL:${IOS}>:TARGET_IPHONE>
|
||||
$<$<BOOL:${TARGET_MAC}>:TARGET_MAC>
|
||||
$<$<BOOL:${IOS}>:GLES>
|
||||
$<$<BOOL:${IOS}>:GLES3>
|
||||
$<$<BOOL:${IOS}>:GLES_SILENCE_DEPRECATION>
|
||||
|
@ -1246,6 +1251,7 @@ if(USE_VULKAN)
|
|||
target_compile_definitions(${PROJECT_NAME} PUBLIC VK_USE_PLATFORM_WIN32_KHR)
|
||||
elseif(APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC VK_USE_PLATFORM_MACOS_MVK)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC VK_USE_PLATFORM_METAL_EXT)
|
||||
endif()
|
||||
|
||||
add_subdirectory(core/deps/Vulkan-Headers)
|
||||
|
@ -1623,6 +1629,20 @@ if(NOT LIBRETRO)
|
|||
COMMAND ldid -S${CMAKE_CURRENT_SOURCE_DIR}/shell/apple/emulator-ios/emulator/flycast.entitlements ${CMAKE_CURRENT_BINARY_DIR}/Payload/Flycast.app/Flycast
|
||||
COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>-${CMAKE_OSX_SYSROOT}/Flycast.ipa" --format=zip ${CMAKE_CURRENT_BINARY_DIR}/Payload)
|
||||
else()
|
||||
|
||||
add_subdirectory(core/deps/Syphon)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Syphon)
|
||||
if(${CMAKE_GENERATOR} MATCHES "^Xcode.*")
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Flycast.app/Contents/Resources/)
|
||||
else()
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${SYPHON_METALLIB}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Flycast.app/Contents/Resources/)
|
||||
endif()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
shell/apple/common/http_client.mm
|
||||
shell/apple/emulator-osx/emulator-osx/SDLMain.h
|
||||
|
@ -1659,6 +1679,7 @@ if(NOT LIBRETRO)
|
|||
find_library(FOUNDATION_LIBRARY Foundation)
|
||||
find_library(AUDIO_TOOLBOX_LIBRARY AudioToolbox)
|
||||
find_library(OPENGL_LIBRARY OpenGL)
|
||||
find_library(METAL_LIBRARY Metal)
|
||||
find_library(IOSURFACE_LIBRARY IOSurface)
|
||||
find_library(MULTITOUCH_SUPPORT_LIBRARY MultitouchSupport /System/Library/PrivateFrameworks)
|
||||
|
||||
|
@ -1667,6 +1688,7 @@ if(NOT LIBRETRO)
|
|||
${FOUNDATION_LIBRARY}
|
||||
${AUDIO_TOOLBOX_LIBRARY}
|
||||
${OPENGL_LIBRARY}
|
||||
${METAL_LIBRARY}
|
||||
${IOSURFACE_LIBRARY}
|
||||
${MULTITOUCH_SUPPORT_LIBRARY})
|
||||
|
||||
|
@ -1713,6 +1735,16 @@ if(NOT LIBRETRO)
|
|||
shell/uwp/http_client.cpp)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RESOURCE "${ResourceFiles}")
|
||||
else()
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/Spout/SPOUTSDK/SpoutGL)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/Spout/SPOUTSDK/SpoutDirectX/SpoutDX)
|
||||
SET(SPOUT_BUILD_CMT OFF CACHE BOOL "For Visual Studio - build /MT to link runtime libraries" FORCE)
|
||||
SET(SPOUT_BUILD_SPOUTDX ON CACHE BOOL "Build SpoutDX DirectX11 support library" FORCE)
|
||||
SET(SPOUT_BUILD_LIBRARY OFF CACHE BOOL "Build C-compatible cross compiler library" FORCE)
|
||||
SET(SKIP_INSTALL_ALL ON CACHE BOOL "Install headers and libraries" FORCE)
|
||||
add_subdirectory(core/deps/Spout)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Spout_static SpoutDX_static)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE VIDEO_ROUTING)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE shell/windows/flycast.rc)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE dsound opengl32 winmm ws2_32 wsock32 xinput9_1_0 cfgmgr32 wininet)
|
||||
endif()
|
||||
|
|
|
@ -106,6 +106,11 @@ Option<bool> DupeFrames("rend.DupeFrames", false);
|
|||
Option<int> PerPixelLayers("rend.PerPixelLayers", 32);
|
||||
Option<bool> NativeDepthInterpolation("rend.NativeDepthInterpolation", false);
|
||||
Option<bool> EmulateFramebuffer("rend.EmulateFramebuffer", false);
|
||||
#ifdef VIDEO_ROUTING
|
||||
Option<bool> VideoRouting("rend.VideoRouting", false);
|
||||
Option<bool> VideoRoutingScale("rend.VideoRoutingScale", false);
|
||||
Option<int> VideoRoutingVRes("rend.VideoRoutingVRes", 720);
|
||||
#endif
|
||||
|
||||
// Misc
|
||||
|
||||
|
|
|
@ -465,6 +465,11 @@ extern Option<bool> ThreadedRendering;
|
|||
extern Option<bool> DupeFrames;
|
||||
extern Option<bool> NativeDepthInterpolation;
|
||||
extern Option<bool> EmulateFramebuffer;
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern Option<bool> VideoRouting;
|
||||
extern Option<bool> VideoRoutingScale;
|
||||
extern Option<int> VideoRoutingVRes;
|
||||
#endif
|
||||
|
||||
// Misc
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 122c8519685f1f221a0dcdda96e23424ffce3f08
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 486dd8e4ef3bead482ea26de99770f4389e24e37
|
|
@ -185,6 +185,10 @@ void DX11Renderer::Term()
|
|||
quad.reset();
|
||||
deviceContext.reset();
|
||||
device.reset();
|
||||
vrStagingTexture.reset();
|
||||
vrStagingTextureSRV.reset();
|
||||
vrScaledTexture.reset();
|
||||
vrScaledRenderTarget.reset();
|
||||
}
|
||||
|
||||
void DX11Renderer::createDepthTexAndView(ComPtr<ID3D11Texture2D>& texture, ComPtr<ID3D11DepthStencilView>& view, int width, int height, DXGI_FORMAT format, UINT bindFlags)
|
||||
|
@ -460,6 +464,7 @@ bool DX11Renderer::Render()
|
|||
deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr);
|
||||
displayFramebuffer();
|
||||
DrawOSD(false);
|
||||
renderVideoRouting();
|
||||
theDX11Context.setFrameRendered();
|
||||
#else
|
||||
ID3D11RenderTargetView *nullView = nullptr;
|
||||
|
@ -960,6 +965,7 @@ void DX11Renderer::RenderFramebuffer(const FramebufferInfo& info)
|
|||
deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr);
|
||||
displayFramebuffer();
|
||||
DrawOSD(false);
|
||||
renderVideoRouting();
|
||||
theDX11Context.setFrameRendered();
|
||||
#else
|
||||
ID3D11RenderTargetView *nullView = nullptr;
|
||||
|
@ -1294,6 +1300,88 @@ void DX11Renderer::writeFramebufferToVRAM()
|
|||
WriteFramebuffer<2, 1, 0, 3>(width, height, (u8 *)tmp_buf.data(), texAddress, pvrrc.fb_W_CTRL, linestride, xClip, yClip);
|
||||
}
|
||||
|
||||
void DX11Renderer::renderVideoRouting()
|
||||
{
|
||||
#ifdef VIDEO_ROUTING
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
extern void os_VideoRoutingPublishFrameTexture(ID3D11Texture2D* pTexture);
|
||||
|
||||
ID3D11RenderTargetView* pRenderTargetView = theDX11Context.getRenderTarget().get();
|
||||
|
||||
// Backbuffer texture would be different after reszing, fetching new address everytime
|
||||
ID3D11Resource* pResource = nullptr;
|
||||
pRenderTargetView->GetResource(&pResource);
|
||||
ID3D11Texture2D* backBufferTexture = nullptr;
|
||||
pResource->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&backBufferTexture);
|
||||
|
||||
if (config::VideoRoutingScale)
|
||||
{
|
||||
static int targetWidth, targetHeight, vrStagingWidth, vrStagingHeight;
|
||||
static D3D11_VIEWPORT scaledViewPort{};
|
||||
|
||||
auto updateScaledTexture = [this]() -> void {
|
||||
targetWidth = config::VideoRoutingVRes * settings.display.width / settings.display.height;
|
||||
targetHeight = config::VideoRoutingVRes;
|
||||
|
||||
vrScaledTexture.reset();
|
||||
vrScaledRenderTarget.reset();
|
||||
createTexAndRenderTarget(vrScaledTexture, vrScaledRenderTarget, targetWidth, targetHeight);
|
||||
|
||||
scaledViewPort.Width = targetWidth;
|
||||
scaledViewPort.Height = targetHeight;
|
||||
scaledViewPort.MinDepth = 0.f;
|
||||
scaledViewPort.MaxDepth = 1.f;
|
||||
};
|
||||
|
||||
D3D11_TEXTURE2D_DESC bbDesc = {};
|
||||
backBufferTexture->GetDesc(&bbDesc);
|
||||
|
||||
// Window resized
|
||||
if (bbDesc.Width != vrStagingWidth || bbDesc.Height != vrStagingHeight)
|
||||
{
|
||||
vrStagingTexture.reset();
|
||||
vrStagingTextureSRV.reset();
|
||||
|
||||
D3D11_TEXTURE2D_DESC srvDesc = bbDesc;
|
||||
srvDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
srvDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
device->CreateTexture2D(&srvDesc, nullptr, &vrStagingTexture.get());
|
||||
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc{};
|
||||
viewDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
viewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
viewDesc.Texture2D.MipLevels = 1;
|
||||
|
||||
device->CreateShaderResourceView(vrStagingTexture.get(), &viewDesc, &vrStagingTextureSRV.get());
|
||||
|
||||
updateScaledTexture();
|
||||
}
|
||||
|
||||
// Scale down value changed
|
||||
if (targetHeight != config::VideoRoutingVRes)
|
||||
{
|
||||
updateScaledTexture();
|
||||
}
|
||||
|
||||
deviceContext->OMSetRenderTargets(1, &vrScaledRenderTarget.get(), nullptr);
|
||||
deviceContext->RSSetViewports(1, &scaledViewPort);
|
||||
deviceContext->CopyResource(vrStagingTexture.get(), backBufferTexture);
|
||||
quad->draw(vrStagingTextureSRV, samplers->getSampler(true));
|
||||
os_VideoRoutingPublishFrameTexture(vrScaledTexture);
|
||||
|
||||
deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr);
|
||||
|
||||
} else {
|
||||
os_VideoRoutingPublishFrameTexture(backBufferTexture);
|
||||
}
|
||||
|
||||
backBufferTexture->Release();
|
||||
pResource->Release();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Renderer *rend_DirectX11()
|
||||
{
|
||||
return new DX11Renderer();
|
||||
|
|
|
@ -96,6 +96,7 @@ protected:
|
|||
void setCullMode(int mode);
|
||||
virtual void setRTTSize(int width, int height) {}
|
||||
void writeFramebufferToVRAM();
|
||||
void renderVideoRouting();
|
||||
|
||||
ComPtr<ID3D11Device> device;
|
||||
ComPtr<ID3D11DeviceContext> deviceContext;
|
||||
|
@ -154,6 +155,10 @@ private:
|
|||
ComPtr<ID3D11Texture2D> fbScaledTexture;
|
||||
ComPtr<ID3D11ShaderResourceView> fbScaledTextureView;
|
||||
ComPtr<ID3D11RenderTargetView> fbScaledRenderTarget;
|
||||
ComPtr<ID3D11Texture2D> vrStagingTexture;
|
||||
ComPtr<ID3D11ShaderResourceView> vrStagingTextureSRV;
|
||||
ComPtr<ID3D11Texture2D> vrScaledTexture;
|
||||
ComPtr<ID3D11RenderTargetView> vrScaledRenderTarget;
|
||||
|
||||
ComPtr<ID3D11RasterizerState> rasterCullNone, rasterCullFront, rasterCullBack;
|
||||
|
||||
|
|
|
@ -161,6 +161,8 @@ bool DX11Context::init(bool keepCurrentWindow)
|
|||
NOTICE_LOG(RENDERER, "No system-provided shader cache");
|
||||
}
|
||||
|
||||
initVideoRouting();
|
||||
|
||||
imguiDriver = std::unique_ptr<ImGuiDriver>(new DX11Driver(pDevice, pDeviceContext));
|
||||
resize();
|
||||
shaders.init(pDevice, &D3DCompile);
|
||||
|
@ -171,6 +173,19 @@ bool DX11Context::init(bool keepCurrentWindow)
|
|||
return success;
|
||||
}
|
||||
|
||||
void DX11Context::initVideoRouting()
|
||||
{
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern void os_VideoRoutingTermDX();
|
||||
extern void os_VideoRoutingInitSpoutDXWithDevice(ID3D11Device* pDevice);
|
||||
os_VideoRoutingTermDX();
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
os_VideoRoutingInitSpoutDXWithDevice(pDevice.get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DX11Context::term()
|
||||
{
|
||||
NOTICE_LOG(RENDERER, "DX11 Context terminating");
|
||||
|
@ -195,6 +210,10 @@ void DX11Context::term()
|
|||
FreeLibrary(d3dcompilerHandle);
|
||||
d3dcompilerHandle = NULL;
|
||||
}
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern void os_VideoRoutingTermDX();
|
||||
os_VideoRoutingTermDX();
|
||||
#endif
|
||||
}
|
||||
|
||||
void DX11Context::Present()
|
||||
|
|
|
@ -33,6 +33,7 @@ class DX11Context : public GraphicsContext
|
|||
{
|
||||
public:
|
||||
bool init(bool keepCurrentWindow = false);
|
||||
void initVideoRouting() override;
|
||||
void term() override;
|
||||
void EndImGuiFrame();
|
||||
void Present();
|
||||
|
|
|
@ -667,6 +667,7 @@ struct DX11OITRenderer : public DX11Renderer
|
|||
deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr);
|
||||
displayFramebuffer();
|
||||
DrawOSD(false);
|
||||
renderVideoRouting();
|
||||
theDX11Context.setFrameRendered();
|
||||
#else
|
||||
ID3D11RenderTargetView *nullView = nullptr;
|
||||
|
|
|
@ -671,6 +671,7 @@ struct OpenGL4Renderer : OpenGLRenderer
|
|||
gl.ofbo2.ready = false;
|
||||
frameRendered = true;
|
||||
}
|
||||
renderVideoRouting();
|
||||
restoreCurrentFramebuffer();
|
||||
|
||||
return true;
|
||||
|
@ -779,6 +780,8 @@ static void resize(int w, int h)
|
|||
|
||||
bool OpenGL4Renderer::renderFrame(int width, int height)
|
||||
{
|
||||
initVideoRoutingFrameBuffer();
|
||||
|
||||
const bool is_rtt = pvrrc.isRTT;
|
||||
|
||||
TransformMatrix<COORD_OPENGL> matrices(pvrrc, is_rtt ? pvrrc.getFramebufferWidth() : width,
|
||||
|
|
|
@ -445,6 +445,7 @@ void termGLCommon()
|
|||
gl.dcfb.tex = 0;
|
||||
gl.ofbo2.framebuffer.reset();
|
||||
gl.fbscaling.framebuffer.reset();
|
||||
gl.videorouting.framebuffer.reset();
|
||||
#ifdef LIBRETRO
|
||||
postProcessor.term();
|
||||
termVmuLightgun();
|
||||
|
@ -1151,6 +1152,8 @@ static void upload_vertex_indices()
|
|||
|
||||
bool OpenGLRenderer::renderFrame(int width, int height)
|
||||
{
|
||||
initVideoRoutingFrameBuffer();
|
||||
|
||||
bool is_rtt = pvrrc.isRTT;
|
||||
|
||||
float vtx_min_fZ = 0.f; //pvrrc.fZ_min;
|
||||
|
@ -1378,6 +1381,22 @@ bool OpenGLRenderer::renderFrame(int width, int height)
|
|||
return !is_rtt;
|
||||
}
|
||||
|
||||
void OpenGLRenderer::initVideoRoutingFrameBuffer()
|
||||
{
|
||||
#ifdef VIDEO_ROUTING
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width);
|
||||
int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height);
|
||||
if (gl.videorouting.framebuffer != nullptr
|
||||
&& (gl.videorouting.framebuffer->getWidth() != targetWidth || gl.videorouting.framebuffer->getHeight() != targetHeight))
|
||||
gl.videorouting.framebuffer.reset();
|
||||
if (gl.videorouting.framebuffer == nullptr)
|
||||
gl.videorouting.framebuffer = std::make_unique<GlFramebuffer>(targetWidth, targetHeight, true, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLRenderer::Term()
|
||||
{
|
||||
TexCache.Clear();
|
||||
|
@ -1399,11 +1418,32 @@ bool OpenGLRenderer::Render()
|
|||
frameRendered = true;
|
||||
gl.ofbo2.ready = false;
|
||||
}
|
||||
|
||||
renderVideoRouting();
|
||||
restoreCurrentFramebuffer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLRenderer::renderVideoRouting()
|
||||
{
|
||||
#ifdef VIDEO_ROUTING
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, gl.ofbo.origFbo);
|
||||
gl.videorouting.framebuffer->bind(GL_DRAW_FRAMEBUFFER);
|
||||
glcache.Disable(GL_SCISSOR_TEST);
|
||||
int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width);
|
||||
int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height);
|
||||
glBlitFramebuffer(0, 0, settings.display.width, settings.display.height,
|
||||
0, 0, targetWidth, targetHeight,
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
extern void os_VideoRoutingPublishFrameTexture(GLuint texID, GLuint texTarget, float w, float h);
|
||||
os_VideoRoutingPublishFrameTexture(gl.videorouting.framebuffer->getTexture(), GL_TEXTURE_2D, targetWidth, targetHeight);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Renderer* rend_GLES2()
|
||||
{
|
||||
return new OpenGLRenderer();
|
||||
|
|
|
@ -305,6 +305,11 @@ struct gl_ctx
|
|||
bool ready = false;
|
||||
} ofbo2;
|
||||
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<GlFramebuffer> framebuffer;
|
||||
} videorouting;
|
||||
|
||||
const char *gl_version;
|
||||
const char *glsl_version_header;
|
||||
int gl_major;
|
||||
|
@ -545,6 +550,7 @@ protected:
|
|||
}
|
||||
|
||||
bool renderLastFrame();
|
||||
void renderVideoRouting();
|
||||
|
||||
private:
|
||||
bool renderFrame(int width, int height);
|
||||
|
@ -553,6 +559,7 @@ protected:
|
|||
bool frameRendered = false;
|
||||
int width = 640;
|
||||
int height = 480;
|
||||
void initVideoRoutingFrameBuffer();
|
||||
};
|
||||
|
||||
void initQuad();
|
||||
|
|
|
@ -1576,7 +1576,7 @@ static void gui_display_settings()
|
|||
if (ImGui::Button("Change"))
|
||||
gui_state = GuiState::Onboarding;
|
||||
#endif
|
||||
#if defined(__APPLE__) && TARGET_OS_OSX
|
||||
#ifdef TARGET_MAC
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Reveal in Finder").x - ImGui::GetStyle().FramePadding.x);
|
||||
if (ImGui::Button("Reveal in Finder"))
|
||||
{
|
||||
|
@ -2143,6 +2143,39 @@ static void gui_display_settings()
|
|||
OptionCheckbox("Load Custom Textures", config::CustomTextures,
|
||||
"Load custom/high-res textures from data/textures/<game id>");
|
||||
}
|
||||
#ifdef VIDEO_ROUTING
|
||||
#ifdef __APPLE__
|
||||
header("Video Routing (Syphon)");
|
||||
#elif defined(_WIN32)
|
||||
((renderApi == 0) || (renderApi == 3)) ? header("Video Routing (Spout)") : header("Video Routing (Only available with OpenGL or DirectX 11)");
|
||||
#endif
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
if (OptionCheckbox("Send video content to another application", config::VideoRouting,
|
||||
"e.g. Route GPU texture to OBS Studio directly instead of using CPU intensive Display/Window Capture"))
|
||||
#elif defined(_WIN32)
|
||||
DisabledScope scope( !( (renderApi == 0) || (renderApi == 3)) );
|
||||
if (OptionCheckbox("Send video content to another program", config::VideoRouting,
|
||||
"e.g. Route GPU texture to OBS Studio directly instead of using CPU intensive Display/Window Capture"))
|
||||
#endif
|
||||
{
|
||||
GraphicsContext::Instance()->initVideoRouting();
|
||||
}
|
||||
{
|
||||
DisabledScope scope(!config::VideoRouting);
|
||||
OptionCheckbox("Scale down before sending", config::VideoRoutingScale, "Could increase performance when sharing a smaller texture, YMMV");
|
||||
{
|
||||
DisabledScope scope(!config::VideoRoutingScale);
|
||||
static int vres = config::VideoRoutingVRes;
|
||||
if( ImGui::InputInt("Output vertical resolution", &vres) )
|
||||
{
|
||||
config::VideoRoutingVRes = vres;
|
||||
}
|
||||
}
|
||||
ImGui::Text("Output texture size: %d x %d", config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width, config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndTabItem();
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "vulkan_context.h"
|
||||
#include "vulkan_renderer.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/backends/imgui_impl_vulkan.h"
|
||||
#include "../gui.h"
|
||||
|
@ -413,6 +414,8 @@ bool VulkanContext::InitDevice()
|
|||
}
|
||||
else if (!strcmp(property.extensionName, "VK_KHR_portability_subset"))
|
||||
deviceExtensions.push_back("VK_KHR_portability_subset");
|
||||
else if (!strcmp(property.extensionName, "VK_EXT_metal_objects"))
|
||||
deviceExtensions.push_back("VK_EXT_metal_objects");
|
||||
#ifdef VK_DEBUG
|
||||
else if (!strcmp(property.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME))
|
||||
{
|
||||
|
@ -514,6 +517,8 @@ bool VulkanContext::InitDevice()
|
|||
+ std::to_string(props.driverVersion / 10000) + "."
|
||||
+ std::to_string((props.driverVersion % 10000) / 100) + "."
|
||||
+ std::to_string(props.driverVersion % 100);
|
||||
|
||||
initVideoRouting();
|
||||
#else
|
||||
driverVersion = std::to_string(VK_API_VERSION_MAJOR(props.driverVersion)) + "."
|
||||
+ std::to_string(VK_API_VERSION_MINOR(props.driverVersion)) + "."
|
||||
|
@ -538,6 +543,19 @@ bool VulkanContext::InitDevice()
|
|||
return false;
|
||||
}
|
||||
|
||||
void VulkanContext::initVideoRouting()
|
||||
{
|
||||
#if defined(VIDEO_ROUTING) && defined(TARGET_MAC)
|
||||
extern void os_VideoRoutingTermVk();
|
||||
extern void os_VideoRoutingInitSyphonWithVkDevice(const vk::UniqueDevice& device);
|
||||
os_VideoRoutingTermVk();
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
os_VideoRoutingInitSyphonWithVkDevice(device);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanContext::CreateSwapChain()
|
||||
{
|
||||
try
|
||||
|
@ -626,8 +644,8 @@ void VulkanContext::CreateSwapChain()
|
|||
if (surfaceCapabilities.maxImageCount != 0)
|
||||
imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount);
|
||||
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
#ifdef TEST_AUTOMATION
|
||||
// for final screenshot
|
||||
#if defined(TEST_AUTOMATION) || (defined(VIDEO_ROUTING) && defined(TARGET_MAC))
|
||||
// for final screenshot or Syphon
|
||||
usage |= vk::ImageUsageFlagBits::eTransferSrc;
|
||||
#endif
|
||||
vk::SwapchainCreateInfoKHR swapChainCreateInfo(vk::SwapchainCreateFlagsKHR(), GetSurface(), imageCount, colorFormat, vk::ColorSpaceKHR::eSrgbNonlinear,
|
||||
|
@ -967,6 +985,8 @@ void VulkanContext::PresentFrame(vk::Image image, vk::ImageView imageView, const
|
|||
DrawOverlay(settings.display.uiScale, config::FloatVMUs, true);
|
||||
renderer->DrawOSD(false);
|
||||
EndFrame(overlayCmdBuffer);
|
||||
static_cast<BaseVulkanRenderer*>(renderer)->RenderVideoRouting();
|
||||
|
||||
} catch (const InvalidVulkanContext& err) {
|
||||
}
|
||||
}
|
||||
|
@ -1020,6 +1040,10 @@ void VulkanContext::term()
|
|||
renderCompleteSemaphores.clear();
|
||||
drawFences.clear();
|
||||
allocator.Term();
|
||||
#if defined(VIDEO_ROUTING) && defined(TARGET_MAC)
|
||||
extern void os_VideoRoutingTermVk();
|
||||
os_VideoRoutingTermVk();
|
||||
#endif
|
||||
#ifndef USE_SDL
|
||||
surface.reset();
|
||||
#else
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
void Present() noexcept;
|
||||
void PresentFrame(vk::Image image, vk::ImageView imageView, const vk::Extent2D& extent, float aspectRatio) noexcept;
|
||||
void PresentLastFrame();
|
||||
void initVideoRouting() override;
|
||||
|
||||
vk::PhysicalDevice GetPhysicalDevice() const { return physicalDevice; }
|
||||
vk::Device GetDevice() const { return *device; }
|
||||
|
@ -69,6 +70,7 @@ public:
|
|||
vk::CommandBuffer GetCurrentCommandBuffer() const { return *commandBuffers[GetCurrentImageIndex()]; }
|
||||
vk::DescriptorPool GetDescriptorPool() const { return *descriptorPool; }
|
||||
vk::Extent2D GetViewPort() const { return { (u32)settings.display.width, (u32)settings.display.height }; }
|
||||
vk::SwapchainKHR GetSwapChain() const { return *swapChain; }
|
||||
u32 GetSwapChainSize() const { return (u32)imageViews.size(); }
|
||||
int GetCurrentImageIndex() const { return currentImage; }
|
||||
void WaitIdle() const;
|
||||
|
|
|
@ -232,6 +232,25 @@ public:
|
|||
fbCommandPool.EndFrame();
|
||||
framebufferRendered = true;
|
||||
}
|
||||
|
||||
void RenderVideoRouting()
|
||||
{
|
||||
#if defined(VIDEO_ROUTING) && defined(TARGET_MAC)
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
auto device = GetContext()->GetDevice();
|
||||
auto srcImage = device.getSwapchainImagesKHR(GetContext()->GetSwapChain())[GetContext()->GetCurrentImageIndex()];
|
||||
auto graphicsQueue = device.getQueue(GetContext()->GetGraphicsQueueFamilyIndex(), 0);
|
||||
|
||||
int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width);
|
||||
int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height);
|
||||
|
||||
extern void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h);
|
||||
os_VideoRoutingPublishFrameTexture(device, srcImage, graphicsQueue, 0, 0, targetWidth, targetHeight);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
BaseVulkanRenderer() : viewport(640, 480) {}
|
||||
|
|
|
@ -1002,3 +1002,65 @@ void os_RunInstance(int argc, const char *argv[])
|
|||
WARN_LOG(BOOT, "Cannot launch Flycast instance: error %d", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VIDEO_ROUTING
|
||||
#include "SpoutSender.h"
|
||||
#include "SpoutDX.h"
|
||||
|
||||
static SpoutSender* spoutSender;
|
||||
static spoutDX* spoutDXSender;
|
||||
|
||||
void os_VideoRoutingInitSpout()
|
||||
{
|
||||
if (spoutSender == nullptr)
|
||||
{
|
||||
spoutSender = new SpoutSender();
|
||||
}
|
||||
|
||||
int boardID = cfgLoadInt("naomi", "BoardId", 0);
|
||||
char buf[32] = {0};
|
||||
vsnprintf(buf, sizeof(buf), (boardID == 0 ? "Flycast - Video Content" : "Flycast - Video Content - %d"), std::va_list(&boardID));
|
||||
spoutSender->SetSenderName(buf);
|
||||
}
|
||||
|
||||
void os_VideoRoutingPublishFrameTexture(GLuint texID, GLuint texTarget, float w, float h)
|
||||
{
|
||||
spoutSender->SendTexture(texID, texTarget, w, h, true);
|
||||
}
|
||||
|
||||
void os_VideoRoutingTermGL()
|
||||
{
|
||||
if (spoutSender)
|
||||
{
|
||||
spoutSender->ReleaseSender();
|
||||
spoutSender = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void os_VideoRoutingInitSpoutDXWithDevice(ID3D11Device* pDevice)
|
||||
{
|
||||
if (spoutDXSender == nullptr)
|
||||
{
|
||||
spoutDXSender = new spoutDX();
|
||||
}
|
||||
spoutDXSender->OpenDirectX11(pDevice);
|
||||
int boardID = cfgLoadInt("naomi", "BoardId", 0);
|
||||
char buf[32] = {0};
|
||||
vsnprintf(buf, sizeof(buf), (boardID == 0 ? "Flycast - Video Content" : "Flycast - Video Content - %d"), std::va_list(&boardID));
|
||||
spoutDXSender->SetSenderName(buf);
|
||||
}
|
||||
|
||||
void os_VideoRoutingPublishFrameTexture(ID3D11Texture2D* pTexture)
|
||||
{
|
||||
spoutDXSender->SendTexture(pTexture);
|
||||
}
|
||||
|
||||
void os_VideoRoutingTermDX()
|
||||
{
|
||||
if (spoutDXSender)
|
||||
{
|
||||
spoutDXSender->ReleaseSender();
|
||||
spoutDXSender = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
virtual std::string getDriverName() = 0;
|
||||
virtual std::string getDriverVersion() = 0;
|
||||
virtual bool hasPerPixel() { return false; }
|
||||
virtual void initVideoRouting() {}
|
||||
|
||||
void setWindow(void *window, void *display = nullptr) {
|
||||
this->window = window;
|
||||
|
|
|
@ -56,6 +56,11 @@ void GLGraphicsContext::preTerm()
|
|||
#ifndef LIBRETRO
|
||||
imguiDriver.reset();
|
||||
#endif
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern void os_VideoRoutingTermGL();
|
||||
os_VideoRoutingTermGL();
|
||||
#endif
|
||||
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -116,10 +116,30 @@ bool SDLGLGraphicsContext::init()
|
|||
glGetError();
|
||||
eglGetError();
|
||||
#endif
|
||||
|
||||
|
||||
initVideoRouting();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLGLGraphicsContext::initVideoRouting()
|
||||
{
|
||||
#ifdef VIDEO_ROUTING
|
||||
extern void os_VideoRoutingTermGL();
|
||||
os_VideoRoutingTermGL();
|
||||
if (config::VideoRouting)
|
||||
{
|
||||
#ifdef TARGET_MAC
|
||||
extern void os_VideoRoutingInitSyphonWithGLContext(void* glContext);
|
||||
os_VideoRoutingInitSyphonWithGLContext(glcontext);
|
||||
#elif defined(_WIN32)
|
||||
extern void os_VideoRoutingInitSpout();
|
||||
os_VideoRoutingInitSpout();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDLGLGraphicsContext::swap()
|
||||
{
|
||||
do_swap_automation();
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
bool init();
|
||||
void term() override;
|
||||
void swap();
|
||||
void initVideoRouting() override;
|
||||
|
||||
private:
|
||||
SDL_GLContext glcontext = nullptr;
|
||||
|
|
|
@ -33,7 +33,8 @@ int darw_printf(const char* text, ...)
|
|||
va_end(args);
|
||||
|
||||
NSString* log = [NSString stringWithCString:temp encoding: NSUTF8StringEncoding];
|
||||
if (getenv("TERM") == NULL) //Xcode console does not support colors
|
||||
static bool isXcode = [[[NSProcessInfo processInfo] environment][@"OS_ACTIVITY_DT_MODE"] boolValue];
|
||||
if (isXcode) // Xcode console does not support colors
|
||||
{
|
||||
log = [log stringByReplacingOccurrencesOfString:@"\x1b[0m" withString:@""];
|
||||
log = [log stringByReplacingOccurrencesOfString:@"\x1b[92m" withString:@"ℹ️ "];
|
||||
|
@ -202,3 +203,59 @@ void os_RunInstance(int argc, const char *argv[])
|
|||
die("execv failed");
|
||||
}
|
||||
}
|
||||
|
||||
#import <Syphon/Syphon.h>
|
||||
#import <cfg/cfg.h>
|
||||
#include "rend/vulkan/vulkan.h"
|
||||
static SyphonOpenGLServer* syphonGLServer;
|
||||
static SyphonMetalServer* syphonMtlServer;
|
||||
|
||||
void os_VideoRoutingInitSyphonWithGLContext(void* glContext)
|
||||
{
|
||||
int boardID = cfgLoadInt("naomi", "BoardId", 0);
|
||||
syphonGLServer = [[SyphonOpenGLServer alloc] initWithName:[NSString stringWithFormat:(boardID == 0 ? @"Video Content" : @"Video Content - %d"), boardID] context:[(__bridge NSOpenGLContext*)glContext CGLContextObj] options:nil];
|
||||
}
|
||||
|
||||
void os_VideoRoutingPublishFrameTexture(GLuint texID, GLuint texTarget, float w, float h)
|
||||
{
|
||||
CGLLockContext([syphonGLServer context]);
|
||||
[syphonGLServer publishFrameTexture:texID textureTarget:texTarget imageRegion:NSMakeRect(0, 0, w, h) textureDimensions:NSMakeSize(w, h) flipped:NO];
|
||||
CGLUnlockContext([syphonGLServer context]);
|
||||
}
|
||||
|
||||
void os_VideoRoutingTermGL()
|
||||
{
|
||||
[syphonGLServer stop];
|
||||
[syphonGLServer release];
|
||||
syphonGLServer = NULL;
|
||||
}
|
||||
|
||||
void os_VideoRoutingInitSyphonWithVkDevice(const vk::UniqueDevice& device)
|
||||
{
|
||||
vk::ExportMetalDeviceInfoEXT deviceInfo;
|
||||
auto objectsInfo = vk::ExportMetalObjectsInfoEXT(&deviceInfo);
|
||||
device->exportMetalObjectsEXT(&objectsInfo);
|
||||
|
||||
int boardID = cfgLoadInt("naomi", "BoardId", 0);
|
||||
syphonMtlServer = [[SyphonMetalServer alloc] initWithName:[NSString stringWithFormat:(boardID == 0 ? @"Video Content" : @"Video Content - %d"), boardID] device:deviceInfo.mtlDevice options:nil];
|
||||
}
|
||||
|
||||
void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h)
|
||||
{
|
||||
auto textureInfo = vk::ExportMetalTextureInfoEXT(image);
|
||||
auto commandInfo = vk::ExportMetalCommandQueueInfoEXT(queue);
|
||||
commandInfo.pNext = &textureInfo;
|
||||
auto objectsInfo = vk::ExportMetalObjectsInfoEXT(&commandInfo);
|
||||
device.exportMetalObjectsEXT(&objectsInfo);
|
||||
|
||||
auto commandBuffer = [commandInfo.mtlCommandQueue commandBufferWithUnretainedReferences];
|
||||
[syphonMtlServer publishFrameTexture:textureInfo.mtlTexture onCommandBuffer:commandBuffer imageRegion:NSMakeRect(x, y, w, h) flipped:YES];
|
||||
[commandBuffer commit];
|
||||
}
|
||||
|
||||
void os_VideoRoutingTermVk()
|
||||
{
|
||||
[syphonMtlServer stop];
|
||||
[syphonMtlServer release];
|
||||
syphonMtlServer = NULL;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue