James Park d91bd327d7 libobs: libobs-d3d11: obs-filters: No excess alpha
Currently SrcBlendAlpha and DestBlendAlpha are both ONE, and can
combine together to form two. This is not a noticeable problem for
UNORM targets because the channels are clamped, but it will likely
become a problem if FLOAT targets are more widely used.

This change switches DestBlendAlpha to INVSRCALPHA, and starts
backgrounds as opaque black instead of transparent black. The blending
behavior of stacked transparents is preserved without overflowing the
alpha channel.
2019-04-07 18:16:56 -07:00

267 lines
6.3 KiB
C

#include <obs-module.h>
#include <util/circlebuf.h>
#define S_DELAY_MS "delay_ms"
#define T_DELAY_MS obs_module_text("DelayMs")
struct frame {
gs_texrender_t *render;
uint64_t ts;
};
struct gpu_delay_filter_data {
obs_source_t *context;
struct circlebuf frames;
uint64_t delay_ns;
uint64_t interval_ns;
uint32_t cx;
uint32_t cy;
bool target_valid;
bool processed_frame;
};
static const char *gpu_delay_filter_get_name(void *unused)
{
UNUSED_PARAMETER(unused);
return obs_module_text("GPUDelayFilter");
}
static void free_textures(struct gpu_delay_filter_data *f)
{
obs_enter_graphics();
while (f->frames.size) {
struct frame frame;
circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
gs_texrender_destroy(frame.render);
}
circlebuf_free(&f->frames);
obs_leave_graphics();
}
static size_t num_frames(struct circlebuf *buf)
{
return buf->size / sizeof(struct frame);
}
static void update_interval(struct gpu_delay_filter_data *f,
uint64_t new_interval_ns)
{
if (!f->target_valid) {
free_textures(f);
return;
}
f->interval_ns = new_interval_ns;
size_t num = (size_t)(f->delay_ns / new_interval_ns);
if (num > num_frames(&f->frames)) {
size_t prev_num = num_frames(&f->frames);
obs_enter_graphics();
circlebuf_upsize(&f->frames, num * sizeof(struct frame));
for (size_t i = prev_num; i < num; i++) {
struct frame *frame = circlebuf_data(&f->frames,
i * sizeof(*frame));
frame->render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
}
obs_leave_graphics();
} else if (num < num_frames(&f->frames)) {
obs_enter_graphics();
while (num_frames(&f->frames) > num) {
struct frame frame;
circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
gs_texrender_destroy(frame.render);
}
obs_leave_graphics();
}
}
static inline void check_interval(struct gpu_delay_filter_data *f)
{
struct obs_video_info ovi = {0};
uint64_t interval_ns;
obs_get_video_info(&ovi);
interval_ns = (uint64_t)ovi.fps_den * 1000000000ULL /
(uint64_t)ovi.fps_num;
if (interval_ns != f->interval_ns)
update_interval(f, interval_ns);
}
static inline void reset_textures(struct gpu_delay_filter_data *f)
{
f->interval_ns = 0;
free_textures(f);
check_interval(f);
}
static inline bool check_size(struct gpu_delay_filter_data *f)
{
obs_source_t *target = obs_filter_get_target(f->context);
uint32_t cx;
uint32_t cy;
f->target_valid = !!target;
if (!f->target_valid)
return true;
cx = obs_source_get_base_width(target);
cy = obs_source_get_base_height(target);
f->target_valid = !!cx && !!cy;
if (!f->target_valid)
return true;
if (cx != f->cx || cy != f->cy) {
f->cx = cx;
f->cy = cy;
reset_textures(f);
return true;
}
return false;
}
static void gpu_delay_filter_update(void *data, obs_data_t *s)
{
struct gpu_delay_filter_data *f = data;
f->delay_ns = (uint64_t)obs_data_get_int(s, S_DELAY_MS) * 1000000ULL;
/* full reset */
f->cx = 0;
f->cy = 0;
f->interval_ns = 0;
free_textures(f);
}
static obs_properties_t *gpu_delay_filter_properties(void *data)
{
obs_properties_t *props = obs_properties_create();
obs_properties_add_int(props, S_DELAY_MS, T_DELAY_MS, 0, 500, 1);
UNUSED_PARAMETER(data);
return props;
}
static void *gpu_delay_filter_create(obs_data_t *settings, obs_source_t *context)
{
struct gpu_delay_filter_data *f = bzalloc(sizeof(*f));
f->context = context;
obs_source_update(context, settings);
return f;
}
static void gpu_delay_filter_destroy(void *data)
{
struct gpu_delay_filter_data *f = data;
free_textures(f);
bfree(f);
}
static void gpu_delay_filter_tick(void *data, float t)
{
UNUSED_PARAMETER(t);
struct gpu_delay_filter_data *f = data;
f->processed_frame = false;
if (check_size(f))
return;
check_interval(f);
}
static void draw_frame(struct gpu_delay_filter_data *f)
{
struct frame frame;
circlebuf_peek_front(&f->frames, &frame, sizeof(frame));
gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
gs_texture_t *tex = gs_texrender_get_texture(frame.render);
if (tex) {
gs_eparam_t *image =
gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture(image, tex);
while (gs_effect_loop(effect, "Draw"))
gs_draw_sprite(tex, 0, f->cx, f->cy);
}
}
static void gpu_delay_filter_render(void *data, gs_effect_t *effect)
{
struct gpu_delay_filter_data *f = data;
obs_source_t *target = obs_filter_get_target(f->context);
obs_source_t *parent = obs_filter_get_parent(f->context);
if (!f->target_valid || !target || !parent || !f->frames.size) {
obs_source_skip_video_filter(f->context);
return;
}
if (f->processed_frame) {
draw_frame(f);
return;
}
struct frame frame;
circlebuf_pop_front(&f->frames, &frame, sizeof(frame));
gs_texrender_reset(frame.render);
gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
if (gs_texrender_begin(frame.render, f->cx, f->cy)) {
uint32_t parent_flags = obs_source_get_output_flags(target);
bool custom_draw = (parent_flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
bool async = (parent_flags & OBS_SOURCE_ASYNC) != 0;
struct vec4 clear_color;
vec4_set(&clear_color, 0.0f, 0.0f, 0.0f, 1.0f);
gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
gs_ortho(0.0f, (float)f->cx, 0.0f, (float)f->cy,
-100.0f, 100.0f);
if (target == parent && !custom_draw && !async)
obs_source_default_render(target);
else
obs_source_video_render(target);
gs_texrender_end(frame.render);
}
gs_blend_state_pop();
circlebuf_push_back(&f->frames, &frame, sizeof(frame));
draw_frame(f);
f->processed_frame = true;
UNUSED_PARAMETER(effect);
}
struct obs_source_info gpu_delay_filter = {
.id = "gpu_delay",
.type = OBS_SOURCE_TYPE_FILTER,
.output_flags = OBS_SOURCE_VIDEO,
.get_name = gpu_delay_filter_get_name,
.create = gpu_delay_filter_create,
.destroy = gpu_delay_filter_destroy,
.update = gpu_delay_filter_update,
.get_properties = gpu_delay_filter_properties,
.video_tick = gpu_delay_filter_tick,
.video_render = gpu_delay_filter_render
};