Skip to content

Fix filter-chain rendering#1279

Closed
Lordmau5 wants to merge 4 commits into
DistroAV:masterfrom
Lordmau5:feat/fix-filter-chain
Closed

Fix filter-chain rendering#1279
Lordmau5 wants to merge 4 commits into
DistroAV:masterfrom
Lordmau5:feat/fix-filter-chain

Conversation

@Lordmau5

@Lordmau5 Lordmau5 commented May 25, 2025

Copy link
Copy Markdown
Contributor

Preface: I almost went bald over this.

This fixes issue #969 (Filter scaling setting not respected) as well as improves on PR #1239 (Use previous filter as source for dedicated NDI output filter)

It uses obs_filter_get_target instead of obs_filter_get_parent, which will get the previous filter in the chain or, if there is no previous filter, the parent / main source.

Additionally, it uses obs_source_skip_video_filter instead of obs_source_video_render to properly handle drawing of the parent / main source when the Dedicated NDI Output filter is the first one in the chain (without it it will render the final output of the filter chain)

Screen.Recording.2025-05-25.220019.mp4

Also since we aren't drawing anything, we use the rendered versions of the callback calls.
Without those the results would look something like this where it somehow renders all the filters twice (as well as blank / black on the NDI receiver end)
image

@BitRate27

Copy link
Copy Markdown
Contributor

Your fix is looking good:
image
Nice job!

@BitRate27 BitRate27 self-assigned this May 26, 2025
@Trouffman Trouffman added this to the 6.1.1 milestone May 26, 2025
@BitRate27

Copy link
Copy Markdown
Contributor

According to my tests, these changes do allow the size of the Dedicated NDI output filter to reflect the size and content of the previous filter in the chain. You mentioned some edge cases where it does not work. Could you elaborate on the cases so we can determine if they are show stoppers?

@Lordmau5

Copy link
Copy Markdown
Contributor Author

As mentioned in Discord already, this behavior is currently in place:

20250526-1418-19.6363461.mp4

when the source is hidden it will still try to render whats in the chain and it does the weird double draw again

I've tried finding a way to make sure we just don't render it at all when the parent source is hidden but I am not sure if that exists...

@BitRate27

Copy link
Copy Markdown
Contributor

There is no way to tell if a source has been hidden in a scene. So I dont think this should stop us from implementing the code to react to the closed window. However, the 2x2 pixel empty frame seems a little kludgy. It would be better to destroy the sender and create when we get a non empty frame.

@Lordmau5

Copy link
Copy Markdown
Contributor Author

Well the 2x2 pixel code is part of the other PR, #1276

Though yes, I can see how to best implement that in the other one

@BitRate27
BitRate27 self-requested a review June 1, 2025 12:40
BitRate27
BitRate27 previously approved these changes Jun 1, 2025

@BitRate27 BitRate27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks good. Previous attempts to use the target instead of the parent failed, but using main_renderED_callback instead, seems to have fixed the black screen issue.

This will need to wait for 6.1.1.

Edit: This is incorrect, the black screen issue is not resolved!

@Lordmau5

Lordmau5 commented Jun 2, 2025

Copy link
Copy Markdown
Contributor Author

As I've mentioned in the Discord, I've found a much better way to fix not just the issue I've had with the disabled source and opening the filters tab or properties (where it started to render it a 2nd time with all filters applied once again)

I need more testing on the new code though. It also uses the render callbacks again and not the renderED ones.

Edit:
Even better: The render callbacks aren't necessary at all. Not even for async sources.

@BitRate27
BitRate27 dismissed their stale review June 4, 2025 14:36

Turns out this does not fix the problem of a black screen in the Dedicated NDI Output. Need to dismiss my review.

@BitRate27

Copy link
Copy Markdown
Contributor

After much investigation, it is not possible to output the previous filter output in the NDI dedicated output filter. This is mostly due to the way we extract the frame data from OBS. Since we use non-public APIs to achieve this, we are not left with a way to fix this deficiency. @Lordmau5 had this summary in Discord:

