Skip to content

Commit 35642b0

Browse files
authored
Use SendTexture to lower the overhead of the spout filter (#46)
* Use SendTexture() to avoid some extra memory copies * Double-buffer to give good perf without a flush * Use intermediate texture to handle sRGB * Tidy Authored-by: @millenium-cyborg Closes #42
1 parent a8f3e2a commit 35642b0

File tree

1 file changed

+62
-73
lines changed

1 file changed

+62
-73
lines changed

source/win-spout-filter.cpp

Lines changed: 62 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ struct win_spout_filter
2323
spoutDX* filter_sender;
2424
obs_source_t* source_context;
2525
const char* sender_name;
26-
pthread_mutex_t mutex;
2726
uint32_t width;
2827
uint32_t height;
29-
gs_texrender_t* texrender;
28+
gs_texrender_t* texrender_curr;
29+
gs_texrender_t* texrender_prev;
30+
gs_texrender_t* texrender_intermediate;
3031
gs_stagesurf_t* stagesurface;
3132
video_t* video_output;
3233
uint8_t* video_data;
@@ -80,17 +81,6 @@ void win_spout_filter_getdefaults(obs_data_t* defaults)
8081
obs_module_text("defaultfiltername"));
8182
}
8283

83-
void win_spout_filter_raw_video(void* data, video_data* frame)
84-
{
85-
struct win_spout_filter* context = (win_spout_filter*)data;
86-
87-
if (!frame|| !frame->data[0]) return;
88-
89-
pthread_mutex_lock(&context->mutex);
90-
context->filter_sender->SendImage(frame->data[0], context->width, context->height);
91-
pthread_mutex_unlock(&context->mutex);
92-
}
93-
9484
void win_spout_offscreen_render(void* data, uint32_t cx, uint32_t cy)
9585
{
9686

@@ -104,76 +94,76 @@ void win_spout_offscreen_render(void* data, uint32_t cx, uint32_t cy)
10494
uint32_t width = obs_source_get_base_width(target);
10595
uint32_t height = obs_source_get_base_height(target);
10696

107-
gs_texrender_reset(context->texrender);
108-
109-
if (gs_texrender_begin(context->texrender, width, height))
110-
{
97+
// Render the target to an intemediate format in sRGB-aware format
98+
gs_texrender_reset(context->texrender_intermediate);
99+
if (gs_texrender_begin(context->texrender_intermediate, width, height)) {
111100
struct vec4 background;
112101
vec4_zero(&background);
113102

114103
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
115-
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f, 100.0f);
104+
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f,
105+
100.0f);
116106

117107
gs_blend_state_push();
118108
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
119109

120110
obs_source_video_render(target);
121111

122112
gs_blend_state_pop();
123-
gs_texrender_end(context->texrender);
113+
gs_texrender_end(context->texrender_intermediate);
114+
}
115+
116+
// Use the default effect to render it back into a format Spout accepts
117+
gs_texrender_reset(context->texrender_curr);
118+
if (gs_texrender_begin(context->texrender_curr, width, height))
119+
{
120+
struct vec4 background;
121+
vec4_zero(&background);
124122

125-
if (context->width != width || context->height != height)
126-
{
123+
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
124+
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f, 100.0f);
127125

128-
gs_stagesurface_destroy(context->stagesurface);
129-
context->stagesurface = gs_stagesurface_create(width, height, GS_BGRA);
126+
gs_blend_state_push();
127+
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
130128

131-
video_output_info video_out = { 0 };
132-
video_out.format = VIDEO_FORMAT_BGRA;
133-
video_out.width = width;
134-
video_out.height = height;
135-
video_out.fps_den = context->video_info.fps_den;
136-
video_out.fps_num = context->video_info.fps_num;
137-
video_out.cache_size = 16;
138-
video_out.colorspace = VIDEO_CS_DEFAULT;
139-
video_out.range = VIDEO_RANGE_DEFAULT;
140-
video_out.name = obs_source_get_name(context->source_context);
129+
// To get sRGB handling, render with the default effect
130+
gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
131+
gs_texture_t *tex = gs_texrender_get_texture(context->texrender_intermediate);
132+
if (tex) {
133+
const bool linear_srgb = gs_get_linear_srgb();
141134

142-
video_output_close(context->video_output);
135+
const bool previous = gs_framebuffer_srgb_enabled();
136+
gs_enable_framebuffer_srgb(linear_srgb);
143137

144-
context->width = width;
145-
context->height = height;
146-
video_output_open(&context->video_output, &video_out);
147-
video_output_connect(context->video_output, nullptr, win_spout_filter_raw_video, context);
138+
gs_eparam_t *image =
139+
gs_effect_get_param_by_name(effect, "image");
140+
if (linear_srgb)
141+
gs_effect_set_texture_srgb(image, tex);
142+
else
143+
gs_effect_set_texture(image, tex);
148144

145+
while (gs_effect_loop(effect, "Draw"))
146+
gs_draw_sprite(tex, 0, width, height);
149147

148+
gs_enable_framebuffer_srgb(previous);
150149
}
151150

152-
struct video_frame output_frame;
153-
if (video_output_lock_frame(context->video_output,
154-
&output_frame, 1, obs_get_video_frame_time()))
155-
{
156-
if (context->video_data) {
157-
gs_stagesurface_unmap(context->stagesurface);
158-
context->video_data = nullptr;
159-
}
160-
161-
gs_stage_texture(context->stagesurface,
162-
gs_texrender_get_texture(context->texrender));
163-
gs_stagesurface_map(context->stagesurface,
164-
&context->video_data, &context->video_linesize);
165-
166-
uint32_t linesize = output_frame.linesize[0];
167-
for (uint32_t i = 0; i < context->height; ++i) {
168-
uint32_t dst_offset = linesize * i;
169-
uint32_t src_offset = context->video_linesize * i;
170-
memcpy(output_frame.data[0] + dst_offset,
171-
context->video_data + src_offset,
172-
linesize);
173-
}
174-
175-
video_output_unlock_frame(context->video_output);
151+
gs_blend_state_pop();
152+
gs_texrender_end(context->texrender_curr);
153+
154+
gs_texture_t *prev_tex =
155+
gs_texrender_get_texture(context->texrender_prev);
156+
if (prev_tex) {
157+
context->filter_sender->SendTexture((
158+
ID3D11Texture2D *)gs_texture_get_obj(prev_tex));
176159
}
160+
161+
// Swap the buffers
162+
// Double-buffering avoids the need for a flush, and also fixes
163+
// some issues related to G-Sync.
164+
gs_texrender_t *tmp = context->texrender_curr;
165+
context->texrender_curr = context->texrender_prev;
166+
context->texrender_prev = tmp;
177167
}
178168
}
179169

