Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenGL: multiple custom shaders unsupported #4729

Closed
mitchellh opened this issue Jan 6, 2025 · 9 comments · Fixed by #5037 or #5294
Closed

OpenGL: multiple custom shaders unsupported #4729

mitchellh opened this issue Jan 6, 2025 · 9 comments · Fixed by #5037 or #5294

Comments

@mitchellh
Copy link
Contributor

Discussed in #4711

Originally posted by Talljoe January 6, 2025

Issue

On Linux/OpenGL, when specifying multiple custom shaders, only the last specified shader is rendered.

Repro steps

ghostty --custom-shader=left.glsl --custom-shader=right.glsl

left.glsl:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    fragColor = (uv.x < 0.5) 
        ? vec4(1., uv.x * 2., uv.y, 1.)
        : fragColor = texture(iChannel0, uv);
}

right.glsl:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    fragColor = (uv.x >= 0.5)
        ? vec4(uv.y, 2. - uv.x * 2., 1., 1.)
        : fragColor = texture(iChannel0, uv);
}

Expected

Screenshot from 2025-01-06 10-47-09

Actual

Screenshot from 2025-01-06 10-47-02

Notes

Looking at the code, I don't see on the OpenGL side where the output buffer is set as the input buffer for the next shader as is done in the Metal codepath.

By the way, uv.y is 1.0 at the top of the image and 0.0 at the bottom of the image. Not sure if this in intentional, but it is counter-intuitive (would expect purple at the bottom). I suspect this is different than Metal given some other shaders I've seen.

System:

OS: Linux Ubuntu 24.04.1 LTS X11
OpenGL version: 4.6 (Compatibility Profile) Mesa 24.0.9-0ubuntu0.3
Ghostty: v1.0.1-037de64ea2c3f6201948236559524986f41a72f7 (built from source)

(Repros on multiple Linux systems, both X11 and Wayland)

log:

Best GLX config is 0 for visual 0x459 with no RGBA Visual
Best GLX config is 96 for visual 0x5D6 with no SRGB
GLX config 97 for visual 0x5DD is the perfect choice
GLX version 1.4 found
 - Vendor: Mesa Project and SGI
 - Checked extensions:
        * GLX_ARB_create_context_profile: yes
        * GLX_EXT_create_context_es2_profile: yes
        * GLX_SGI_swap_control: yes
        * GLX_EXT_swap_control: yes
        * GLX_EXT_texture_from_pixmap: yes
        * GLX_SGI_video_sync: yes
        * GLX_EXT_buffer_age: yes
        * GLX_OML_sync_control: yes
        * GLX_ARB_multisample: yes
        * GLX_EXT_visual_rating: yes
Creating GLX context version 3.2 (debug:no, forward:no, legacy:no, es:no)
Realized GLX context[(nil)], indirect, version: 1.4
Using OpenGL backend X11 GLX
info(gtk): libadwaita version build=1.6.2 runtime=1.6.2
Creating GLX context version 3.2 (debug:no, forward:no, legacy:no, es:no)
Realized GLX context[(nil)], indirect, version: 1.4
Making GLX context 0x39689e20 current to drawable 83886089
OpenGL version: 4.6 (core)
* GLSL version: 4.60
* Max texture size: 16384

Enabled features (use GDK_GL_DISABLE env var to disable):
    debug: YES
    unpack-subimage: YES
    half-float: YES
    sync: YES
    base-instance: YES
    buffer-storage: YES
Making GLX context 0x39a9a1a0 current to drawable 83886094
OpenGL version: 4.6 (core)
* GLSL version: 4.60
* Max texture size: 16384

Enabled features (use GDK_GL_DISABLE env var to disable):
    debug: YES
    unpack-subimage: YES
    half-float: YES
    sync: YES
    base-instance: YES
    buffer-storage: YES
Creating GLX context version 3.2 (debug:no, forward:no, legacy:no, es:no)
Realized GLX context[(nil)], indirect, version: 1.4
Making GLX context 0x3bfb5300 current to drawable 83886114
OpenGL version: 4.6 (core)
* GLSL version: 4.60
* Max texture size: 16384

Enabled features (use GDK_GL_DISABLE env var to disable):
    debug: YES
    unpack-subimage: YES
    half-float: YES
    sync: YES
    base-instance: YES
    buffer-storage: YES
Creating GLX context version 3.3 (debug:no, forward:no, legacy:no, es:no)
Realized GLX context[(nil)], indirect, version: 1.4
Making GLX context 0x3c075a70 current to drawable 83886089
OpenGL version: 4.6 (core)
* GLSL version: 4.60
* Max texture size: 16384

Enabled features (use GDK_GL_DISABLE env var to disable):
    debug: YES
    unpack-subimage: YES
    half-float: YES
    sync: YES
    base-instance: YES
    buffer-storage: YES
info(grid): loaded OpenGL 4.6
info(shadertoy): loaded custom shader path=left.glsl
info(shadertoy): loaded custom shader path=right.glsl
@juliapaci
Copy link
Contributor

juliapaci commented Jan 12, 2025

both shaders get loaded, the issue is that "right.glsl" is overwriting "left.glsl"'s work in fragColor = texture(iChannel0, uv);. something like:
right.glsl

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    if(uv.x >= 0.5)
        fragColor = vec4(uv.y, 2. - uv.x * 2., 1., 1.);
}

works fine:
Image
maybe the way other graphics api treat undefined fragment colours will differ though (im unable to try metal)

By the way, uv.y is 1.0 at the top of the image and 0.0 at the bottom of the image

yeah, texture coordinates in opengl are (usually) normalized from 0 to 1. you'll see most people on shadertoy create a uv variable as vec2 uv = fragCoord/iResolution.xy * 2 - 1; to centre it

@juliapaci
Copy link
Contributor

oh wait i seem to have misinterpreted the issue.

On Linux/OpenGL, when specifying multiple custom shaders, only the last specified shader is rendered.

would not be the correct way to describe it. it should be something like "the output of fragment shader is not being correctly updated in the fbo backbuffer texture" or something.

@Talljoe
Copy link

Talljoe commented Jan 13, 2025

Ah, sorry. I could have been more clear. The shaders all run, but they can't access the texture from the previous shader like they (I presume) can on Metal because the output of each shader is not copied to the texture buffer. The repro is a trivial example, but if you wanted to add bloom on top of a starfield then there wouldn't be a way to do it.

By the way, uv.y is 1.0 at the top of the image and 0.0 at the bottom of the image

yeah, texture coordinates in opengl are (usually) normalized from 0 to 1. you'll see most people on shadertoy create a uv variable as vec2 uv = fragCoord/iResolution.xy * 2 - 1; to centre it

I was confused about which way was up, so ignore that. I remembered running this shader (License: CC3 BY-NC-SA) and the animation was flipped (rocket came from the top of the screen) so I thought perhaps that Y was flipped on Linux.

@juliapaci
Copy link
Contributor

ah, sorry about the confusion. does my fix solve your issue?

im not exactly sure if this would be a good idea because multiple shaders sampling from the default screen is important for different effects which happen to be split across many shaders. maybe this should be a toggle able feature (but even then it seems a bit niche)?

@juliapaci
Copy link
Contributor

also, have you tested the same behavior using another graphics api? if it matches your example then this wouldn't be a bug

@github-actions github-actions bot added this to the 1.1.0 milestone Jan 20, 2025
@Talljoe
Copy link

Talljoe commented Jan 22, 2025

It's still broken, but in the opposite way.

Image

Now, it appears that all of the shaders are being rendered but only the first shader makes it to the screen (previously, the later shaders would get the original terminal texture overwriting whatever the previous shaders did). I have tried this with a number of shaders (from m-ahdal's repository and elsewhere) and only the first shader makes it to the screen, no matter how many I specify. For example, starfield and bettercrt.

In the Metal side of things, there is a copy step that happens in between each custom shader to copy the output of one custom shader to the input of the next.

Here are updated repros to also show the terminal text:
left.glsl

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    vec4 terminalColor = texture(iChannel0, uv);
    fragColor = mix(
        mix(vec4(1., uv.x * 2., uv.y, 1.), terminalColor, step(0.05, length(terminalColor.rgb))),
        terminalColor,
        step(0.5, uv.x)
    );
}

right.glsl

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    vec4 terminalColor = texture(iChannel0, uv);
    fragColor = mix(
        terminalColor,
        mix(vec4(uv.y, 2. - uv.x * 2., 1., 1.), terminalColor, step(0.05, length(terminalColor.rgb))),
        step(0.5, uv.x)
    );
}

Expected:
Image

Actual:
Image

@juliapaci
Copy link
Contributor

juliapaci commented Jan 22, 2025

ah, sorry again. before i continue, could you please tell me if #5294 works for all your tests? im getting your expected output but its not exactly the same as how the Metal one works so i just want to make sure. Thanks!

@Talljoe
Copy link

Talljoe commented Jan 22, 2025

#5294 looks good

Image

And here's one with three shaders (starfield->negative->bettercrt) working as expected

Image

@juliapaci
Copy link
Contributor

alright cool. im slightly hesitant for the pr to be merged because im truly unsure if the way i did it was the best way. i would like to wait for someone to review it first (preferably someone who is familiar with the Metal system because i think keeping them similar is important)

mitchellh added a commit that referenced this issue Jan 23, 2025
NEEDS REVIEW

continuation of #5037
resolves #4729 

renders all shaders to the default buffer and then copies it to the
designated custom shader texture.

this is a draft pr because:
- it introduces a new shader "pipeline" which doesnt fit in with how the
system was designed to work (which is only rendering to the fbo)
- im not sure if this is the best way to achieve shaders being able to
sample their output while also drawing to the screen. the cusom fbo
(previous implementation) was useful in that it modularized the custom
shader stage in rendering

---------

Co-authored-by: Mitchell Hashimoto <m@mitchellh.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants