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

Implement WebGL2 Backend #1686

Merged
merged 4 commits into from
Oct 7, 2021
Merged

Implement WebGL2 Backend #1686

merged 4 commits into from
Oct 7, 2021

Conversation

zicklag
Copy link
Contributor

@zicklag zicklag commented Jul 17, 2021

Connections
Works towards #1617 .

Closes #1740.

Description
Migrates the WebGL2 backend from gfx-hal-backend to wgpu-hal.

Testing
Tested WebGL ( Brave Browser ( Chrome fork ), and Firefox ) and GL backends on Ubuntu 20.04 in.


Currently only the cube, triangle, and msaa-line examples work. This is still work-in-progress, with some things still to be done, but I wanted to get the code up here, and there isn't a lot that I think will changing drastically.

image

Remaining Work

  • A little script for building and serving the examples for web
  • Fix fastclear bug workaround
  • Update shadows example to not use SSBOs on WebGL
  • Fix lack of sRGB conversion
  • Properly determining the web hardware limit values ( see adapter.rs:183 )
  • Fix webgl-lint errors
  • Fix issue with water rendering on water shader

@zicklag zicklag changed the title Implement WebGL Backend Implement WebGL2 Backend Jul 17, 2021
@zicklag zicklag force-pushed the webgl-backend branch 2 times, most recently from d5aab51 to 8667453 Compare July 19, 2021 16:37
@zicklag
Copy link
Contributor Author

zicklag commented Jul 20, 2021

@cwfitzgerald Sorry I had to drop off of chat, but I've got another issue.

When Naga compiles the shadow shader for GLSL ES, it includes certain features that aren't supported such as an array without a length. While that array isn't used, because we were going to use a different entrypoint, it doesn't matter because the GLSL won't compile.

I'm not seeing a way around needing a shader preprocessor. Something like #ifdef STORAGE checks.

I could trivially implement one with whatever syntax we want if you don't mind pulling in 1 dependency ( not counting quote and proc_macro2 ) from the peg crate. The implementation would probably cost < 100 lines of code and could even be dropped reasonably in framework.rs for the examples.

What do you think?

@kvark
Copy link
Member

kvark commented Jul 20, 2021

When Naga compiles the shadow shader for GLSL ES, it includes certain features that aren't supported such as an array without a length. While that array isn't used, because we were going to use a different entrypoint, it doesn't matter because the GLSL won't compile.

Where is this failing and how? Shadow example works on GLES backend generally on native.

When Naga's GLSL backend is invoked, it's given the exact entry point. And it already knows from the IR what global variables are used by this entry point. So technically it could be smart enough to avoid emitting the definition of this structure entirely if a different entry point is chosen.

@zicklag
Copy link
Contributor Author

zicklag commented Jul 20, 2021

Where is this failing and how? Shadow example works on GLES backend generally on native.

It fails on WebGL when uncommenting this line in the shader:

[[block]]
struct Lights {
    data: [[stride(96)]] array<Light>;
}

image

It works fine on Native OpenGL.

So technically it could be smart enough to avoid emitting the definition of this structure entirely if a different entry point is chosen.

Ah, that would be great. I could look into doing a PR for that, then.

@zicklag
Copy link
Contributor Author

zicklag commented Jul 22, 2021

Now we've got bunnymark and shadow exeamples working. The water example is almost working, but the water is not appearing, and I can narrow the issue down to a pixel depth calculation failing. If I take out the step in the shader that uses the that depth, it appears fine minus the slight bluring around the edges of the water that isn't there:

image

Here's the portion of the shader in question:

water.wgsl:

// I think the issue's in here
fn to_linear_depth(depth: f32) -> f32 {
    let z_n: f32 = 2.0 * depth - 1.0;
    let z_e: f32 = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
    return z_e;
}

[[stage(fragment)]]
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
    let reflection_colour = textureSample(reflection, colour_sampler, in.f_WaterScreenPos.xy).xyz;

    let pixel_depth = to_linear_depth(in.position.z);
    let normalized_coords = in.position.xy / vec2<f32>(uniforms.time_size_width.w, uniforms.viewport_height);
    let terrain_depth = to_linear_depth(textureSample(terrain_depth_tex, colour_sampler, normalized_coords).r);

    // This pixel depth here is the issue. If set to zero the water is visible
    let dist = terrain_depth - pixel_depth;
    let clamped = pow(smoothStep(0.0, 1.5, dist), 4.8);

    let final_colour = in.f_Light + reflection_colour;
    let t = smoothStep(1.0, 5.0, dist) * 0.2; //TODO: splat for mix()?
    let depth_colour = mix(final_colour, water_colour, vec3<f32>(t, t, t));

    return vec4<f32>(depth_colour, clamped * (1.0 - in.f_Fresnel));
}

