Skip to content

Commit

Permalink
Alpha-correct blur by using extra premul step
Browse files Browse the repository at this point in the history
  • Loading branch information
y2kcyborg committed May 28, 2024
1 parent 84d2fc1 commit 5c8b695
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 83 deletions.
123 changes: 91 additions & 32 deletions data/shaders/gaussian_1d.effect
Original file line number Diff line number Diff line change
Expand Up @@ -30,46 +30,105 @@ float offsetLookup(uint i) {
return offset[i/4u][i%4u];
}

float4 PremulToStraight(float4 col)
{
col.rgb = saturate(col.rgb / max(col.a, 0.0001f));
return col;
}

float4 StraightToPremul(float4 col)
{
col.rgb *= col.a;
return col;
}


VertData mainTransform(VertData v_in)
{
v_in.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
return v_in;
}

float4 mainImage(VertData v_in) : TARGET
float4 SampleBlur(float2 uv)
{
// DO THE BLUR

// 1. Sample incoming pixel, multiply by weight[0]
float weight = weightLookup(0);
float4 col = image.Sample(textureSampler, uv) * weight;
float total_weight = weight;

// 2. March out from incoming pixel, multiply by corresponding weight.
for (uint i = 1; i < kernel_size; i++)
{
float weight = weightLookup(i);
float offset = offsetLookup(i);
total_weight += 2.0 * weight;
col += image.Sample(textureSampler, uv + (offset * texel_step)) * weight;
col += image.Sample(textureSampler, uv - (offset * texel_step)) * weight;
}
col /= total_weight;
return col;
}

float4 mainStraightToPremul(VertData v_in) : TARGET
{
return StraightToPremul(image.Sample(textureSampler, v_in.uv));
}

float4 mainPremulToStraight(VertData v_in) : TARGET
{
return PremulToStraight(image.Sample(textureSampler, v_in.uv));
}

float4 mainBlur(VertData v_in) : TARGET
{
// Input image is premultiplied
float4 col = SampleBlur(v_in.uv);
// Output is still premultiplied
return col;
}

float4 mainBlurPremulToStraight(VertData v_in) : TARGET
{
// Input image is premultiplied
float4 col = SampleBlur(v_in.uv);
// Output is straight alpha
return PremulToStraight(col);
}

technique DrawStraightToPremul
{
pass
{
vertex_shader = mainTransform(v_in);
pixel_shader = mainStraightToPremul(v_in);
}
}

technique DrawPremulToStraight
{
pass
{
vertex_shader = mainTransform(v_in);
pixel_shader = mainPremulToStraight(v_in);
}
}

technique DrawBlur
{
// DO THE BLUR
// 1. Sample incoming pixel, multiply by weight[0]
float4 sample0 = image.Sample(textureSampler, v_in.uv);
// Convert to premul alpha
sample0.rgb *= sample0.a;
// Now it's cool to multiply and add col
float4 col = sample0 * weightLookup(0);
float total_weight = weightLookup(0);

// 2. March out from incoming pixel, multiply by corresponding weight.
for(uint i=1; i<kernel_size; i++) {
float weight = weightLookup(i);
float offset = offsetLookup(i);
total_weight += 2.0*weight;
float4 sampleL = image.Sample(textureSampler, v_in.uv + (offset * texel_step));
sampleL.rgb *= sampleL.a;
float4 sampleR = image.Sample(textureSampler, v_in.uv - (offset * texel_step));
sampleR.rgb *= sampleR.a;
col += sampleL * weight;
col += sampleR * weight;
}
col /= total_weight;
// Safely convert back to straight alpha
col.rgb = saturate(col.rgb / max(col.a, 0.0001f));
return col;
pass
{
vertex_shader = mainTransform(v_in);
pixel_shader = mainBlur(v_in);
}
}

technique Draw
technique DrawBlurPremulToStraight
{
pass
{
vertex_shader = mainTransform(v_in);
pixel_shader = mainImage(v_in);
}
pass
{
vertex_shader = mainTransform(v_in);
pixel_shader = mainBlurPremulToStraight(v_in);
}
}
127 changes: 76 additions & 51 deletions src/blur/blur.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,34 +50,20 @@ void blur_destroy(retro_effects_filter_data_t *filter)
filter->blur_data = NULL;
}