So the bottom line is that I see 2 approaches now:

Use synchronous video_render callback (which also works on Async sources since it's added as an effect filter) - The problem is that it will only send the video over NDI when the source is active in the main view as otherwise the video_render function isn't being called, however it would allow rendering the target (as in, previous filter or parent, depending on the position)
Another problem is that I've so far only tested it with frames, but not with audio as well (e.g. a media source) - I've noticed when the media source stutters in the main OBS output then so will the one that is on the receiver.,

Keep using render or rendered callbacks but draw the parent as a whole. That way we can still support Crop/Pad, Aspect Ratio, etc. but the problem is that it will only take the final output, no matter at which position the NDI filter is at.
As a compromise, it is possible to use the Source Clone plugin to make a clone of the specific source with no filters enabled, then adding the NDI filter on that one.

@Lordmau5

Lordmau5 commented Jun 6, 2025

Copy link
Copy Markdown
Contributor Author

I personally believe the best approach would be to go with the final output from the parent after all filters.

As I've said, this would be in line with what would be possible with Canvases as well once support for those is available.

That way users can already prepare themselves for how to use it going forward.

Do you want me to adapt the PR to have that functionality instead?

@Trouffman Trouffman modified the milestones: 6.1.1, 6.1.2 Jun 12, 2025
@Trouffman

Copy link
Copy Markdown
Collaborator

Bumping this up.

Source with filter (in this order):

  • Filter A
  • Filter B
  • NDI Output Filter
  • Filter C
  • Filter D

Here are some of the test cases I had in mind (please add / suggest)

Case 1 :

Filter chain rendering

  • NDI Output Filter Stream to be : Source + Filter A + Filter B

Filter C & D are only visible in OBS but do not output to NDI

Case 2

Hidden source behavior

  • Source is hidden
  • NDI Stream is not stopped
  • Source resolution should NOT change ( this will break havoc with hardware decoders & is more a hack than anything else, also for some user 2x2 will not be an acceptable solution)
  • Source could keep the same resolution BUT send and Alpha-frame (aka 100% transparent) - NB: Might not be possible

Case 3

Stop Hidden Source

  • Source is hidden
  • The filter should have an option similar to the one we have in "NDI Source" : "Keep active, send source", "Keep active, Send Transparent only", "Stop NDI Output".

@Lordmau5 Are Case 1 & 2 fixed with this PR?

Case 3 should be a dedicated PR as it will be quite a bigger change.

@Lordmau5
Lordmau5 marked this pull request as draft June 13, 2025 18:30
@Lordmau5

Lordmau5 commented Jun 13, 2025

Copy link
Copy Markdown
Contributor Author

The PR in its current form doesn't 100% fix the issue that's present currently.

The better approach I've mentioned a few times, which would then also prepare users for the eventual Canvases implementation, would be to use the synchronous video_render callback from the filter (Yes, this also works for Asynchronous sources)

Case 1 would not be functional in that way anymore - it would send whatever the final output of the parent / source is that is also visible inside OBS. However, this can be worked around by simply cloning the source without any active filters (e.g. with Exeldro's Source Clone plugin) and then attaching either just the filters necessary or not use filters at all (besides the Dedicated NDI Output filter of course)

Case 2 is not really possible with this approach as the source is simply not giving any video output in the video_render callback when the source is hidden in the main OBS view. It would pick up rendering again when the properties or filters menu for it is being opened again (as in, the popup) as that would cause it to be rendered again.

Case 3 would pretty much be the only realistic solution I see with this that wouldn't cause issues.
However, it seems that by using this native video_render callback of the filter with a Group does not cause higher CPU usage compared to the current implementation. The downside: Audio of the sources within that group will not be sent.
Audio with Canvases hasn't even been properly thought through on the OBS side unfortunately so I have no idea what the plan for that would be anyway sadly...

@Lordmau5 Lordmau5 closed this Aug 18, 2025
@Lordmau5
Lordmau5 deleted the feat/fix-filter-chain branch August 18, 2025 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants