mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-26 13:29:31 +08:00
[feat] add OpenGL NV12 underlay rendering for Slint on Linux
This commit is contained in:
@@ -36,7 +36,6 @@
|
|||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
|
|
||||||
#include "platform/tray/win_tray.h"
|
#include "platform/tray/win_tray.h"
|
||||||
#include "platform/windows_opengl_video_renderer.h"
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#include "platform/metal_video_renderer.h"
|
#include "platform/metal_video_renderer.h"
|
||||||
#include "platform/tray/mac_tray.h"
|
#include "platform/tray/mac_tray.h"
|
||||||
@@ -49,6 +48,9 @@
|
|||||||
|
|
||||||
#include "platform/tray/linux_tray.h"
|
#include "platform/tray/linux_tray.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
|
#include "platform/opengl_video_renderer.h"
|
||||||
|
#endif
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "server_window_state.h"
|
#include "server_window_state.h"
|
||||||
#include "ui/ui_localization.h"
|
#include "ui/ui_localization.h"
|
||||||
@@ -62,6 +64,7 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
#if !defined(__APPLE__)
|
#if !defined(__APPLE__)
|
||||||
constexpr GLint kGlClampToEdge = 0x812F;
|
constexpr GLint kGlClampToEdge = 0x812F;
|
||||||
|
constexpr float kStreamWindowCornerRadius = 12.0f;
|
||||||
#else
|
#else
|
||||||
constexpr int kMetalAttachmentAttemptLimit = 30;
|
constexpr int kMetalAttachmentAttemptLimit = 30;
|
||||||
#endif
|
#endif
|
||||||
@@ -1114,9 +1117,8 @@ void GuiApplication::InitializeModules() {
|
|||||||
if (modules_inited_) {
|
if (modules_inited_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
windows_opengl_video_renderer_ =
|
opengl_video_renderer_ = std::make_unique<OpenGlVideoRenderer>();
|
||||||
std::make_unique<WindowsOpenGlVideoRenderer>();
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
// Metal setup can log device, shader, or pipeline failures. Initialize it
|
// Metal setup can log device, shader, or pipeline failures. Initialize it
|
||||||
// here, after Run() has selected the application's log directory, rather
|
// here, after Run() has selected the application's log directory, rather
|
||||||
@@ -2351,9 +2353,9 @@ void GuiApplication::SyncStreamWindow() {
|
|||||||
}
|
}
|
||||||
if (!has_sessions) {
|
if (!has_sessions) {
|
||||||
SetStreamKeyboardFocus(false);
|
SetStreamKeyboardFocus(false);
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_) {
|
if (opengl_video_renderer_) {
|
||||||
windows_opengl_video_renderer_->SetSelectedStream({});
|
opengl_video_renderer_->SetSelectedStream({});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
@@ -2643,10 +2645,10 @@ void GuiApplication::SyncStreamVideoFrame() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_ &&
|
if (opengl_video_renderer_ &&
|
||||||
windows_opengl_video_renderer_->IsReady()) {
|
opengl_video_renderer_->IsReady()) {
|
||||||
windows_opengl_video_renderer_->SetSelectedStream(props->remote_id_);
|
opengl_video_renderer_->SetSelectedStream(props->remote_id_);
|
||||||
SubmitCachedFrameToOpenGl(props);
|
SubmitCachedFrameToOpenGl(props);
|
||||||
if (!(*ui_->stream)->get_native_video_enabled()) {
|
if (!(*ui_->stream)->get_native_video_enabled()) {
|
||||||
(*ui_->stream)->set_native_video_enabled(true);
|
(*ui_->stream)->set_native_video_enabled(true);
|
||||||
@@ -2854,10 +2856,10 @@ void GuiApplication::ConfigureStreamVideoRenderer() {
|
|||||||
glBindTexture(GL_TEXTURE_2D,
|
glBindTexture(GL_TEXTURE_2D,
|
||||||
static_cast<GLuint>(previous_texture));
|
static_cast<GLuint>(previous_texture));
|
||||||
ui_->video_gl_texture = texture;
|
ui_->video_gl_texture = texture;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_ &&
|
if (opengl_video_renderer_ &&
|
||||||
windows_opengl_video_renderer_->Setup()) {
|
opengl_video_renderer_->Setup()) {
|
||||||
windows_opengl_video_renderer_->SetSelectedStream(
|
opengl_video_renderer_->SetSelectedStream(
|
||||||
focused_remote_id_);
|
focused_remote_id_);
|
||||||
video_frame_dirty_.store(true, std::memory_order_release);
|
video_frame_dirty_.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
@@ -2865,10 +2867,10 @@ void GuiApplication::ConfigureStreamVideoRenderer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (state == slint::RenderingState::BeforeRendering &&
|
if (state == slint::RenderingState::BeforeRendering &&
|
||||||
windows_opengl_video_renderer_ &&
|
opengl_video_renderer_ &&
|
||||||
windows_opengl_video_renderer_->IsReady() && ui_->stream &&
|
opengl_video_renderer_->IsReady() && ui_->stream &&
|
||||||
(*ui_->stream)->get_native_video_enabled()) {
|
(*ui_->stream)->get_native_video_enabled()) {
|
||||||
const auto window_size = (*ui_->stream)->window().size();
|
const auto window_size = (*ui_->stream)->window().size();
|
||||||
const float scale_factor =
|
const float scale_factor =
|
||||||
@@ -2877,9 +2879,18 @@ void GuiApplication::ConfigureStreamVideoRenderer() {
|
|||||||
!fullscreen_button_pressed_ && ui_->tab_ids.size() > 1
|
!fullscreen_button_pressed_ && ui_->tab_ids.size() > 1
|
||||||
? static_cast<int>(std::lround(30.0f * scale_factor))
|
? static_cast<int>(std::lround(30.0f * scale_factor))
|
||||||
: 0;
|
: 0;
|
||||||
windows_opengl_video_renderer_->RenderLatest(
|
const bool rounded_window =
|
||||||
|
(*ui_->stream)->get_custom_titlebar() &&
|
||||||
|
!fullscreen_button_pressed_ &&
|
||||||
|
!(*ui_->stream)->window().is_maximized();
|
||||||
|
const int corner_radius =
|
||||||
|
rounded_window
|
||||||
|
? static_cast<int>(std::lround(
|
||||||
|
kStreamWindowCornerRadius * scale_factor))
|
||||||
|
: 0;
|
||||||
|
opengl_video_renderer_->RenderLatest(
|
||||||
focused_remote_id_, static_cast<int>(window_size.width),
|
focused_remote_id_, static_cast<int>(window_size.width),
|
||||||
static_cast<int>(window_size.height), top_inset);
|
static_cast<int>(window_size.height), top_inset, corner_radius);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -2914,9 +2925,9 @@ void GuiApplication::ConfigureStreamVideoRenderer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == slint::RenderingState::RenderingTeardown) {
|
if (state == slint::RenderingState::RenderingTeardown) {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_) {
|
if (opengl_video_renderer_) {
|
||||||
windows_opengl_video_renderer_->Teardown();
|
opengl_video_renderer_->Teardown();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ui_->video_gl_texture != 0) {
|
if (ui_->video_gl_texture != 0) {
|
||||||
@@ -2933,19 +2944,19 @@ void GuiApplication::ConfigureStreamVideoRenderer() {
|
|||||||
});
|
});
|
||||||
if (error.has_value()) {
|
if (error.has_value()) {
|
||||||
LOG_WARN("Slint OpenGL video renderer unavailable, using pixel buffers");
|
LOG_WARN("Slint OpenGL video renderer unavailable, using pixel buffers");
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
} else if (windows_opengl_video_renderer_) {
|
} else if (opengl_video_renderer_) {
|
||||||
windows_opengl_video_renderer_->SetSelectedStream(focused_remote_id_);
|
opengl_video_renderer_->SetSelectedStream(focused_remote_id_);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
void GuiApplication::SubmitCachedFrameToOpenGl(
|
void GuiApplication::SubmitCachedFrameToOpenGl(
|
||||||
const std::shared_ptr<RemoteSession>& session) {
|
const std::shared_ptr<RemoteSession>& session) {
|
||||||
if (!session || !windows_opengl_video_renderer_ ||
|
if (!session || !opengl_video_renderer_ ||
|
||||||
!windows_opengl_video_renderer_->IsReady() ||
|
!opengl_video_renderer_->IsReady() ||
|
||||||
session->connection_status_.load() != ConnectionStatus::Connected) {
|
session->connection_status_.load() != ConnectionStatus::Connected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2968,10 +2979,10 @@ void GuiApplication::SubmitCachedFrameToOpenGl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cached_frame && cached_width > 0 && cached_height > 0 &&
|
if (cached_frame && cached_width > 0 && cached_height > 0 &&
|
||||||
windows_opengl_video_renderer_->SubmitCachedNv12(
|
opengl_video_renderer_->SubmitCachedNv12(
|
||||||
session->remote_id_, cached_frame->data(), cached_frame->size(),
|
session->remote_id_, cached_frame->data(), cached_frame->size(),
|
||||||
cached_width, cached_height) ==
|
cached_width, cached_height) ==
|
||||||
WindowsOpenGlVideoRenderer::SubmitResult::submitted) {
|
OpenGlVideoRenderer::SubmitResult::submitted) {
|
||||||
video_frame_dirty_.store(true, std::memory_order_release);
|
video_frame_dirty_.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3321,7 +3332,7 @@ void GuiApplication::SelectStreamTab(int index) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::string selected_remote_id = ui_->tab_ids[index];
|
const std::string selected_remote_id = ui_->tab_ids[index];
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
const bool selection_changed = focused_remote_id_ != selected_remote_id;
|
const bool selection_changed = focused_remote_id_ != selected_remote_id;
|
||||||
bool renderer_selection_changed = false;
|
bool renderer_selection_changed = false;
|
||||||
#endif
|
#endif
|
||||||
@@ -3331,10 +3342,10 @@ void GuiApplication::SelectStreamTab(int index) {
|
|||||||
}
|
}
|
||||||
focused_remote_id_ = selected_remote_id;
|
focused_remote_id_ = selected_remote_id;
|
||||||
controlled_remote_id_ = selected_remote_id;
|
controlled_remote_id_ = selected_remote_id;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_) {
|
if (opengl_video_renderer_) {
|
||||||
renderer_selection_changed =
|
renderer_selection_changed =
|
||||||
windows_opengl_video_renderer_->SetSelectedStream(selected_remote_id);
|
opengl_video_renderer_->SetSelectedStream(selected_remote_id);
|
||||||
}
|
}
|
||||||
if (renderer_selection_changed) {
|
if (renderer_selection_changed) {
|
||||||
video_frame_dirty_.store(true, std::memory_order_release);
|
video_frame_dirty_.store(true, std::memory_order_release);
|
||||||
@@ -3371,7 +3382,7 @@ void GuiApplication::SelectStreamTab(int index) {
|
|||||||
selected_session->connection_status_.load() ==
|
selected_session->connection_status_.load() ==
|
||||||
ConnectionStatus::Connected;
|
ConnectionStatus::Connected;
|
||||||
}
|
}
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (renderer_selection_changed) {
|
if (renderer_selection_changed) {
|
||||||
SubmitCachedFrameToOpenGl(selected_session);
|
SubmitCachedFrameToOpenGl(selected_session);
|
||||||
}
|
}
|
||||||
@@ -3427,9 +3438,9 @@ void GuiApplication::CloseStreamTab(const std::string& remote_id) {
|
|||||||
if (focused_remote_id_ == remote_id) {
|
if (focused_remote_id_ == remote_id) {
|
||||||
focused_remote_id_.clear();
|
focused_remote_id_.clear();
|
||||||
controlled_remote_id_.clear();
|
controlled_remote_id_.clear();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_ &&
|
if (opengl_video_renderer_ &&
|
||||||
windows_opengl_video_renderer_->SetSelectedStream({})) {
|
opengl_video_renderer_->SetSelectedStream({})) {
|
||||||
video_frame_dirty_.store(true, std::memory_order_release);
|
video_frame_dirty_.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
@@ -3631,9 +3642,9 @@ void GuiApplication::Cleanup() {
|
|||||||
WaitForThumbnailSaveTasks();
|
WaitForThumbnailSaveTasks();
|
||||||
devices_.DestroyAudioOutput();
|
devices_.DestroyAudioOutput();
|
||||||
if (ui_->stream) {
|
if (ui_->stream) {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
if (windows_opengl_video_renderer_) {
|
if (opengl_video_renderer_) {
|
||||||
windows_opengl_video_renderer_->SetSelectedStream({});
|
opengl_video_renderer_->SetSelectedStream({});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ private:
|
|||||||
void SyncStreamVideoFrame();
|
void SyncStreamVideoFrame();
|
||||||
void ScheduleNextVideoFrame();
|
void ScheduleNextVideoFrame();
|
||||||
void ConfigureStreamVideoRenderer();
|
void ConfigureStreamVideoRenderer();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
void SubmitCachedFrameToOpenGl(
|
void SubmitCachedFrameToOpenGl(
|
||||||
const std::shared_ptr<RemoteSession>& session);
|
const std::shared_ptr<RemoteSession>& session);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+200
-86
@@ -1,13 +1,20 @@
|
|||||||
#include "platform/windows_opengl_video_renderer.h"
|
#include "platform/opengl_video_renderer.h"
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32) && !defined(__linux__)
|
||||||
#error "WindowsOpenGlVideoRenderer is only available on Windows"
|
#error "OpenGlVideoRenderer is only available on Windows and Linux"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <GL/gl.h>
|
#include <GL/gl.h>
|
||||||
#include <GL/glext.h>
|
#include <GL/glext.h>
|
||||||
|
#if defined(__linux__)
|
||||||
|
#include <GL/glx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -49,6 +56,7 @@ struct SharedFrameState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename T> bool LoadOpenGlFunction(T *function, const char *name) {
|
template <typename T> bool LoadOpenGlFunction(T *function, const char *name) {
|
||||||
|
#if defined(_WIN32)
|
||||||
PROC address = wglGetProcAddress(name);
|
PROC address = wglGetProcAddress(name);
|
||||||
if (address == nullptr || address == reinterpret_cast<PROC>(1) ||
|
if (address == nullptr || address == reinterpret_cast<PROC>(1) ||
|
||||||
address == reinterpret_cast<PROC>(2) ||
|
address == reinterpret_cast<PROC>(2) ||
|
||||||
@@ -58,6 +66,14 @@ template <typename T> bool LoadOpenGlFunction(T *function, const char *name) {
|
|||||||
address = module ? GetProcAddress(module, name) : nullptr;
|
address = module ? GetProcAddress(module, name) : nullptr;
|
||||||
}
|
}
|
||||||
*function = reinterpret_cast<T>(address);
|
*function = reinterpret_cast<T>(address);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
void *address = dlsym(RTLD_DEFAULT, name);
|
||||||
|
if (address == nullptr) {
|
||||||
|
address = reinterpret_cast<void *>(
|
||||||
|
glXGetProcAddressARB(reinterpret_cast<const GLubyte *>(name)));
|
||||||
|
}
|
||||||
|
*function = reinterpret_cast<T>(address);
|
||||||
|
#endif
|
||||||
return *function != nullptr;
|
return *function != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +101,10 @@ struct OpenGlFunctions {
|
|||||||
PFNGLGETVERTEXATTRIBPOINTERVPROC get_vertex_attrib_pointer_v = nullptr;
|
PFNGLGETVERTEXATTRIBPOINTERVPROC get_vertex_attrib_pointer_v = nullptr;
|
||||||
PFNGLLINKPROGRAMPROC link_program = nullptr;
|
PFNGLLINKPROGRAMPROC link_program = nullptr;
|
||||||
PFNGLSHADERSOURCEPROC shader_source = nullptr;
|
PFNGLSHADERSOURCEPROC shader_source = nullptr;
|
||||||
|
PFNGLUNIFORM1FPROC uniform_1f = nullptr;
|
||||||
PFNGLUNIFORM1IPROC uniform_1i = nullptr;
|
PFNGLUNIFORM1IPROC uniform_1i = nullptr;
|
||||||
|
PFNGLUNIFORM2FPROC uniform_2f = nullptr;
|
||||||
|
PFNGLUNIFORM4FPROC uniform_4f = nullptr;
|
||||||
PFNGLUSEPROGRAMPROC use_program = nullptr;
|
PFNGLUSEPROGRAMPROC use_program = nullptr;
|
||||||
PFNGLVERTEXATTRIBPOINTERPROC vertex_attrib_pointer = nullptr;
|
PFNGLVERTEXATTRIBPOINTERPROC vertex_attrib_pointer = nullptr;
|
||||||
|
|
||||||
@@ -116,7 +135,10 @@ struct OpenGlFunctions {
|
|||||||
"glGetVertexAttribPointerv") &&
|
"glGetVertexAttribPointerv") &&
|
||||||
LoadOpenGlFunction(&link_program, "glLinkProgram") &&
|
LoadOpenGlFunction(&link_program, "glLinkProgram") &&
|
||||||
LoadOpenGlFunction(&shader_source, "glShaderSource") &&
|
LoadOpenGlFunction(&shader_source, "glShaderSource") &&
|
||||||
|
LoadOpenGlFunction(&uniform_1f, "glUniform1f") &&
|
||||||
LoadOpenGlFunction(&uniform_1i, "glUniform1i") &&
|
LoadOpenGlFunction(&uniform_1i, "glUniform1i") &&
|
||||||
|
LoadOpenGlFunction(&uniform_2f, "glUniform2f") &&
|
||||||
|
LoadOpenGlFunction(&uniform_4f, "glUniform4f") &&
|
||||||
LoadOpenGlFunction(&use_program, "glUseProgram") &&
|
LoadOpenGlFunction(&use_program, "glUseProgram") &&
|
||||||
LoadOpenGlFunction(&vertex_attrib_pointer, "glVertexAttribPointer");
|
LoadOpenGlFunction(&vertex_attrib_pointer, "glVertexAttribPointer");
|
||||||
}
|
}
|
||||||
@@ -205,7 +227,7 @@ void RestoreVertexAttrib(const OpenGlFunctions &gl, GLuint index,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
struct WindowsOpenGlVideoRenderer::Impl {
|
struct OpenGlVideoRenderer::Impl {
|
||||||
OpenGlFunctions gl;
|
OpenGlFunctions gl;
|
||||||
std::shared_ptr<SharedFrameState> frames =
|
std::shared_ptr<SharedFrameState> frames =
|
||||||
std::make_shared<SharedFrameState>();
|
std::make_shared<SharedFrameState>();
|
||||||
@@ -218,6 +240,10 @@ struct WindowsOpenGlVideoRenderer::Impl {
|
|||||||
GLint texcoord_location = -1;
|
GLint texcoord_location = -1;
|
||||||
GLint y_texture_location = -1;
|
GLint y_texture_location = -1;
|
||||||
GLint uv_texture_location = -1;
|
GLint uv_texture_location = -1;
|
||||||
|
GLint target_size_location = -1;
|
||||||
|
GLint video_rect_location = -1;
|
||||||
|
GLint corner_radius_location = -1;
|
||||||
|
GLint video_enabled_location = -1;
|
||||||
int texture_width = 0;
|
int texture_width = 0;
|
||||||
int texture_height = 0;
|
int texture_height = 0;
|
||||||
std::string uploaded_stream;
|
std::string uploaded_stream;
|
||||||
@@ -232,6 +258,10 @@ struct WindowsOpenGlVideoRenderer::Impl {
|
|||||||
texcoord_location = -1;
|
texcoord_location = -1;
|
||||||
y_texture_location = -1;
|
y_texture_location = -1;
|
||||||
uv_texture_location = -1;
|
uv_texture_location = -1;
|
||||||
|
target_size_location = -1;
|
||||||
|
video_rect_location = -1;
|
||||||
|
corner_radius_location = -1;
|
||||||
|
video_enabled_location = -1;
|
||||||
texture_width = 0;
|
texture_width = 0;
|
||||||
texture_height = 0;
|
texture_height = 0;
|
||||||
uploaded_stream.clear();
|
uploaded_stream.clear();
|
||||||
@@ -239,12 +269,12 @@ struct WindowsOpenGlVideoRenderer::Impl {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::WindowsOpenGlVideoRenderer()
|
OpenGlVideoRenderer::OpenGlVideoRenderer()
|
||||||
: impl_(std::make_unique<Impl>()) {}
|
: impl_(std::make_unique<Impl>()) {}
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::~WindowsOpenGlVideoRenderer() = default;
|
OpenGlVideoRenderer::~OpenGlVideoRenderer() = default;
|
||||||
|
|
||||||
bool WindowsOpenGlVideoRenderer::Setup() {
|
bool OpenGlVideoRenderer::Setup() {
|
||||||
if (IsReady()) {
|
if (IsReady()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -274,15 +304,41 @@ precision mediump float;
|
|||||||
#endif
|
#endif
|
||||||
uniform sampler2D y_texture;
|
uniform sampler2D y_texture;
|
||||||
uniform sampler2D uv_texture;
|
uniform sampler2D uv_texture;
|
||||||
|
uniform vec2 target_size;
|
||||||
|
uniform vec4 video_rect;
|
||||||
|
uniform float corner_radius;
|
||||||
|
uniform float video_enabled;
|
||||||
varying vec2 video_texcoord;
|
varying vec2 video_texcoord;
|
||||||
void main() {
|
void main() {
|
||||||
float y = 1.16438356 *
|
vec2 surface_point = video_texcoord * target_size;
|
||||||
(texture2D(y_texture, video_texcoord).r - 16.0 / 255.0);
|
vec3 rgb = vec3(0.0);
|
||||||
vec2 uv = texture2D(uv_texture, video_texcoord).ra - vec2(0.5, 0.5);
|
if (video_enabled > 0.5 &&
|
||||||
vec3 rgb = vec3(y + 1.59602678 * uv.y,
|
surface_point.x >= video_rect.x &&
|
||||||
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
surface_point.x <= video_rect.x + video_rect.z &&
|
||||||
y + 2.01723214 * uv.x);
|
surface_point.y >= video_rect.y &&
|
||||||
gl_FragColor = vec4(clamp(rgb, 0.0, 1.0), 1.0);
|
surface_point.y <= video_rect.y + video_rect.w) {
|
||||||
|
vec2 frame_texcoord =
|
||||||
|
(surface_point - video_rect.xy) / video_rect.zw;
|
||||||
|
float y = 1.16438356 *
|
||||||
|
(texture2D(y_texture, frame_texcoord).r - 16.0 / 255.0);
|
||||||
|
vec2 uv =
|
||||||
|
texture2D(uv_texture, frame_texcoord).ra - vec2(0.5, 0.5);
|
||||||
|
rgb = clamp(vec3(y + 1.59602678 * uv.y,
|
||||||
|
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
||||||
|
y + 2.01723214 * uv.x),
|
||||||
|
0.0, 1.0);
|
||||||
|
}
|
||||||
|
float coverage = 1.0;
|
||||||
|
if (corner_radius > 0.0) {
|
||||||
|
vec2 half_size = target_size * 0.5;
|
||||||
|
vec2 corner = abs(surface_point - half_size) -
|
||||||
|
(half_size - vec2(corner_radius));
|
||||||
|
float distance_to_edge =
|
||||||
|
length(max(corner, vec2(0.0))) +
|
||||||
|
min(max(corner.x, corner.y), 0.0) - corner_radius;
|
||||||
|
coverage = clamp(0.5 - distance_to_edge, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
gl_FragColor = vec4(rgb * coverage, coverage);
|
||||||
}
|
}
|
||||||
)glsl";
|
)glsl";
|
||||||
static constexpr char kDesktopVertexShader[] = R"glsl(
|
static constexpr char kDesktopVertexShader[] = R"glsl(
|
||||||
@@ -299,15 +355,41 @@ void main() {
|
|||||||
#version 110
|
#version 110
|
||||||
uniform sampler2D y_texture;
|
uniform sampler2D y_texture;
|
||||||
uniform sampler2D uv_texture;
|
uniform sampler2D uv_texture;
|
||||||
|
uniform vec2 target_size;
|
||||||
|
uniform vec4 video_rect;
|
||||||
|
uniform float corner_radius;
|
||||||
|
uniform float video_enabled;
|
||||||
varying vec2 video_texcoord;
|
varying vec2 video_texcoord;
|
||||||
void main() {
|
void main() {
|
||||||
float y = 1.16438356 *
|
vec2 surface_point = video_texcoord * target_size;
|
||||||
(texture2D(y_texture, video_texcoord).r - 16.0 / 255.0);
|
vec3 rgb = vec3(0.0);
|
||||||
vec2 uv = texture2D(uv_texture, video_texcoord).ra - vec2(0.5, 0.5);
|
if (video_enabled > 0.5 &&
|
||||||
vec3 rgb = vec3(y + 1.59602678 * uv.y,
|
surface_point.x >= video_rect.x &&
|
||||||
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
surface_point.x <= video_rect.x + video_rect.z &&
|
||||||
y + 2.01723214 * uv.x);
|
surface_point.y >= video_rect.y &&
|
||||||
gl_FragColor = vec4(clamp(rgb, 0.0, 1.0), 1.0);
|
surface_point.y <= video_rect.y + video_rect.w) {
|
||||||
|
vec2 frame_texcoord =
|
||||||
|
(surface_point - video_rect.xy) / video_rect.zw;
|
||||||
|
float y = 1.16438356 *
|
||||||
|
(texture2D(y_texture, frame_texcoord).r - 16.0 / 255.0);
|
||||||
|
vec2 uv =
|
||||||
|
texture2D(uv_texture, frame_texcoord).ra - vec2(0.5, 0.5);
|
||||||
|
rgb = clamp(vec3(y + 1.59602678 * uv.y,
|
||||||
|
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
||||||
|
y + 2.01723214 * uv.x),
|
||||||
|
0.0, 1.0);
|
||||||
|
}
|
||||||
|
float coverage = 1.0;
|
||||||
|
if (corner_radius > 0.0) {
|
||||||
|
vec2 half_size = target_size * 0.5;
|
||||||
|
vec2 corner = abs(surface_point - half_size) -
|
||||||
|
(half_size - vec2(corner_radius));
|
||||||
|
float distance_to_edge =
|
||||||
|
length(max(corner, vec2(0.0))) +
|
||||||
|
min(max(corner.x, corner.y), 0.0) - corner_radius;
|
||||||
|
coverage = clamp(0.5 - distance_to_edge, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
gl_FragColor = vec4(rgb * coverage, coverage);
|
||||||
}
|
}
|
||||||
)glsl";
|
)glsl";
|
||||||
|
|
||||||
@@ -363,8 +445,19 @@ void main() {
|
|||||||
impl_->gl.get_uniform_location(impl_->program, "y_texture");
|
impl_->gl.get_uniform_location(impl_->program, "y_texture");
|
||||||
impl_->uv_texture_location =
|
impl_->uv_texture_location =
|
||||||
impl_->gl.get_uniform_location(impl_->program, "uv_texture");
|
impl_->gl.get_uniform_location(impl_->program, "uv_texture");
|
||||||
|
impl_->target_size_location =
|
||||||
|
impl_->gl.get_uniform_location(impl_->program, "target_size");
|
||||||
|
impl_->video_rect_location =
|
||||||
|
impl_->gl.get_uniform_location(impl_->program, "video_rect");
|
||||||
|
impl_->corner_radius_location =
|
||||||
|
impl_->gl.get_uniform_location(impl_->program, "corner_radius");
|
||||||
|
impl_->video_enabled_location =
|
||||||
|
impl_->gl.get_uniform_location(impl_->program, "video_enabled");
|
||||||
if (impl_->position_location < 0 || impl_->texcoord_location < 0 ||
|
if (impl_->position_location < 0 || impl_->texcoord_location < 0 ||
|
||||||
impl_->y_texture_location < 0 || impl_->uv_texture_location < 0) {
|
impl_->y_texture_location < 0 || impl_->uv_texture_location < 0 ||
|
||||||
|
impl_->target_size_location < 0 || impl_->video_rect_location < 0 ||
|
||||||
|
impl_->corner_radius_location < 0 ||
|
||||||
|
impl_->video_enabled_location < 0) {
|
||||||
LOG_ERROR("OpenGL NV12 program is missing required shader bindings");
|
LOG_ERROR("OpenGL NV12 program is missing required shader bindings");
|
||||||
impl_->gl.delete_program(impl_->program);
|
impl_->gl.delete_program(impl_->program);
|
||||||
impl_->ResetGlHandles();
|
impl_->ResetGlHandles();
|
||||||
@@ -429,7 +522,7 @@ void main() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsOpenGlVideoRenderer::Teardown() {
|
void OpenGlVideoRenderer::Teardown() {
|
||||||
impl_->ready.store(false, std::memory_order_release);
|
impl_->ready.store(false, std::memory_order_release);
|
||||||
if (impl_->y_texture != 0)
|
if (impl_->y_texture != 0)
|
||||||
glDeleteTextures(1, &impl_->y_texture);
|
glDeleteTextures(1, &impl_->y_texture);
|
||||||
@@ -444,11 +537,11 @@ void WindowsOpenGlVideoRenderer::Teardown() {
|
|||||||
impl_->ResetGlHandles();
|
impl_->ResetGlHandles();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsOpenGlVideoRenderer::IsReady() const {
|
bool OpenGlVideoRenderer::IsReady() const {
|
||||||
return impl_->ready.load(std::memory_order_acquire);
|
return impl_->ready.load(std::memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsOpenGlVideoRenderer::SetSelectedStream(std::string remote_id) {
|
bool OpenGlVideoRenderer::SetSelectedStream(std::string remote_id) {
|
||||||
std::lock_guard lock(impl_->frames->mutex);
|
std::lock_guard lock(impl_->frames->mutex);
|
||||||
if (impl_->frames->selected_stream == remote_id) {
|
if (impl_->frames->selected_stream == remote_id) {
|
||||||
return false;
|
return false;
|
||||||
@@ -463,7 +556,7 @@ bool WindowsOpenGlVideoRenderer::SetSelectedStream(std::string remote_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowsOpenGlVideoRenderer::DiscardStream(std::string_view remote_id) {
|
void OpenGlVideoRenderer::DiscardStream(std::string_view remote_id) {
|
||||||
std::lock_guard lock(impl_->frames->mutex);
|
std::lock_guard lock(impl_->frames->mutex);
|
||||||
for (auto &slot : impl_->frames->slots) {
|
for (auto &slot : impl_->frames->slots) {
|
||||||
if (slot.remote_id == remote_id && slot.use != SlotUse::uploading) {
|
if (slot.remote_id == remote_id && slot.use != SlotUse::uploading) {
|
||||||
@@ -477,22 +570,22 @@ void WindowsOpenGlVideoRenderer::DiscardStream(std::string_view remote_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::SubmitResult
|
OpenGlVideoRenderer::SubmitResult
|
||||||
WindowsOpenGlVideoRenderer::SubmitNv12(std::string_view remote_id,
|
OpenGlVideoRenderer::SubmitNv12(std::string_view remote_id,
|
||||||
const uint8_t *data, size_t size,
|
const uint8_t *data, size_t size,
|
||||||
int width, int height) {
|
int width, int height) {
|
||||||
return SubmitNv12Internal(remote_id, data, size, width, height, true);
|
return SubmitNv12Internal(remote_id, data, size, width, height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::SubmitResult
|
OpenGlVideoRenderer::SubmitResult
|
||||||
WindowsOpenGlVideoRenderer::SubmitCachedNv12(std::string_view remote_id,
|
OpenGlVideoRenderer::SubmitCachedNv12(std::string_view remote_id,
|
||||||
const uint8_t *data, size_t size,
|
const uint8_t *data, size_t size,
|
||||||
int width, int height) {
|
int width, int height) {
|
||||||
return SubmitNv12Internal(remote_id, data, size, width, height, false);
|
return SubmitNv12Internal(remote_id, data, size, width, height, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::SubmitResult
|
OpenGlVideoRenderer::SubmitResult
|
||||||
WindowsOpenGlVideoRenderer::SubmitNv12Internal(std::string_view remote_id,
|
OpenGlVideoRenderer::SubmitNv12Internal(std::string_view remote_id,
|
||||||
const uint8_t *data, size_t size,
|
const uint8_t *data, size_t size,
|
||||||
int width, int height,
|
int width, int height,
|
||||||
bool replace_pending) {
|
bool replace_pending) {
|
||||||
@@ -562,10 +655,11 @@ WindowsOpenGlVideoRenderer::SubmitNv12Internal(std::string_view remote_id,
|
|||||||
return SubmitResult::submitted;
|
return SubmitResult::submitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer::RenderOutcome
|
OpenGlVideoRenderer::RenderOutcome
|
||||||
WindowsOpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
OpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
||||||
int target_width, int target_height,
|
int target_width, int target_height,
|
||||||
int top_inset_pixels) {
|
int top_inset_pixels,
|
||||||
|
int corner_radius_pixels) {
|
||||||
if (!IsReady() || target_width <= 0 || target_height <= 0) {
|
if (!IsReady() || target_width <= 0 || target_height <= 0) {
|
||||||
return {RenderResult::failed};
|
return {RenderResult::failed};
|
||||||
}
|
}
|
||||||
@@ -643,7 +737,10 @@ WindowsOpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
|||||||
glDisable(GL_STENCIL_TEST);
|
glDisable(GL_STENCIL_TEST);
|
||||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||||
glViewport(0, 0, target_width, target_height);
|
glViewport(0, 0, target_width, target_height);
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
// The Slint items above this underlay are transparent around the custom
|
||||||
|
// window corners. Keep those pixels transparent so the compositor can show
|
||||||
|
// the desktop instead of an opaque black framebuffer clear.
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
bool upload_succeeded = true;
|
bool upload_succeeded = true;
|
||||||
@@ -696,60 +793,77 @@ WindowsOpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderOutcome outcome{RenderResult::empty};
|
const int content_y = std::clamp(top_inset_pixels, 0, target_height);
|
||||||
if (upload_succeeded && impl_->uploaded_stream == remote_id &&
|
const int content_height = target_height - content_y;
|
||||||
impl_->texture_width > 0 && impl_->texture_height > 0) {
|
const bool has_video =
|
||||||
|
upload_succeeded && impl_->uploaded_stream == remote_id &&
|
||||||
|
impl_->texture_width > 0 && impl_->texture_height > 0 &&
|
||||||
|
content_height > 0;
|
||||||
|
int video_x = 0;
|
||||||
|
int video_y = content_y;
|
||||||
|
int video_width = target_width;
|
||||||
|
int video_height = content_height;
|
||||||
|
if (has_video) {
|
||||||
source_width = impl_->texture_width;
|
source_width = impl_->texture_width;
|
||||||
source_height = impl_->texture_height;
|
source_height = impl_->texture_height;
|
||||||
sequence = impl_->uploaded_sequence;
|
sequence = impl_->uploaded_sequence;
|
||||||
const int video_height = std::max(
|
const double source_aspect =
|
||||||
0, target_height - std::clamp(top_inset_pixels, 0, target_height));
|
static_cast<double>(source_width) / source_height;
|
||||||
if (video_height > 0) {
|
const double target_aspect =
|
||||||
const double source_aspect =
|
static_cast<double>(target_width) / content_height;
|
||||||
static_cast<double>(source_width) / source_height;
|
if (source_aspect > target_aspect) {
|
||||||
const double target_aspect =
|
video_height =
|
||||||
static_cast<double>(target_width) / video_height;
|
std::max(1, static_cast<int>(target_width / source_aspect + 0.5));
|
||||||
int viewport_width = target_width;
|
video_y = content_y + (content_height - video_height) / 2;
|
||||||
int viewport_height = video_height;
|
} else {
|
||||||
int viewport_x = 0;
|
video_width =
|
||||||
int viewport_y = 0;
|
std::max(1, static_cast<int>(content_height * source_aspect + 0.5));
|
||||||
if (source_aspect > target_aspect) {
|
video_x = (target_width - video_width) / 2;
|
||||||
viewport_height =
|
|
||||||
std::max(1, static_cast<int>(target_width / source_aspect + 0.5));
|
|
||||||
viewport_y = (video_height - viewport_height) / 2;
|
|
||||||
} else {
|
|
||||||
viewport_width =
|
|
||||||
std::max(1, static_cast<int>(video_height * source_aspect + 0.5));
|
|
||||||
viewport_x = (target_width - viewport_width) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
|
|
||||||
impl_->gl.use_program(impl_->program);
|
|
||||||
impl_->gl.active_texture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, impl_->y_texture);
|
|
||||||
impl_->gl.uniform_1i(impl_->y_texture_location, 0);
|
|
||||||
impl_->gl.active_texture(GL_TEXTURE1);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, impl_->uv_texture);
|
|
||||||
impl_->gl.uniform_1i(impl_->uv_texture_location, 1);
|
|
||||||
impl_->gl.bind_buffer(GL_ARRAY_BUFFER, impl_->vertex_buffer);
|
|
||||||
impl_->gl.vertex_attrib_pointer(
|
|
||||||
static_cast<GLuint>(impl_->position_location), 2, GL_FLOAT, GL_FALSE,
|
|
||||||
4 * static_cast<GLsizei>(sizeof(GLfloat)), nullptr);
|
|
||||||
impl_->gl.vertex_attrib_pointer(
|
|
||||||
static_cast<GLuint>(impl_->texcoord_location), 2, GL_FLOAT, GL_FALSE,
|
|
||||||
4 * static_cast<GLsizei>(sizeof(GLfloat)),
|
|
||||||
reinterpret_cast<const void *>(2 * sizeof(GLfloat)));
|
|
||||||
impl_->gl.enable_vertex_attrib_array(
|
|
||||||
static_cast<GLuint>(impl_->position_location));
|
|
||||||
impl_->gl.enable_vertex_attrib_array(
|
|
||||||
static_cast<GLuint>(impl_->texcoord_location));
|
|
||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
||||||
outcome = {glGetError() == GL_NO_ERROR ? RenderResult::rendered
|
|
||||||
: RenderResult::failed,
|
|
||||||
source_width, source_height, sequence};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (glGetError() != GL_NO_ERROR) {
|
||||||
|
}
|
||||||
|
impl_->gl.use_program(impl_->program);
|
||||||
|
impl_->gl.active_texture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, impl_->y_texture);
|
||||||
|
impl_->gl.uniform_1i(impl_->y_texture_location, 0);
|
||||||
|
impl_->gl.active_texture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, impl_->uv_texture);
|
||||||
|
impl_->gl.uniform_1i(impl_->uv_texture_location, 1);
|
||||||
|
impl_->gl.uniform_2f(impl_->target_size_location,
|
||||||
|
static_cast<GLfloat>(target_width),
|
||||||
|
static_cast<GLfloat>(target_height));
|
||||||
|
impl_->gl.uniform_4f(
|
||||||
|
impl_->video_rect_location, static_cast<GLfloat>(video_x),
|
||||||
|
static_cast<GLfloat>(video_y), static_cast<GLfloat>(video_width),
|
||||||
|
static_cast<GLfloat>(video_height));
|
||||||
|
impl_->gl.uniform_1f(
|
||||||
|
impl_->corner_radius_location,
|
||||||
|
static_cast<GLfloat>(std::clamp(
|
||||||
|
corner_radius_pixels, 0, std::min(target_width, target_height) / 2)));
|
||||||
|
impl_->gl.uniform_1f(impl_->video_enabled_location, has_video ? 1.0f : 0.0f);
|
||||||
|
impl_->gl.bind_buffer(GL_ARRAY_BUFFER, impl_->vertex_buffer);
|
||||||
|
impl_->gl.vertex_attrib_pointer(
|
||||||
|
static_cast<GLuint>(impl_->position_location), 2, GL_FLOAT, GL_FALSE,
|
||||||
|
4 * static_cast<GLsizei>(sizeof(GLfloat)), nullptr);
|
||||||
|
impl_->gl.vertex_attrib_pointer(
|
||||||
|
static_cast<GLuint>(impl_->texcoord_location), 2, GL_FLOAT, GL_FALSE,
|
||||||
|
4 * static_cast<GLsizei>(sizeof(GLfloat)),
|
||||||
|
reinterpret_cast<const void *>(2 * sizeof(GLfloat)));
|
||||||
|
impl_->gl.enable_vertex_attrib_array(
|
||||||
|
static_cast<GLuint>(impl_->position_location));
|
||||||
|
impl_->gl.enable_vertex_attrib_array(
|
||||||
|
static_cast<GLuint>(impl_->texcoord_location));
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
const GLenum draw_error = glGetError();
|
||||||
|
RenderOutcome outcome{
|
||||||
|
!upload_succeeded || draw_error != GL_NO_ERROR
|
||||||
|
? RenderResult::failed
|
||||||
|
: has_video ? RenderResult::rendered : RenderResult::empty,
|
||||||
|
has_video ? source_width : 0, has_video ? source_height : 0,
|
||||||
|
has_video ? sequence : 0};
|
||||||
|
|
||||||
RestoreVertexAttrib(impl_->gl, static_cast<GLuint>(impl_->position_location),
|
RestoreVertexAttrib(impl_->gl, static_cast<GLuint>(impl_->position_location),
|
||||||
position_state);
|
position_state);
|
||||||
RestoreVertexAttrib(impl_->gl, static_cast<GLuint>(impl_->texcoord_location),
|
RestoreVertexAttrib(impl_->gl, static_cast<GLuint>(impl_->texcoord_location),
|
||||||
@@ -786,7 +900,7 @@ WindowsOpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
|||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowsOpenGlVideoRenderer::CopyLatestNv12(
|
bool OpenGlVideoRenderer::CopyLatestNv12(
|
||||||
std::string_view remote_id, std::vector<unsigned char> *output, int *width,
|
std::string_view remote_id, std::vector<unsigned char> *output, int *width,
|
||||||
int *height) const {
|
int *height) const {
|
||||||
if (!output || !width || !height) {
|
if (!output || !width || !height) {
|
||||||
+18
-16
@@ -1,5 +1,5 @@
|
|||||||
#ifndef CROSSDESK_GUI_PLATFORM_WINDOWS_OPENGL_VIDEO_RENDERER_H_
|
#ifndef CROSSDESK_GUI_PLATFORM_OPENGL_VIDEO_RENDERER_H_
|
||||||
#define CROSSDESK_GUI_PLATFORM_WINDOWS_OPENGL_VIDEO_RENDERER_H_
|
#define CROSSDESK_GUI_PLATFORM_OPENGL_VIDEO_RENDERER_H_
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -10,11 +10,12 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
// Windows NV12 underlay for Slint's FemtoVG OpenGL renderer. MiniRTC submits
|
// NV12 underlay for Slint's FemtoVG OpenGL renderer on Windows and Linux.
|
||||||
// tightly packed CPU NV12 frames from its decode callback thread. The Slint UI
|
// MiniRTC submits tightly packed CPU NV12 frames from its decode callback
|
||||||
// thread uploads the newest frame as Y and UV textures and performs color
|
// thread. The Slint UI thread uploads the newest frame as Y and UV textures and
|
||||||
// conversion and scaling in a fragment shader before Slint draws its overlay.
|
// performs color conversion and scaling in a fragment shader before Slint
|
||||||
class WindowsOpenGlVideoRenderer {
|
// draws its overlay.
|
||||||
|
class OpenGlVideoRenderer {
|
||||||
public:
|
public:
|
||||||
enum class SubmitResult {
|
enum class SubmitResult {
|
||||||
submitted,
|
submitted,
|
||||||
@@ -37,12 +38,11 @@ public:
|
|||||||
uint64_t sequence = 0;
|
uint64_t sequence = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer();
|
OpenGlVideoRenderer();
|
||||||
~WindowsOpenGlVideoRenderer();
|
~OpenGlVideoRenderer();
|
||||||
|
|
||||||
WindowsOpenGlVideoRenderer(const WindowsOpenGlVideoRenderer &) = delete;
|
OpenGlVideoRenderer(const OpenGlVideoRenderer &) = delete;
|
||||||
WindowsOpenGlVideoRenderer &
|
OpenGlVideoRenderer &operator=(const OpenGlVideoRenderer &) = delete;
|
||||||
operator=(const WindowsOpenGlVideoRenderer &) = delete;
|
|
||||||
|
|
||||||
// These methods must run while Slint's OpenGL context is current, from the
|
// These methods must run while Slint's OpenGL context is current, from the
|
||||||
// RenderingSetup and RenderingTeardown notifier states respectively.
|
// RenderingSetup and RenderingTeardown notifier states respectively.
|
||||||
@@ -61,10 +61,12 @@ public:
|
|||||||
SubmitResult SubmitCachedNv12(std::string_view remote_id, const uint8_t *data,
|
SubmitResult SubmitCachedNv12(std::string_view remote_id, const uint8_t *data,
|
||||||
size_t size, int width, int height);
|
size_t size, int width, int height);
|
||||||
|
|
||||||
// Draws below Slint while its OpenGL context is current. target dimensions
|
// Draws below Slint while its OpenGL context is current. All dimensions are
|
||||||
// and top_inset_pixels are physical pixels in the window client area.
|
// physical pixels in the window client area. corner_radius_pixels clips the
|
||||||
|
// underlay to Slint's custom rounded window surface.
|
||||||
RenderOutcome RenderLatest(std::string_view remote_id, int target_width,
|
RenderOutcome RenderLatest(std::string_view remote_id, int target_width,
|
||||||
int target_height, int top_inset_pixels);
|
int target_height, int top_inset_pixels,
|
||||||
|
int corner_radius_pixels);
|
||||||
|
|
||||||
// Used during disconnect cleanup so thumbnail generation does not require a
|
// Used during disconnect cleanup so thumbnail generation does not require a
|
||||||
// second per-frame CPU copy during normal playback.
|
// second per-frame CPU copy during normal playback.
|
||||||
@@ -83,4 +85,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif // CROSSDESK_GUI_PLATFORM_WINDOWS_OPENGL_VIDEO_RENDERER_H_
|
#endif // CROSSDESK_GUI_PLATFORM_OPENGL_VIDEO_RENDERER_H_
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
#include "display_stream_id.h"
|
#include "display_stream_id.h"
|
||||||
#include "localization.h"
|
#include "localization.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
#include "platform/windows_opengl_video_renderer.h"
|
#include "platform/opengl_video_renderer.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include "platform/metal_video_renderer.h"
|
#include "platform/metal_video_renderer.h"
|
||||||
@@ -229,7 +229,7 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
|||||||
frame_snapshot = props->front_frame_;
|
frame_snapshot = props->front_frame_;
|
||||||
video_width = props->video_width_;
|
video_width = props->video_width_;
|
||||||
video_height = props->video_height_;
|
video_height = props->video_height_;
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
if ((!frame_snapshot || frame_snapshot->empty()) &&
|
if ((!frame_snapshot || frame_snapshot->empty()) &&
|
||||||
props->thumbnail_frame_ && !props->thumbnail_frame_->empty()) {
|
props->thumbnail_frame_ && !props->thumbnail_frame_->empty()) {
|
||||||
frame_snapshot = props->thumbnail_frame_;
|
frame_snapshot = props->thumbnail_frame_;
|
||||||
@@ -238,11 +238,11 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
#if defined(_WIN32)
|
#if defined(__APPLE__)
|
||||||
auto* native_renderer = windows_opengl_video_renderer_.get();
|
|
||||||
#else
|
|
||||||
auto* native_renderer = mac_metal_video_renderer_.get();
|
auto* native_renderer = mac_metal_video_renderer_.get();
|
||||||
|
#else
|
||||||
|
auto* native_renderer = opengl_video_renderer_.get();
|
||||||
#endif
|
#endif
|
||||||
if ((!frame_snapshot || frame_snapshot->empty()) && native_renderer) {
|
if ((!frame_snapshot || frame_snapshot->empty()) && native_renderer) {
|
||||||
auto native_snapshot = std::make_shared<std::vector<unsigned char>>();
|
auto native_snapshot = std::make_shared<std::vector<unsigned char>>();
|
||||||
@@ -276,7 +276,7 @@ void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
if (native_renderer) {
|
if (native_renderer) {
|
||||||
native_renderer->DiscardStream(props->remote_id_);
|
native_renderer->DiscardStream(props->remote_id_);
|
||||||
video_frame_dirty_.store(true, std::memory_order_release);
|
video_frame_dirty_.store(true, std::memory_order_release);
|
||||||
@@ -343,7 +343,7 @@ void GuiRuntime::ResetRemoteSessionResources(
|
|||||||
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
props->front_frame_.reset();
|
props->front_frame_.reset();
|
||||||
props->back_frame_.reset();
|
props->back_frame_.reset();
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
props->thumbnail_frame_.reset();
|
props->thumbnail_frame_.reset();
|
||||||
props->thumbnail_width_ = 0;
|
props->thumbnail_width_ = 0;
|
||||||
props->thumbnail_height_ = 0;
|
props->thumbnail_height_ = 0;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
#include "display_stream_id.h"
|
#include "display_stream_id.h"
|
||||||
#include "localization.h"
|
#include "localization.h"
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
#include "platform/windows_opengl_video_renderer.h"
|
#include "platform/opengl_video_renderer.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include "platform/metal_video_renderer.h"
|
#include "platform/metal_video_renderer.h"
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
class WindowsOpenGlVideoRenderer;
|
class OpenGlVideoRenderer;
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
class MacMetalVideoRenderer;
|
class MacMetalVideoRenderer;
|
||||||
@@ -90,8 +90,8 @@ class GuiRuntime : protected gui_detail::GuiState {
|
|||||||
KeyboardController keyboard_;
|
KeyboardController keyboard_;
|
||||||
PeerEventHandler peer_events_;
|
PeerEventHandler peer_events_;
|
||||||
std::atomic<bool> video_frame_dirty_{false};
|
std::atomic<bool> video_frame_dirty_{false};
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
std::unique_ptr<WindowsOpenGlVideoRenderer> windows_opengl_video_renderer_;
|
std::unique_ptr<OpenGlVideoRenderer> opengl_video_renderer_;
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::unique_ptr<MacMetalVideoRenderer> mac_metal_video_renderer_;
|
std::unique_ptr<MacMetalVideoRenderer> mac_metal_video_renderer_;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
#include "file_transfer.h"
|
#include "file_transfer.h"
|
||||||
#include "localization.h"
|
#include "localization.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
#include "platform/windows_opengl_video_renderer.h"
|
#include "platform/opengl_video_renderer.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include "platform/metal_video_renderer.h"
|
#include "platform/metal_video_renderer.h"
|
||||||
@@ -319,7 +319,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
props->enable_mouse_control_ = false;
|
props->enable_mouse_control_ = false;
|
||||||
runtime->ResetRemoteServiceStatus(*props);
|
runtime->ResetRemoteServiceStatus(*props);
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
std::shared_ptr<std::vector<unsigned char>> native_snapshot;
|
std::shared_ptr<std::vector<unsigned char>> native_snapshot;
|
||||||
int native_snapshot_width = 0;
|
int native_snapshot_width = 0;
|
||||||
int native_snapshot_height = 0;
|
int native_snapshot_height = 0;
|
||||||
@@ -330,11 +330,10 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
props->thumbnail_frame_->empty();
|
props->thumbnail_frame_->empty();
|
||||||
}
|
}
|
||||||
if (needs_native_snapshot) {
|
if (needs_native_snapshot) {
|
||||||
#if defined(_WIN32)
|
#if defined(__APPLE__)
|
||||||
auto* native_renderer =
|
|
||||||
runtime->windows_opengl_video_renderer_.get();
|
|
||||||
#else
|
|
||||||
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
||||||
|
#else
|
||||||
|
auto* native_renderer = runtime->opengl_video_renderer_.get();
|
||||||
#endif
|
#endif
|
||||||
auto snapshot = std::make_shared<std::vector<unsigned char>>();
|
auto snapshot = std::make_shared<std::vector<unsigned char>>();
|
||||||
if (native_renderer && native_renderer->CopyLatestNv12(
|
if (native_renderer && native_renderer->CopyLatestNv12(
|
||||||
@@ -349,7 +348,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
props->front_frame_.reset();
|
props->front_frame_.reset();
|
||||||
props->back_frame_.reset();
|
props->back_frame_.reset();
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
if (native_snapshot &&
|
if (native_snapshot &&
|
||||||
(!props->thumbnail_frame_ || props->thumbnail_frame_->empty())) {
|
(!props->thumbnail_frame_ || props->thumbnail_frame_->empty())) {
|
||||||
props->thumbnail_frame_ = std::move(native_snapshot);
|
props->thumbnail_frame_ = std::move(native_snapshot);
|
||||||
@@ -363,11 +362,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
props->render_rect_dirty_ = true;
|
props->render_rect_dirty_ = true;
|
||||||
props->stream_cleanup_pending_ = true;
|
props->stream_cleanup_pending_ = true;
|
||||||
}
|
}
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
#if defined(_WIN32)
|
#if defined(__APPLE__)
|
||||||
auto* native_renderer = runtime->windows_opengl_video_renderer_.get();
|
|
||||||
#else
|
|
||||||
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
||||||
|
#else
|
||||||
|
auto* native_renderer = runtime->opengl_video_renderer_.get();
|
||||||
#endif
|
#endif
|
||||||
if (native_renderer) {
|
if (native_renderer) {
|
||||||
native_renderer->DiscardStream(remote_id);
|
native_renderer->DiscardStream(remote_id);
|
||||||
|
|||||||
@@ -7,18 +7,18 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "runtime/gui_runtime.h"
|
#include "runtime/gui_runtime.h"
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(__APPLE__)
|
||||||
#include "platform/windows_opengl_video_renderer.h"
|
|
||||||
#else
|
|
||||||
#include "platform/metal_video_renderer.h"
|
#include "platform/metal_video_renderer.h"
|
||||||
|
#else
|
||||||
|
#include "platform/opengl_video_renderer.h"
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kBackgroundSnapshotInterval = std::chrono::seconds(1);
|
constexpr auto kBackgroundSnapshotInterval = std::chrono::seconds(1);
|
||||||
@@ -45,14 +45,14 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
runtime->remote_sessions_.find(remote_id)->second.get();
|
runtime->remote_sessions_.find(remote_id)->second.get();
|
||||||
|
|
||||||
if (props->connection_established_) {
|
if (props->connection_established_) {
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
bool background_snapshot_only = false;
|
bool background_snapshot_only = false;
|
||||||
#if defined(_WIN32)
|
#if defined(__APPLE__)
|
||||||
auto* native_renderer = runtime->windows_opengl_video_renderer_.get();
|
|
||||||
using NativeVideoRenderer = WindowsOpenGlVideoRenderer;
|
|
||||||
#else
|
|
||||||
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
auto* native_renderer = runtime->mac_metal_video_renderer_.get();
|
||||||
using NativeVideoRenderer = MacMetalVideoRenderer;
|
using NativeVideoRenderer = MacMetalVideoRenderer;
|
||||||
|
#else
|
||||||
|
auto* native_renderer = runtime->opengl_video_renderer_.get();
|
||||||
|
using NativeVideoRenderer = OpenGlVideoRenderer;
|
||||||
#endif
|
#endif
|
||||||
if (native_renderer && native_renderer->IsReady()
|
if (native_renderer && native_renderer->IsReady()
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
@@ -101,7 +101,7 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
if (background_snapshot_only && props->thumbnail_frame_ &&
|
if (background_snapshot_only && props->thumbnail_frame_ &&
|
||||||
!props->thumbnail_frame_->empty() &&
|
!props->thumbnail_frame_->empty() &&
|
||||||
@@ -137,7 +137,7 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
props->video_size_ = video_frame->size;
|
props->video_size_ = video_frame->size;
|
||||||
|
|
||||||
props->front_frame_.swap(props->back_frame_);
|
props->front_frame_.swap(props->back_frame_);
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
props->thumbnail_frame_ = props->front_frame_;
|
props->thumbnail_frame_ = props->front_frame_;
|
||||||
props->thumbnail_width_ = video_frame->width;
|
props->thumbnail_width_ = video_frame->width;
|
||||||
props->thumbnail_height_ = video_frame->height;
|
props->thumbnail_height_ = video_frame->height;
|
||||||
@@ -149,7 +149,7 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
props->streaming_ = true;
|
props->streaming_ = true;
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
if (background_snapshot_only) {
|
if (background_snapshot_only) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ struct RemoteSession {
|
|||||||
std::mutex video_frame_mutex_;
|
std::mutex video_frame_mutex_;
|
||||||
std::shared_ptr<std::vector<unsigned char>> front_frame_;
|
std::shared_ptr<std::vector<unsigned char>> front_frame_;
|
||||||
std::shared_ptr<std::vector<unsigned char>> back_frame_;
|
std::shared_ptr<std::vector<unsigned char>> back_frame_;
|
||||||
#if defined(__APPLE__) || defined(_WIN32)
|
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
|
||||||
// Retains the most recent background CPU frame across disconnect cleanup
|
// Retains the most recent background CPU frame across disconnect cleanup
|
||||||
// so closing an unselected tab can still update its recent-item thumbnail.
|
// so closing an unselected tab can still update its recent-item thumbnail.
|
||||||
std::shared_ptr<std::vector<unsigned char>> thumbnail_frame_;
|
std::shared_ptr<std::vector<unsigned char>> thumbnail_frame_;
|
||||||
|
|||||||
+3
-2
@@ -279,7 +279,7 @@ function setup_targets()
|
|||||||
add_cxxflags("/bigobj")
|
add_cxxflags("/bigobj")
|
||||||
add_links("opengl32")
|
add_links("opengl32")
|
||||||
add_files("src/gui/platform/tray/win_tray.cpp",
|
add_files("src/gui/platform/tray/win_tray.cpp",
|
||||||
"src/gui/platform/windows_opengl_video_renderer.cpp")
|
"src/gui/platform/opengl_video_renderer.cpp")
|
||||||
add_includedirs("src/service/windows", {public = true})
|
add_includedirs("src/service/windows", {public = true})
|
||||||
elseif is_os("macosx") then
|
elseif is_os("macosx") then
|
||||||
add_files("src/gui/runtime/*.mm", "src/gui/platform/tray/*.mm",
|
add_files("src/gui/runtime/*.mm", "src/gui/platform/tray/*.mm",
|
||||||
@@ -287,7 +287,8 @@ function setup_targets()
|
|||||||
"src/gui/platform/metal_video_renderer.mm")
|
"src/gui/platform/metal_video_renderer.mm")
|
||||||
elseif is_os("linux") then
|
elseif is_os("linux") then
|
||||||
add_links("GL")
|
add_links("GL")
|
||||||
add_files("src/gui/platform/tray/linux_tray.cpp")
|
add_files("src/gui/platform/tray/linux_tray.cpp",
|
||||||
|
"src/gui/platform/opengl_video_renderer.cpp")
|
||||||
end
|
end
|
||||||
|
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
|
|||||||
Reference in New Issue
Block a user