generated glsl for:

float to_linear_depth(float depth) {
    float z_n = ((2.0 * depth) - 1.0);
    float z_e = (((2.0 * 10.0) * 400.0) / ((400.0 + 10.0) - (z_n * (400.0 - 10.0))));
    return z_e;
}

void main() {
    VertexOutput in1 = VertexOutput(gl_FragCoord, _vs2fs_location0, _vs2fs_location1, _vs2fs_location2);
    vec4 _expr16 = texture(_group_0_binding_1, vec2(in1.f_WaterScreenPos.xy));
    vec3 reflection_colour = _expr16.xyz;
    float _expr20 = to_linear_depth(in1.position.z);
    vec4 _expr24 = _group_0_binding_0.time_size_width;
    float _expr27 = _group_0_binding_0.viewport_height;
    vec2 normalized_coords = (in1.position.xy / vec2(_expr24.w, _expr27));
    vec4 _expr30 = texture(_group_0_binding_2, vec2(normalized_coords));
    float _expr32 = to_linear_depth(_expr30.x);
    float dist = (_expr32 - _expr20);
    float clamped = pow(smoothstep(0.0, 1.5, dist), 4.8);
    vec3 final_colour = (in1.f_Light + reflection_colour);
    float t = (smoothstep(1.0, 5.0, dist) * 0.2);
    vec3 depth_colour = mix(final_colour, vec3(0.0, 0.46, 0.95), vec3(t, t, t));
    _fs2p_location0 = vec4(depth_colour, (clamped * (1.0 - in1.f_Fresnel)));
    return;
}

Getting close!

@cwfitzgerald
Copy link
Member

For platforms that don't support Read-only depth stencil like OpenGL/WebGL (we should have a downlevel limit for this) we can do an explicit copy-texture-to-texture to another depth buffer so we don't get a cycle of textures.

@kvark
Copy link
Member

kvark commented Jul 22, 2021

In addition to @cwfitzgerald , this appears to be UB according to WebGL spec. I see that it works at least on some OpenGL ES devices, so I thought we may get away without another downlevel flag. Looks like WebGL is more strict about this, although I'd expect to see an actual error.

@zicklag zicklag force-pushed the webgl-backend branch 4 times, most recently from 9c3b6a4 to 8c434e1 Compare July 23, 2021 13:58
wgpu-types/src/lib.rs Outdated Show resolved Hide resolved
wgpu/examples/framework.rs Outdated Show resolved Hide resolved
wgpu/examples/shadow/main.rs Outdated Show resolved Hide resolved
wgpu/examples/shadow/main.rs Outdated Show resolved Hide resolved
wgpu-hal/src/gles/queue.rs Outdated Show resolved Hide resolved
@zicklag
Copy link
Contributor Author

zicklag commented Jul 25, 2021

Still don't bother reviewing because this is all work-in-progress, but I got sRGB conversion working!

image

image

bors bot added a commit that referenced this pull request Jul 28, 2021
1737: Use Bitshift Style for All Bitflag Consts r=cwfitzgerald a=zicklag

**Connections**
None exactly, but somewhat related to #1686 as I was going to put some of this change into that PR, but am splitting it out for ease of review.

**Description**
This is purely code-style related, but it makes it easier to read bitflag constants and much eaiser to add bitflags in the future.

**Testing**
Tested on Ubuntu 20.04


Co-authored-by: Zicklag <zicklag@katharostech.com>
@zicklag zicklag closed this Jul 28, 2021
@cwfitzgerald
Copy link
Member

👀

@zicklag
Copy link
Contributor Author

zicklag commented Jul 28, 2021

Whoops! That was an accident. I actually just pushed an update. :)

@zicklag zicklag reopened this Jul 28, 2021
@zicklag zicklag force-pushed the webgl-backend branch 4 times, most recently from aafb727 to b6d7a87 Compare July 28, 2021 21:32
@zicklag zicklag force-pushed the webgl-backend branch 5 times, most recently from d4dd43f to 5797c12 Compare October 7, 2021 14:03
wgpu-core/Cargo.toml Show resolved Hide resolved
wgpu/examples/framework.rs Outdated Show resolved Hide resolved
wgpu-hal/src/gles/queue.rs Show resolved Hide resolved
@kvark kvark merged commit 312828f into gfx-rs:master Oct 7, 2021
@kvark
Copy link
Member

kvark commented Oct 7, 2021

Filed #2031 to track the last unresolved thing

@zicklag
Copy link
Contributor Author

zicklag commented Oct 7, 2021

Perfect. I also opened #2032 to track the water example issue. Do we want a separate issue for WebGL lint issues?

@kvark
Copy link
Member

kvark commented Oct 7, 2021

Sure, and thank you!

@allsey87
Copy link

