-
Notifications
You must be signed in to change notification settings - Fork 23
RFC: 关于支持 macOS 和 Linux 的讨论 #6
Description
这不是一个 feat request,仅仅只是一个讨论。但是放在 discussion 感觉容易被忽略,因此放到这里。
我在自己尝试为 soluna 添加 mac 平台和 linux 的支持。代码在: https://github.com/cloudwu/soluna/compare/master...yuchanns:soluna:feat/multiplatform-support.diff
当然代码实现很粗造,主要是想先跑起来。
现在已经在 mac 上运行起来,并且可以运行 deepfuture:
目前遇到两个问题,想咨询一下云风的想法。我知道现在这块不是你关注的点,可能并不在意这些问题,不过我还是想提出来问问。因为我真的很想让它在另外两个平台也运行起来。
1. mac 平台,不支持在非主线程改变窗体属性
例如设置标题。会直接 panic。
我已经阅读过你的关于 SetWindowText 的文章,理解了添加 window service 的理由。另外我阅读 commit 发现你曾经尝试通过更改 CTX 的属性来通知主线程更改标题,当然这可能会有竞争问题。这也是为什么 macos 禁止非主线程更改窗体的原因。
这点我觉得 windows 的设计挺好的,不管什么线程都只是发送一个 message 排队修改。
我在想,是否可以通过类似的方式,将更改标题通过 message 发送给 soluna 主线程,例如:
function frame(count)
sendmessage("frame", count)
local cmd, data = barrier:wait() -- 返回 message 通知一些必须在主线程进行操作
commands[cmd](data) -- 更改窗体标题等
end看起来 event 只是一个 condvar 应该不支持这么改造,just an idea. :)
2. linux 平台,渲染只能在主线程
这个问题属于比较严重的问题了,直接动摇 soluna 现在的设计。
上面的分支,在 Linux 平台也可以构建,以及运行。但是起来之后黑屏,游戏本身还是可以通过鼠标点击交互触发逻辑运行,观看日志输出也是正常的,只不过看不见图形效果。
一开始我进行了断点调试,尝试发现代码错误。后来发现原因是非主线程渲染就会黑屏。(当然,可能是我理解错误。我只有写 web 后端业务的经验,对这方面知识积累为零。但是从下面的 demo 来看符合我的结论。)
注:demo 我是直接让 sonnet-4 生成的。
最小可渲染版本
#define SOKOL_IMPL
#define SOKOL_GLCORE
#include <stdint.h>
#include <stdio.h>
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "colorquad.glsl.h"
typedef struct {
float pos[4]; // position (x, y, z, w)
uint32_t idx; // idx
float color[4]; // c (color)
} vertex_t;
static struct {
sg_pass_action pass_action;
sg_pipeline pip;
sg_bindings bind;
sg_buffer vertex_buffer;
sg_buffer storage_buffer;
vs_params_t vs_params;
} state;
static void init(void) {
printf("Starting init...\n");
sg_setup(&(sg_desc){
.environment = sglue_environment(),
});
printf("Sokol setup complete\n");
state.pass_action = (sg_pass_action) {
.colors[0] = { .load_action=SG_LOADACTION_CLEAR, .clear_value={0.2f, 0.3f, 0.4f, 1.0f} }
};
printf("Init complete\n");
}
static void frame(void) {
sg_begin_pass(&(sg_pass) {
.action = state.pass_action,
.swapchain = sglue_swapchain(),
});
sg_end_pass();
sg_commit();
}
static void cleanup(void) {
sg_shutdown();
}
sapp_desc sokol_main(int argc, char* argv[]) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.cleanup_cb = cleanup,
.width = 800,
.height = 600,
.window_title = "ColorQuad Example",
};
}添加多线程之后就不 work 了,即使没有竞态,只是在一个线程中做这件事,也会黑屏。
#define SOKOL_IMPL
#define SOKOL_GLCORE
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "colorquad.glsl.h"
typedef struct {
float pos[4]; // position (x, y, z, w)
uint32_t idx; // idx
float color[4]; // c (color)
} vertex_t;
typedef struct {
pthread_t render_thread;
sem_t frame_start;
sem_t frame_done;
volatile int should_exit;
volatile int render_ready;
} thread_sync_t;
static struct {
sg_pass_action pass_action;
sg_pipeline pip;
sg_bindings bind;
sg_buffer vertex_buffer;
sg_buffer storage_buffer;
// vs_params_t vs_params;
thread_sync_t sync;
} state;
static void* render_thread_func(void* arg) {
printf("Render thread started (thread ID: %lu)\n", pthread_self());
while (!state.sync.should_exit) {
sem_wait(&state.sync.frame_start);
if (state.sync.should_exit) break;
printf("Render thread: executing render commands\n");
sg_begin_pass(&(sg_pass) {
.action = state.pass_action,
.swapchain = sglue_swapchain(),
});
sg_end_pass();
sg_commit();
printf("Render thread: render commands completed\n");
sem_post(&state.sync.frame_done);
}
printf("Render thread exiting\n");
return NULL;
}
static void init(void) {
printf("Starting init... (main thread ID: %lu)\n", pthread_self());
sg_setup(&(sg_desc){
.environment = sglue_environment(),
});
printf("Sokol setup complete\n");
state.pass_action = (sg_pass_action) {
.colors[0] = { .load_action=SG_LOADACTION_CLEAR, .clear_value={0.2f, 0.3f, 0.4f, 1.0f} }
};
state.sync.should_exit = 0;
state.sync.render_ready = 0;
if (sem_init(&state.sync.frame_start, 0, 0) != 0) {
printf("Failed to initialize frame_start semaphore\n");
return;
}
if (sem_init(&state.sync.frame_done, 0, 0) != 0) {
printf("Failed to initialize frame_done semaphore\n");
return;
}
if (pthread_create(&state.sync.render_thread, NULL, render_thread_func, NULL) != 0) {
printf("Failed to create render thread\n");
return;
}
state.sync.render_ready = 1;
printf("Init complete, render thread created\n");
}
static void frame(void) {
if (!state.sync.render_ready) return;
printf("Main thread: triggering render\n");
sem_post(&state.sync.frame_start);
printf("Main thread: waiting for render completion\n");
sem_wait(&state.sync.frame_done);
printf("Main thread: render completed\n");
}
static void cleanup(void) {
printf("Starting cleanup\n");
if (state.sync.render_ready) {
state.sync.should_exit = 1;
sem_post(&state.sync.frame_start);
pthread_join(state.sync.render_thread, NULL);
sem_destroy(&state.sync.frame_start);
sem_destroy(&state.sync.frame_done);
}
sg_shutdown();
printf("Cleanup complete\n");
}
sapp_desc sokol_main(int argc, char* argv[]) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.cleanup_cb = cleanup,
.width = 800,
.height = 600,
.window_title = "ColorQuad Example - Threading Test",
};
}