@@ -194,7 +184,11 @@ void* win_spout_filter_create(obs_data_t* settings, obs_source_t* source)
194184
struct win_spout_filter* context = (win_spout_filter*)bzalloc(sizeof(win_spout_filter));
195185

196186
context->source_context = source;
197-
context->texrender = gs_texrender_create(GS_BGRA, GS_ZS_NONE);
187+
// Use a Spout-compatible texture format
188+
context->texrender_curr = gs_texrender_create(GS_BGRA_UNORM, GS_ZS_NONE);
189+
context->texrender_prev = gs_texrender_create(GS_BGRA_UNORM, GS_ZS_NONE);
190+
context->texrender_intermediate =
191+
gs_texrender_create(GS_BGRA, GS_ZS_NONE);
198192
context->sender_name = obs_data_get_string(settings, FILTER_PROP_NAME);
199193
context->video_data = nullptr;
200194

@@ -203,15 +197,9 @@ void* win_spout_filter_create(obs_data_t* settings, obs_source_t* source)
203197
obs_get_video_info(&context->video_info);
204198
win_spout_filter_update(context, settings);
205199

206-
207-
208200
if (openDX11(context))
209201
{
210-
pthread_mutex_init_value(&context->mutex);
211-
if (pthread_mutex_init(&context->mutex, NULL) == 0)
212-
{
213-
return context;
214-
}
202+
return context;
215203
}
216204

217205
blog(LOG_ERROR, "Failed to create spout output!");
@@ -234,8 +222,9 @@ void win_spout_filter_destroy(void* data)
234222
video_output_close(context->video_output);
235223
gs_stagesurface_unmap(context->stagesurface);
236224
gs_stagesurface_destroy(context->stagesurface);
237-
gs_texrender_destroy(context->texrender);
238-
pthread_mutex_destroy(&context->mutex);
225+
gs_texrender_destroy(context->texrender_intermediate);
226+
gs_texrender_destroy(context->texrender_prev);
227+
gs_texrender_destroy(context->texrender_curr);
239228
bfree(context);
240229
}
241230
}
@@ -259,7 +248,7 @@ struct obs_source_info create_spout_filter_info()
259248
struct obs_source_info win_spout_filter_info = {};
260249
win_spout_filter_info.id = "win_spout_filter";
261250
win_spout_filter_info.type = OBS_SOURCE_TYPE_FILTER;
262-
win_spout_filter_info.output_flags = OBS_SOURCE_VIDEO;
251+
win_spout_filter_info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB;
263252
win_spout_filter_info.get_name = win_spout_filter_getname;
264253
win_spout_filter_info.get_properties = win_spout_filter_getproperties;
265254
win_spout_filter_info.get_defaults = win_spout_filter_getdefaults;

0 commit comments

Comments
 (0)