/*
* Performs an area blur using the gaussian kernel. Blur is
* equal in both x and y directions.
*/
void gaussian_area_blur(gs_texture_t *texture, blur_data_t *data)
static void gaussian_area_blur_tech(gs_texture_t *src, gs_texrender_t *dest, blur_data_t *data, char const* technique)
{
gs_effect_t *effect = data->gaussian_effect;

if (!effect || !texture) {
return;
}

uint32_t width = gs_texture_get_width(texture);
uint32_t height = gs_texture_get_height(texture);
uint32_t width = gs_texture_get_width(src);
uint32_t height = gs_texture_get_height(src);

if (data->radius < MIN_GAUSSIAN_BLUR_RADIUS) {
data->blur_output =
create_or_reset_texrender(data->blur_output);
texrender_set_texture(texture, data->blur_output);
return;
if (data->param_uv_size) {
struct vec2 uv_size;
uv_size.x = (float)width;
uv_size.y = (float)height;
gs_effect_set_vec2(data->param_uv_size, &uv_size);
}

data->blur_buffer_1 = create_or_reset_texrender(data->blur_buffer_1);

// 1. First pass- apply 1D blur kernel to horizontal dir.
gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture_srgb(image, texture);

switch (data->device_type) {
case GS_DEVICE_DIRECT3D_11:
if (data->param_weight) {
Expand All @@ -98,63 +84,102 @@ void gaussian_area_blur(gs_texture_t *texture, blur_data_t *data)
}
}

const int k_size = (int)data->kernel_size;
if (data->param_kernel_size) {
const int k_size = (int)data->kernel_size;
gs_effect_set_int(data->param_kernel_size, k_size);
}

struct vec2 texel_step;
texel_step.x = 1.0f / (float)width;
texel_step.y = 0.0f;
if (data->param_texel_step) {
gs_effect_set_vec2(data->param_texel_step, &texel_step);
}
gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture_srgb(image, src);

const bool previous_framebuffer_srgb = gs_framebuffer_srgb_enabled();
gs_enable_framebuffer_srgb(true);

set_blending_parameters();

if (gs_texrender_begin(data->blur_buffer_1, width, height)) {
if (gs_texrender_begin(dest, width, height)) {
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f,
100.0f);
while (gs_effect_loop(effect, "Draw"))
gs_draw_sprite(texture, 0, width, height);
gs_texrender_end(data->blur_buffer_1);
while (gs_effect_loop(effect, technique))
gs_draw_sprite(src, 0, width, height);
gs_texrender_end(dest);
}

// 2. Save texture from first pass in variable "texture"
gs_texture_t *texture2 = gs_texrender_get_texture(data->blur_buffer_1);
gs_blend_state_pop();

// 3. Second Pass- Apply 1D blur kernel vertically.
image = gs_effect_get_param_by_name(effect, "image");
gs_effect_set_texture_srgb(image, texture2);
gs_enable_framebuffer_srgb(previous_framebuffer_srgb);
}

static void gaussian_area_blur_premul(gs_texture_t *src, gs_texrender_t *dest, blur_data_t *data)
{
gaussian_area_blur_tech(src, dest, data, "DrawStraightToPremul");
}

if (data->device_type == GS_DEVICE_OPENGL &&
data->param_kernel_texture) {
gs_effect_set_texture(data->param_kernel_texture,
data->kernel_texture);
static void gaussian_area_blur_h(gs_texture_t *src, gs_texrender_t *dest, blur_data_t *data)
{
uint32_t width = gs_texture_get_width(src);
uint32_t height = gs_texture_get_height(src);

struct vec2 texel_step;
texel_step.x = 1.0f / (float)width;
texel_step.y = 0.0f;
if (data->param_texel_step) {
gs_effect_set_vec2(data->param_texel_step, &texel_step);
}

gaussian_area_blur_tech(src, dest, data, "DrawBlur");
}

static void gaussian_area_blur_v(gs_texture_t *src, gs_texrender_t *dest, blur_data_t *data)
{
uint32_t width = gs_texture_get_width(src);
uint32_t height = gs_texture_get_height(src);

struct vec2 texel_step;
texel_step.x = 0.0f;
texel_step.y = 1.0f / (float)height;
if (data->param_texel_step) {
gs_effect_set_vec2(data->param_texel_step, &texel_step);
}

data->blur_output = create_or_reset_texrender(data->blur_output);
gaussian_area_blur_tech(src, dest, data, "DrawBlurPremulToStraight");
}

if (gs_texrender_begin(data->blur_output, width, height)) {
gs_ortho(0.0f, (float)width, 0.0f, (float)height, -100.0f,
100.0f);
while (gs_effect_loop(effect, "Draw"))
gs_draw_sprite(texture, 0, width, height);
gs_texrender_end(data->blur_output);
/*
* Performs an area blur using the gaussian kernel. Blur is
* equal in both x and y directions.
*/
void gaussian_area_blur(gs_texture_t *texture, blur_data_t *data)
{
// Ensure data exists, intermediate and output textures exist
gs_effect_t *effect = data->gaussian_effect;

if (!effect || !texture) {
return;
}

gs_blend_state_pop();
if (data->radius < MIN_GAUSSIAN_BLUR_RADIUS) {
data->blur_output =
create_or_reset_texrender(data->blur_output);
texrender_set_texture(texture, data->blur_output);
return;
}

gs_enable_framebuffer_srgb(previous_framebuffer_srgb);
// 1. Prepare by converting to premultiplied alpha
// This ensures that the bilinear filtering in the blur passes gives correct results
data->blur_buffer_1 = create_or_reset_texrender(data->blur_buffer_1);
gaussian_area_blur_premul(texture, data->blur_buffer_1, data);

// 2. First pass- apply 1D blur kernel to horizontal dir.

gs_texture_t *buffer_1_tex = gs_texrender_get_texture(data->blur_buffer_1);
data->blur_buffer_2 = create_or_reset_texrender(data->blur_buffer_2);
gaussian_area_blur_h(buffer_1_tex, data->blur_buffer_2, data);

// 3. Second Pass- Apply 1D blur kernel vertically.
gs_texture_t *buffer_2_tex = gs_texrender_get_texture(data->blur_buffer_2);
data->blur_output = create_or_reset_texrender(data->blur_output);
gaussian_area_blur_v(buffer_2_tex, data->blur_output, data);
}

/*
Expand Down

0 comments on commit 5c8b695

Please sign in to comment.