@zicklag this doesn't support OffscreenCanvas, right? Is there already an issue tracking that?

@anlumo
Copy link
Contributor

anlumo commented Jan 20, 2022

@zicklag this doesn't support OffscreenCanvas, right? Is there already an issue tracking that?

#1837

@haraldreingruber
Copy link
Contributor

haraldreingruber commented Apr 7, 2022

@zicklag @anlumo We might consider working on a PR for supporting WebGL2 with OffscreenCanvas.

Do you have an idea how difficult this might be or how to get started?
I would assume the internals of wgpu-hal/gles don't need to be touched as it is only the WebGL2Context that has to be created from a different source.
I guess the main challenge is connecting the wgpu/backend/web.rs instance_create_surface_from_offscreen_canvas fn with the GLES backend?

@zicklag
Copy link
Contributor Author

zicklag commented Apr 8, 2022

It might have already been implemented here: #1932.

@haraldreingruber
Copy link
Contributor

It might have already been implemented here: #1932.

Okay, I thought it might have been disabled here:

#[cfg(all(target_arch = "wasm32", not(feature = "webgl")))]

Will give it a try 👍

@zicklag
Copy link
Contributor Author

zicklag commented Apr 8, 2022

Oh, I'm not sure then. You might be right.

I don't remember what the state was in, and I haven't been following progress so I'm not sure where it's at now, but my gut reaction is that your initial comment above seemed correct about how you might go about it, and that it probably wouldn't be super difficult to get working.

@haraldreingruber-dedalus
Copy link
Contributor

haraldreingruber-dedalus commented Apr 13, 2022

I guess the main challenge is connecting the wgpu/backend/web.rs instance_create_surface_from_offscreen_canvas fn with the GLES backend?

It looks like this shouldn't be very difficult, but one challenge is that the WebGPU function instance_create_surface_from_offscreen_canvas is exposed directly, and is not part of the HAL API. Which makes sense, because WebGPU is not accesed via the HAL.
It is not yet clear to me, how to expose a similar function in an elegant manner from GLES.
I think I would duplicate this function in lib.rs with cfg(feature = webgl).

Is it possible to check if the current backend is GLES and directly access a function that is not part of the HAL API?

Any ideas welcome 😊

@zicklag
Copy link
Contributor Author

zicklag commented Apr 13, 2022

OK, yeah, I'm looking a little deeper and it looks like this should be easy.

So right after this struct I think you want to create a new impl Instance block where you expose a pub fn instance_create_surface_from_canvas. ( Does WebGL have a separate concept of an "offscreen canvas" or is it just a canvas with the CSS set to display: none? )

You want to implement this similar to the create_surface function, but instead of finding the canvas using the raw window handle, you'll simply return the Surface struct using the canvas that was passed into the function.

Then you'll need to create a function like this one with the cfg check for WebGL.

You'll need to do something similar to that function where it calls an instance function with self.instance.metal, but you need to access your instance_create_surface_from_canvas function on self.instance.gl instead.

Next you need to add a function like this one that calls the function you just wrote above. ( Again we're following the pattern of how the metal backend allows you to create surfaces directly. )

Finally you create one more function like this one to finish it off and expose the function for creating a surface from the canvas in the public API of wgpu::Instance.

I may have gotten something wrong, so hopefully that works or at least gives you direction. :)

@anlumo
Copy link
Contributor

anlumo commented Apr 13, 2022

Does WebGL have a separate concept of an "offscreen canvas" or is it just a canvas with the CSS set to display: none?

The type definition is:

typedef (OffscreenCanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext or GPUCanvasContext) OffscreenRenderingContext;

So the type itself is the same, but the biggest difference is that OffscreenCanvas is available in web workers. You don't have a DOM in web workers, so you can't create a regular canvas there.

@haraldreingruber-dedalus
Copy link
Contributor

Thanks a lot @zicklag for the great outline how start with the OffscreenCanvas feature. 🙏
Let's continue the discussion in #2603

( Does WebGL have a separate concept of an "offscreen canvas" or is it just a canvas with the CSS set to display: none? )

Yes, it's separate and is not part of the HTML DOM.
It basically only exists in the JS/WASM code, but after rendering an ImageBitmap can be transferred to a canvas.
We use it as a workaround to share resources between multiple canvases, which is not possible for WebGL.

@anlumo
Copy link
Contributor

anlumo commented Apr 15, 2022

It basically only exists in the JS/WASM code, but after rendering an ImageBitmap can be transferred to a canvas.

There's also transferControlToOffscreen, which allows you to move the renderer to a web worker, but still render directly to the screen. This avoids blocking the UI thread.

@haraldreingruber
Copy link
Contributor

Cool, thanks for sharing this approach @anlumo!

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.

[Android] Error:glMapBufferRange::<access> is not an accepted value